Почему `let ref a: Trait = Struct` запрещен?
У нас есть некопируемый тип и черта:
struct Struct;
trait Trait {}
impl Trait for Struct {}
Если мы создадим &Struct
и разыменовывая его, мы получаем ссылку на rvalue, которую мы можем использовать для инициализации привязки by-ref:
let a: &Struct = &Struct;
let ref a: Struct = *a;
Мы также можем напрямую инициализировать это с помощью refinding:
let ref a: Struct = Struct;
Но если мы объявим нашу переменную привязку, чтобы потребовать ссылку, только первый фрагмент кода работает
let a: &Trait = &Struct;
let ref a: Trait = *a;
Попытка сделать это напрямую
let ref a: Trait = Struct;
Или, пройдя через петли
let a: &Struct = &Struct;
let ref a: Trait = *a;
Или же
let ref a: Trait = *&Struct;
Даст нам mismatched types
ошибка. Очевидно, они не одного типа, но вывод работает для ссылок.
Это просто не реализовано (пока?) Или есть более глубокая причина, по которой это запрещено?
1 ответ
Здесь происходит немного неуловимой тонкости. Ключевая разница между
let a: &Struct = &Struct;
let ref a: Struct = *a;
а также
let a: &Trait = &Struct;
let ref a: Trait = *a;
Это выражение *a
создает значение, размер которого неизвестен во время компиляции. Это проявляется как ошибка, когда мы пытаемся сделать:
let ref a: Trait = Struct as Trait;
<anon>:6:24: 6:39 error: cast to unsized type: `Struct` as `Trait`
<anon>:6 let ref a: Trait = Struct as Trait;
^~~~~~~~~~~~~~~
<anon>:6:24: 6:30 help: consider using a box or reference as appropriate
<anon>:6 let ref a: Trait = Struct as Trait;
В общем, компилятор не может знать размер чистой черты, используемой в качестве типа, например Trait
используется здесь. Это потому, что любой тип может реализовать Trait
- поэтому размер признака может быть любым, в зависимости от типа, который его реализует. Итак, это объясняет, почему let ref a: Trait = Struct
а также let a: &Struct = &Struct; let ref a: Trait = *a
не работает, потому что кастинг Struct
к Trait
это нестандартный актерский состав.
Что касается того, почему работает ваш фрагмент кода рабочей черты, глядя на MIR для этих двух примеров, мы можем видеть, что компилятор обрабатывает два вышеупомянутых назначения немного по-разному:
let a: &Struct = &Struct;
let ref a: Struct = *a;
bb0: {
tmp1 = Struct;
tmp0 = &tmp1;
var0 = &(*tmp0);
var1 = &(*var0);
return = ();
goto -> bb1;
}
let a: &Trait = &Struct;
let ref a: Trait = *a;
bb0: {
tmp2 = Struct;
tmp1 = &tmp2;
tmp0 = &(*tmp1);
var0 = tmp0 as &'static Trait + 'static (Unsize);
var1 = &(*var0);
return = ();
goto -> bb1;
}
Мы видим, что компилятор должен выполнить приведение к объекту черты &'static Trait + 'static
удовлетворить неявное принуждение &Struct
в &Trait
, Оттуда, шаблон ссылки просто var1 = &(*var0);
, что в данном случае является простым присваиванием от признака объекта var0
к признаку объекта var1
,
Это похоже на MIR, сгенерированный этой функцией:
fn stuff() {
let sized = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
let slice : &[u8] = &sized;
let ref other_slice = *slice;
}
bb0: {
var0 = [const 1u8, ..., const 0u8];
tmp2 = &var0;
tmp1 = &(*tmp2);
var1 = tmp1 as &'static [u8] (Unsize);
var2 = &(*var1);
return = ();
goto -> bb1;
}
Так как тип [u8]
не имеет размера, он выполняет аналогичное приведение к фрагменту, который очень похож на макет объекта черты. В конечном счете, компилятор допускает код, который не вводит никаких нестандартных локальных объектов.