Как работает механизм создания штучных признаков?
У меня возникают проблемы с пониманием того, как появляются ценности в штучной упаковке. Рассмотрим следующий код:
trait Fooer {
fn foo(&self);
}
impl Fooer for i32 {
fn foo(&self) { println!("Fooer on i32!"); }
}
fn main() {
let a = Box::new(32); // works, creates a Box<i32>
let b = Box::<i32>::new(32); // works, creates a Box<i32>
let c = Box::<Fooer>::new(32); // doesn't work
let d: Box<Fooer> = Box::new(32); // works, creates a Box<Fooer>
let e: Box<Fooer> = Box::<i32>::new(32); // works, creates a Box<Fooer>
}
Очевидно, что варианты a и b работают, тривиально. Однако вариант с не имеет, вероятно, потому что new
Функция принимает только значения одного типа, что не так, поскольку Fooer != i32
, Вариант d и e работает, что позволяет мне подозревать, что какое-то автоматическое преобразование из Box<i32>
в Box<Fooer>
выполняется
Итак, мои вопросы:
- Здесь происходит какое-то обращение?
- Если да, то какой механизм стоит за ним и как он работает? (Мне также интересны детали низкого уровня, то есть, как вещи представлены под капотом)
- Есть ли способ создать
Box<Fooer>
прямо изi32
? Если нет: почему бы и нет?
2 ответа
Однако вариант с не имеет, вероятно, потому что
new
Функция принимает только значения одного типа, что не так, посколькуFooer != i32
,
Нет, это потому что нет new
функция для Box<dyn Fooer>
, В документации:
impl<T> Box<T>
pub fn new(x: T) -> Box<T>
Большинство методов на Box<T>
разрешать T: ?Sized
, но new
определяется в impl
без T: ?Sized
связаны. Это означает, что вы можете только позвонить Box::<T>::new
когда T
это тип с известным размером. dyn Fooer
Негабаритный, так что просто нет new
метод для вызова.
На самом деле, этот метод не может существовать. Для того, чтобы что-то упаковать, нужно знать его размер. Чтобы передать его в функцию, вам нужно знать его размер. Чтобы даже иметь переменную, содержащую что-то, она должна иметь размер. Негабаритные типы, такие как dyn Fooer
может существовать только за "толстым указателем", то есть указателем на объект и указателем на реализацию Fooer
для этого объекта.
Как вы получаете толстый указатель? Вы начинаете с тонкого указателя и принуждаете его. Вот что происходит в этих двух строках:
let d: Box<Fooer> = Box::new(32); // works, creates a Box<Fooer>
let e: Box<Fooer> = Box::<i32>::new(32); // works, creates a Box<Fooer>
Box::new
возвращает Box<i32>
, который затем приводится к Box<Fooer>
, Вы можете считать это преобразованием, но Box
не изменен; все, что делает компилятор, это прикрепляет дополнительный указатель и забывает его первоначальный тип. Ответ Родриго более подробно описывает механику языкового уровня этого принуждения.
Надеюсь, все это объясняет, почему ответ
Есть ли способ создать
Box<Fooer>
прямо изi32
?
разве он не i32
должен быть в штучной упаковке, прежде чем вы можете стереть его тип. Это та же самая причина, по которой ты не можешь писать let x: Fooer = 10i32
,
связанные с
Я постараюсь объяснить, какие преобразования (приведения) происходят в вашем коде.
Существует маркерная черта с именем Unsize
что между другими:
Unsize реализован для:
T
являетсяUnsize<Trait>
когдаT: Trait
,- [...]
Эта черта, AFAIK, не используется непосредственно для принуждения. Вместо CoerceUnsized
используется. Эта черта реализована во многих случаях, некоторые из них вполне ожидаемы, такие как:
impl<'a, 'b, T, U> CoerceUnsized<&'a U> for &'b T
where
'b: 'a,
T: Unsize<U> + ?Sized,
U: ?Sized
что используется для принуждения &i32
в &Fooer
,
Интересная, не очень очевидная реализация этой черты, которая влияет на ваш код:
impl<T, U> CoerceUnsized<Box<U>> for Box<T>
where
T: Unsize<U> + ?Sized,
U: ?Sized
Это вместе с определением Unsize
маркер, можно несколько прочитать как: если U
это черта и T
инвентарь U
, затем Box<T>
может быть приведен в Box<U>
,
О вашем последнем вопросе:
Есть ли способ создать Box прямо из i32? Если нет: почему бы и нет?
Не то, что я знаю из. Проблема в том, что Box::new(T)
требуется значение размера, поскольку переданное значение перемещается в поле, а значения без размера не могут быть перемещены.
На мой взгляд, самый простой способ сделать это - просто написать:
let c = Box::new(42) as Box<Fooer>;
То есть вы создаете Box
соответствующего типа, а затем приведите к нестандартному (обратите внимание, что это выглядит очень похоже на ваш d
пример).