В следующем коде у меня есть простой трейт A и структура Foo, которая реализует A...
Затем я определяю функцию, которая принимает ссылку на трейт-объект. Из main() я передаю ссылку на конкретный Foo. Это отлично работает и успешно вызывает метод в трейте.
trait A {
fn do_something(&self) {
println!("Doing something.");
}
}
struct Foo {}
impl A for Foo {}
fn main() {
let foo = Foo {};
make_do_something(&foo);
}
fn make_do_something(a: &dyn A) {
a.do_something();
}
Что здесь делает компилятор Rust? Конкретная ссылка Foo автоматически приводится к ссылке на объект Trait? То есть создается ли в куче новый трейт-объект, который ссылается на мой конкретный объект? Я не могу найти четкого описания того, как это работает в документации.
Спасибо @PitaJ. Эта ссылка помогает. Возможно, этот вопрос здесь неуместен, но я не уверен на 100%, почему трейт-объекты не имеют размера. То есть я считаю, что трейт-объект всегда должен содержать ровно 2 указателя — указатель на vtable и указатель на конкретный объект. Поэтому я не уверен, почему вам всегда нужно &dyn Foo или Box<dyn Foo> вместо просто dyn Foo. Например, почему dyn Foo размером ровно 2 указателя не может жить прямо в стеке? Я уверен, что мне не хватает какой-то тонкости.
Трейт-объект dyn Foo
сам по себе не является указателем, это просто тип (с динамическим размером и т. д.). Только в сочетании со ссылкой или интеллектуальным указателем (например, &dyn Foo
или Box<dyn Foo>
) он обрабатывается так, как вы описываете. &dyn Foo
— это ровно два указателя, которые живут в стеке.
Еще раз спасибо, @PitaJ. Ваш ответ и ответ ниже от Кевина Рида прояснили это для меня.
Конкретная ссылка Foo автоматически приводится к ссылке на объект Trait?
Да, &Foo
принуждают к &dyn A
. &Foo
состоит из указателя на данные Foo
(который в вашем примере имеет длину 0 байтов, но это не имеет значения); &dyn A
состоит из этого указателя, а также указателя на vtable, созданную из impl A for Foo
.
это новый трейт-объект, создаваемый в куче
Нет; трейт-объект требует дополнительных данных для ссылки на него, а не для его хранения.
Сравните это с другим основным типом динамического размера (!Sized
) в Rust, срезом: &Foo
содержит указатель на Foo
, а &[Foo]
содержит этот указатель и длину. Это могут быть одни и те же данные, на которые указывают (вы можете преобразовать в обе стороны), но указатель другой.
В целом можно сказать, что для указания на значение типа Sized
требуется только машинный указатель на данные; указание на значение типа !Sized
требует дополнительной информации (называемой «метаданными»).
Каждый указатель на тип с динамическим размером создается либо с помощью приведения, и в этом случае компилятор вставляет необходимые данные, известные во время компиляции (vtable для трейт-объекта или длину для указателя среза, полученного из массива указателей), или кодом библиотеки, который что-то знает о том, для чего он создает указатель (например, когда Vec<T>
создает &[T]
).
Да, они автоматически принуждаются. Это одна из форм «неразмерного принуждения». doc.rust-lang.org/reference/…