Как работает механизм создания штучных признаков? - PullRequest
0 голосов
/ 12 сентября 2018

У меня проблемы с пониманием того, как возникают ценности в штучной упаковке.Рассмотрим следующий код:

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

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

  • здесь происходит преобразование?
  • Если да, то какой механизм стоит за ним и как он работает?(Мне также интересны детали низкого уровня, то есть, как вещи представлены под капотом)
  • Есть ли способ создать Box<Fooer> непосредственно из i32?Если нет: почему бы и нет?

Ответы [ 2 ]

0 голосов
/ 12 сентября 2018

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

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

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

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

Связанные

0 голосов
/ 12 сентября 2018

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

Существует черта маркера с именем 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<Fooer> непосредственно из i32? Если нет: почему бы и нет?

Не то, что я знаю. Проблема заключается в том, что Box::new(T) требуется значение размера, поскольку переданное значение перемещается в поле, а значения без размера не могут быть перемещены.

На мой взгляд, самый простой способ сделать это - просто написать:

let c = Box::new(42) as Box<Fooer>;

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

...