То, что вы видите здесь, вообще не связано с дисперсией и подтипированием.
Во-первых, наиболее информативное чтение о подтипировании в Rust - this chapter Nomicon. Вы можете найти там, что в отношении подтипирования Rust (т. Е. Когда вы можете передать значение одного типа функции или переменной, которая ожидает, что переменная различна для типа) очень ограничена. Это можно наблюдать только при работе со временем жизни.
Например, следующий фрагмент кода показывает, как именно &Box<T>
представляет собой (со) вариант:
fn test<'a>(x: &'a Box<&'a i32>) {}
fn main() {
static X: i32 = 12;
let xr: &'static i32 = &X;
let xb: Box<&'static i32> = Box::new(xr); // <---- start of box lifetime
let xbr: &Box<&'static i32> = &xb;
test(xbr); // Covariance in action: since 'static is longer than or the
// same as any 'a, &Box<&'static i32> can be passed to
// a function which expects &'a Box<&'a i32>
//
// Note that it is important that both "inner" and "outer"
// references in the function signature are defined with
// the same lifetime parameter, and thus in `test(xbr)` call
// 'a gets instantiated with the lifetime associated with
// the scope I've marked with <----, but nevertheless we are
// able to pass &'static i32 as &'a i32 because the
// aforementioned scope is less than 'static, therefore any
// shared reference type with 'static lifetime is a subtype of
// a reference type with the lifetime of that scope
} // <---- end of box lifetime
Эта программа компилируется, что означает, что оба &
и Box
ковариантны над их соответствующими параметрами типа и продолжительности жизни.
В отличие от большинства «обычных» языков ООП, которые имеют классы/интерфейсы, такие как C++ и Java, в свойствах ржавчины не вводят отношение подтипирования.Даже если, скажем,
trait Show {
fn show(&self) -> String;
}
сильно напоминает
interface Show {
String show();
}
в каком-то языке, как Java, они совершенно разные по семантике. В Rust голой черты, при использовании в качестве типа, является никогда супертипом любого типа, который реализует эту черту:
impl Show for i32 { ... }
// the above does not mean that i32 <: Show
Show
, в то же время черта, на самом деле может использоваться в позиции типа, но он обозначает специальный нестандартный тип, который может использоваться только для формирования trait objects. Вы не можете иметь значения типа голого типа, поэтому даже не имеет смысла говорить о подтипировании и дисперсии с типом голых признаков.
Черта объекты принимают форму &SomeTrait
или &mut SomeTrait
или SmartPointer<SomeTrait>
, и они могут быть розданы и хранятся в переменных, и они необходимы, чтобы абстрагироваться от фактической реализации признака. Однако &T
, где T: SomeTrait
не является подтипом &SomeTrait
, и эти типы вообще не участвуют в дисперсии.
Trait объекты и регулярные указатели имеют несовместимую внутреннюю структуру: &T
просто обычный указатель на конкретный тип T
, в то время как &SomeTrait
является жиром указателя, который содержит указатель на исходное значение такого типа, который реализует SomeTrait
, а также второй указатель на таблицу vtable для реализации SomeTrait
вышеупомянутого типа.
Тот факт, что передача &T
в &SomeTrait
или Rc<T>
как Rc<SomeTrait>
работы происходит потому, что ржавчина делает автоматическое приведения ссылки и смарт-указатели: он способен построить жир указателя &SomeTrait
для обычной ссылки &T
, если он знает T
; это вполне естественно, я считаю. Например, ваш пример с Rc::downgrade()
работает, потому что Rc::downgrade()
возвращает значение типа Weak<MyStruct>
, которое принуждается к Weak<MyTrait>
.
Однако строительство &Box<SomeTrait>
из &Box<T>
если T: SomeTrait
гораздо сложнее: для одного, то компилятор должен выделить временное значение нового потому Box<T>
и Box<SomeTrait>
имеют различные представления памяти. Если у вас есть, скажем, Box<Box<T>>
, получение Box<Box<SomeTrait>>
из этого еще сложнее, потому что ему понадобится , создавая новое выделение на куче для хранения Box<SomeTrait>
. Таким образом, автоматические принуждения для вложенных ссылок и интеллектуальных указателей отсутствуют, и опять же, это не связано с подтипированием и дисперсией вообще.
Обратите внимание, что это * не * проблема дисперсии. В Rust реализация реализации не устанавливает отношения «is-a», это не похоже на наследование на других языках. Rust не имеет подтипирования, за исключением времени жизни ссылок (например, '& 'a T' является подтипом' &' static T' для любого '' a'). Вы можете найти более [здесь] (https://doc.rust-lang.org/nightly/nomicon/subtyping.html). –
@VladimirMatveev Ум, документ, к которому вы ссылаетесь (который я читал, пытаясь понять это), конкретно указывает, что '& T' является вариантом по сравнению с' T' и 'Box' также является вариантом над 'T'. Тем не менее, как показывает мой вопрос, '& Box ' является * не * вариантом над 'T'. Если вы можете объяснить, почему это так, пожалуйста, разделите его как ответ (если это понимание также дает некоторый альтернативный способ борьбы с инвариантностью, тем лучше). –
@VladimirMatveev Вы можете быть правы, хотя «есть» следует заменить чем-то вроде «удовлетворяет типу ограничения» и «вариант», следует заменить чем-то другим. Но я не уверен, что. –