#rust #traits #trait-objects
#Ржавчина #Трейты #объекты-объекты
Вопрос:
Я писал некоторый код, и у меня была черта с методом, который принимает 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() amp;amp;
, позволяющего std::unique_ptr
очистить перемещенный-из объекта через виртуальный деструктор), но я предполагаю, что Rust действительно передает значение, перемещая аргумент на прежнее место, поэтому разумно, что он отклоняет код.
Проблема в том, что я не уверен, как получить желаемое поведение, при котором я могу использовать Box
объект d trait. Я попытался добавить метод к черте с реализацией по умолчанию, думая, что это может дать мне что-то полезное в виртуальной таблице:
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(amp;str) -> Option<Box<dyn Consumable> > {
//do fancy stuff
}
где, по большей части, это простые старые данные (но, возможно, сколь угодно большие их блоки из-за дженериков), но также это совместимо с передачей более непрозрачных дескрипторов для такого рода вещей — отсюда и желание иметь возможность работать Box<dyn Consumable>
. По крайней мере, на уровне языка, это хорошая модель того, чем я занимаюсь — единственными ресурсами, принадлежащими этим объектам, являются фрагменты памяти (ничего общего с многопоточностью и без самореферентных махинаций) — хотя эта модель не учитывает, что у меня есть вариант использованиятот, где для реализации полезно использовать объект, а не просто читать его, и при этом он не моделирует должным образом, что я хочу «открытый» класс возможных сегментов, а не конечный набор возможностей (что затрудняет выполнение чего-то вроде enum
, представляющего дерево напрямую) — следовательно, почему яя спрашиваю о передаче по значению, а не пытаюсь переписать его для передачи по ссылке.
Комментарии:
1. Если
Self
размер известен во время компиляции, то вы можете добавить: Sized
к черте2. @vallentin Я только что попытался изменить первую строку на
trait Consumable: Sized
, но он жалуется, что «признакConsumable
не может быть в объекте… потому что для этого требуетсяSelf: Sized
» — я думаю, он жалуется на то, что он не знает размер объекта признака во время компиляции, а не размер какого-либо конкретного разработчика (хотя я также не пытаюсь использовать какие-либо трюки с реализацией признака в странных вещах)3. @vallentin Я вставил несколько фрагментов, чтобы дать представление о том, какие конкретные типы будут реализовывать признак, если это полезно.
Ответ №1:
В настоящее время это не поддерживается. A dyn Consumable
представляет собой тип без размера, который очень ограничен, за исключением косвенного обращения (через ссылки или Box
подобные структуры).
Однако существует 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());
}
Смотрите на игровой площадке.
Ответ №2:
Я считаю
trait Consumable {
fn consume(self) -> u64;
}
fn consume_box(val: impl Consumable) -> u64 {
val.consume()
}
может делать то, что вы хотите. Я всего лишь эксперт по Rust — или эксперт по C , если на то пошло, — но я думаю, что это должно работать почти так же, как семантика перемещения в C , которую вы упомянули в терминах поведения памяти. Насколько я понимаю, это универсальная форма, в которой Rust реализует функцию для каждого типа, с которым вы ее вызываете.