2016-06-01 2 views
5

Ссылки на типы оболочек, как &Rc<T> и &Box<T> инвариантны T (&Rc<T> не &Rc<U> даже если T является U). Конкретный пример выпуска (Rust Playground):Как справиться с инвариантностью типа оболочки в Rust?

use std::rc::Rc; 
use std::rc::Weak; 

trait MyTrait {} 

struct MyStruct { 
} 

impl MyTrait for MyStruct {} 

fn foo(rc_trait: Weak<MyTrait>) {} 

fn main() { 
    let a = Rc::new(MyStruct {}); 
    foo(Rc::downgrade(&a)); 
} 

Этот код приводит к следующей ошибке:

<anon>:15:23: 15:25 error: mismatched types: 
expected `&alloc::rc::Rc<MyTrait>`, 
    found `&alloc::rc::Rc<MyStruct>` 

Подобный пример (с аналогичной ошибкой) с Box<T> (Rust Playground):

trait MyTrait {} 

struct MyStruct { 
} 

impl MyTrait for MyStruct {} 

fn foo(rc_trait: &Box<MyTrait>) {} 

fn main() { 
    let a = Box::new(MyStruct {}); 
    foo(&a); 
} 

В этих случаях я мог бы, конечно, просто комментировать a с желаемым типом, но во многих случаях это не будет поскольку исходный тип также необходим. Так что же мне тогда делать?

+0

Обратите внимание, что это * не * проблема дисперсии. В Rust реализация реализации не устанавливает отношения «is-a», это не похоже на наследование на других языках. Rust не имеет подтипирования, за исключением времени жизни ссылок (например, '& 'a T' является подтипом' &' static T' для любого '' a'). Вы можете найти более [здесь] (https://doc.rust-lang.org/nightly/nomicon/subtyping.html). –

+0

@VladimirMatveev Ум, документ, к которому вы ссылаетесь (который я читал, пытаясь понять это), конкретно указывает, что '& T' является вариантом по сравнению с' T' и 'Box ' также является вариантом над 'T'. Тем не менее, как показывает мой вопрос, '& Box ' является * не * вариантом над 'T'. Если вы можете объяснить, почему это так, пожалуйста, разделите его как ответ (если это понимание также дает некоторый альтернативный способ борьбы с инвариантностью, тем лучше). –

+0

@VladimirMatveev Вы можете быть правы, хотя «есть» следует заменить чем-то вроде «удовлетворяет типу ограничения» и «вариант», следует заменить чем-то другим. Но я не уверен, что. –

ответ

4

То, что вы видите здесь, вообще не связано с дисперсией и подтипированием.

Во-первых, наиболее информативное чтение о подтипировании в 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>. Таким образом, автоматические принуждения для вложенных ссылок и интеллектуальных указателей отсутствуют, и опять же, это не связано с подтипированием и дисперсией вообще.

+0

Хорошо, я понимаю, что я, вероятно, уделял больше внимания отсутствию подтипов для черт, чем это было необходимо, но все же я попытался объяснить, почему «плоские» ссылки и умные указатели можно принуждать к объектам и почему " вложенные "указатели не могут. –

+0

Спасибо. Отлично. Хотя мне удалось выяснить код, который работал сам по себе, теперь я понимаю намного лучше, почему конкретные варианты работают, а не. :) –

3

В случае Rc::downgrade это на самом деле просто отказ от вывода типа в данном конкретном случае, и будет работать, если это делается в виде отдельной аренды:

fn foo(rc_trait: Weak<MyTrait>) {} 

fn main() { 
    let a = Rc::new(MyStruct {}); 
    let b = Rc::downgrade(&a); 
    foo(b); 
} 

Playground

Для Box<T> очень вероятно, что на самом деле вам не нужна ссылка на поле в качестве аргумента, а ссылка на содержимое. В этом случае нет инвариантность иметь дело с:

fn foo(rc_trait: &MyTrait) {} 

fn main() { 
    let a = Box::new(MyStruct {}); 
    foo(a.as_ref()); 
} 

Playground

Аналогично, для случая с Rc<T>, если вы пишете функцию, которая принимает Rc<T> вы, вероятно, хотите клонировать (т.е. ссылка подсчитывали ссылка), а не обычная ссылка:

fn foo(rc_trait: Rc<MyTrait>) {} 

fn main() { 
    let a = Rc::new(MyStruct {}); 
    foo(a.clone()); 
} 

Playground

Смежные вопросы