Я писал код, и у меня был трейт с методом, который принимает self
по значению. Я хочу вызвать этот метод для трейт-объекта Box
'd (используя Box
и его значение). Это возможно? Если да, то как?
С точки зрения кода минимальный пример выглядит как следующий (неполный) код:
trait Consumable {
fn consume(self) -> u64;
}
fn consume_box(ptr: Box<dyn Consumable>) -> u64 {
//what can I put here?
}
Мой вопрос заключается в том, как заполнить функцию consume_box
указанной сигнатурой, чтобы возвращаемое значение было тем значением, которое будет получено путем вызова consume
для значения Box
'd.
Я изначально написал
ptr.consume()
как тело функции, хотя я понимаю, что это не совсем правильная идея, поскольку она не передает тот факт, что я хочу, чтобы Box
потреблялся, а не только его содержимое, но это единственное, о чем я мог подумать. из. Не компилируется, выдает ошибку:
невозможно переместить значение типа dyn Consumable: размер dyn Consumable не может быть определен статически
Это было несколько неожиданно для меня, поскольку я новичок в Rust, я подумал, что, возможно, аргумент self
передается аналогично ссылке rvalue в C++ (это действительно то, что я хочу — в C++ я, вероятно, реализовал бы это с помощью метода с подпись virtual std::uint64_t consume() &&
, позволяя std::unique_ptr
очистить объект, перемещенный из, с помощью виртуального деструктора), но я думаю, что Rust действительно передает по значению, перемещая аргумент на место раньше, поэтому разумно, что он отклоняет код.
Проблема в том, что я не знаю, как добиться желаемого поведения, когда я могу потреблять трейт-объект Box
'd. Я попытался добавить метод к трейту с реализацией по умолчанию, думая, что это может принести мне что-то полезное в vtable:
trait Consumable {
fn consume(self) -> u64;
fn consume_box(me: Box<Self>) -> u64 {
me.consume()
}
}
Однако это приводит к ошибке
черта
Consumable
не может быть превращена в объект
когда я упоминаю тип Box<dyn Consumable>
- что не так уж удивительно, поскольку компилятор, выясняющий, что делать с функцией, тип аргумента которой меняется с Self
, было бы чудом.
Можно ли реализовать функцию consume_box
с предоставленной сигнатурой, даже изменив трейт при необходимости?
Если это полезно, точнее, это часть своего рода представления некоторых математических выражений - возможно, игрушечной моделью будут конкретные реализации, которые выглядят примерно так:
impl Consumable for u64 {
fn consume(self) -> u64 {
self
}
}
struct Sum<A, B>(A, B);
impl<A: Consumable, B: Consumable> Consumable for Sum<A, B> {
fn consume(self) -> u64 {
self.0.consume() + self.1.consume()
}
}
struct Product<A, B>(A, B);
impl<A: Consumable, B: Consumable> Consumable for Product<A, B> {
fn consume(self) -> u64 {
self.0.consume() * self.1.consume()
}
}
fn parse(&str) -> Option<Box<dyn Consumable> > {
//do fancy stuff
}
где, по большей части, вещи представляют собой простые старые данные (но произвольно большие их блоки, потенциально из-за дженериков), но также чтобы это было совместимо с передачей более непрозрачных дескрипторов для таких вещей - отсюда желание уметь работать с Box<dyn Consumable>
. По крайней мере, на уровне языка это хорошая модель того, чем я занимаюсь - единственными ресурсами, принадлежащими этим объектам, являются части памяти (ничего общего с многопоточностью и без самореферентных махинаций) - хотя это модель не отражает тот вариант использования, который у меня есть, когда для реализации полезно потреблять объект, а не просто читать его, и при этом она не моделирует должным образом то, что мне нужен «открытый» класс возможных сегментов, а не конечный набор возможностей (из-за чего сложно сделать что-то вроде enum
, которое напрямую представляет дерево) - поэтому я спрашиваю о передаче по значению, а не пытаюсь переписать его для передачи по ссылке.
@vallentin Я только что попытался изменить первую строку на trait Consumable: Sized
, но она жалуется, что «черта Consumable
не может быть в объекте ... потому что для этого требуется Self: Sized
» - я думаю, что она жалуется, что не знает размер объекта черты во время компиляции, а не размер какого-либо конкретного исполнителя (хотя я также не пытаюсь использовать какие-либо трюки с реализацией трейта для странных вещей)
@vallentin Я добавил несколько фрагментов, чтобы дать представление о том, какие конкретные типы будут реализовывать эту черту, если это полезно.
Вы можете потреблять из Box<dyn Trait>
, если параметр равен self: Box<Self>
:
trait Consumable {
fn consume(self) -> u64;
fn consume_box(self: Box<Self>) -> u64;
}
struct Foo;
impl Consumable for Foo {
fn consume(self) -> u64 {
42
}
fn consume_box(self: Box<Self>) -> u64 {
self.consume()
}
}
fn main() {
let ptr: Box<dyn Consumable> = Box::new(Foo);
println!("result is {}", ptr.consume_box());
}
Тем не менее, у этого есть раздражающий шаблон, заключающийся в необходимости реализовывать consume_box()
для каждой реализации; попытка определить реализацию по умолчанию приведет к ошибке «невозможно переместить значение типа Self
- размер Self
не может быть определен статически».
В общем, хотя это не поддерживается. dyn Consumable
представляет собой неразмерный тип, который очень ограничен, за исключением косвенности (через ссылки или Box
-подобные структуры). Это работает для приведенного выше случая, потому что Box
немного особенный (это единственный диспетчеризируемый тип, которым вы можете стать владельцем), а метод consume_box
не помещает self
в стек как динамический объект типажа (только в каждой реализации, где его конкретный).
Однако есть RFC 1909: Unsized RValues , который надеется ослабить некоторые из этих ограничений. Один из них может передавать параметры функции без размера, например self
в этом случае. Текущая реализация этого RFC принимает ваш исходный код при ночной компиляции с помощью unsized_fn_params
:
#![feature(unsized_fn_params)]
trait Consumable {
fn consume(self) -> u64;
}
struct Foo;
impl Consumable for Foo {
fn consume(self) -> u64 {
42
}
}
fn main () {
let ptr: Box<dyn Consumable> = Box::new(Foo);
println!("result is {}", ptr.consume());
}
Смотрите на детской площадке.
Я считаю
trait Consumable {
fn consume(self) -> u64;
}
fn consume_box(val: impl Consumable) -> u64 {
val.consume()
}
может делать то, что вы хотите. Я почти эксперт по Rust — или эксперт по C++, если на то пошло — но я думаю, что это должно работать почти так же, как семантика перемещения в C++, которую вы упомянули с точки зрения поведения памяти. Насколько я понимаю, это форма обобщения, в которой Rust реализует функцию для каждого типа, с которым вы ее вызываете.
Это не работает, потому что Box<dyn Consumable>
не реализует Consumable
.
@jthulhu извините за поздний ответ, я давно не был на SO. Вы конечно правы. С тем, что я узнал, так как я вижу несколько вещей, которые можно сделать, которые не решают исходный вопрос, но выглядят похожими, где размер известен во время компиляции. Вот площадка: play.rust-lang.org/… . Не знаю, как добавить это в эту тему, возможно, я предложу отредактировать предложенный ответ, который добавляет по крайней мере метод «где Self: Sized» с его ограничениями.
Если вы не используете nightly Rust, я написал макрос здесь. Он автоматически генерирует вторую функцию типажа.
trait Consumable {
fn consume(self) -> u64;
fn consume_box(me: Box<Self>) -> u64 ;
}
Это предотвращает превращение Consumable
в трейт-объект.
@jthulhu Его все еще можно использовать как трейт-объект, если параметр называется self
: площадка . Из безопасности объектаBox<Self>
можно отправить, а self: Box<Self>
является допустимым получателем.
Если размер
Self
известен во время компиляции, вы можете добавить: Sized
к свойству