Как работает механизм, лежащий в основе создания коробочных признаков?


Мне трудно понять, как возникают ценности упакованных черт. Рассмотрим следующий код:

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>
}
Очевидно, что варианты А и в работают тривиально. Однако вариант с этого не делает, вероятно, потому, что функция new принимает только значения одного и того же типа, что не имеет места, так как Fooer != i32. Варианты d и e работают, что позволяет мне подозревать, что выполняется какое-то автоматическое преобразование из Box<i32> в Box<Fooer>.

Итак, мои вопросы таковы:

  • делает здесь происходит какое-то преобразование?
  • Если да, то что за механизм стоит за этим и как он работает? (Меня также интересуют детали низкого уровня, то есть то, как материал представлен под капотом) Есть ли способ создать Box<Fooer> непосредственно из i32? Если нет, то почему?
2 4

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 без A 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 возвращает a Box<i32>, который затем принуждается к Box<Fooer>. Вы можете считать это преобразованием, но Box не изменяется; все, что делает компилятор, - это вставляет дополнительный указатель на него и забывает его исходный тип. ответ Родриго более подробно описывает механика этого принуждения на уровне языка.

Надеюсь, все это объясняет, почему ответ на

Есть ли способ создать Box<Fooer> непосредственно из i32?

- это "нет": i32 должен быть упакован , прежде чем можно будет стереть его тип. Это та же самая причина, по которой вы не можете писать let x: Fooer = 10i32.

Связанные

Я попытаюсь объяснить, какие преобразования (coertions) происходят в вашем коде.

Существует маркерная черта, названная Unsize это, между другими:

Unsize реализован для:

  • T - это Unsize<Trait>, Когда T: Trait.
  • [...]

Эта черта, насколько мне известно, не используется непосредственно для coertions. Вместо этого 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>.

По поводу вашего последнего вопроса:

Есть ли способ создать коробку непосредственно из i32? Если нет, то почему - нет?

Насколько мне известно, нет. Проблема заключается в том, что Box::new(T) требует размерного значения, так как передаваемое значение перемещается в поле, а некрупные значения не могут быть перемещены. На мой взгляд, самый простой способ сделать это-просто написать:
let c = Box::new(42) as Box<Fooer>;

То есть вы создаете Box нужного типа и затем принуждаете к неназначенному (обратите внимание, что он выглядит очень похожим на ваш пример d).