Как передать объект в штучной упаковке по значению в Rust?

#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 реализует функцию для каждого типа, с которым вы ее вызываете.