Чтобы понять, почему работает неизменяемая версия, а изменяемая версия не работает (как написано), нам нужно обсудить подтип и дисперсию .
В Rust в основном нет подтипов. Значения обычно имеют уникальный тип. Одно место, где у Rust есть есть подтипы, это время жизни. Если 'a: 'b
(чтение 'a
длиннее, чем 'b
), то, например, &'a T
является подтипом &'b T
, интуитивно, потому что более длительные времена жизни могут рассматриваться как если бы они были короче.
Дисперсия - это способ распространения подтипов. Если A
является подтипом B
, и у нас есть обобщенный c тип Foo<T>
, Foo<A>
может быть подтипом Foo<B>
, наоборот, или ни того, ни другого. В первом случае, когда направление подтипа остается неизменным, Foo<T>
называется ковариантным относительно T
. Во втором случае, когда направление меняется на противоположное, оно называется контрвариантным, а в третьем случае оно называется инвариантным.
Для этого случая соответствующими типами являются &'a T
и &'a mut T
. Оба являются ковариантными в 'a
(поэтому ссылки с более длинными временами жизни можно привести к ссылкам с более короткими временами жизни). &'a T
является ковариантным в T
, но &'a mut T
является инвариантом в T
.
Причина этого объясняется в Nomicon (ссылка выше), поэтому я ' Я просто покажу вам (несколько упрощенный) пример, приведенный там. Код Trentcl является рабочим примером того, что пойдет не так, если &'a mut T
является ковариантным в T
.
fn evil_feeder(pet: &mut Animal) {
let spike: Dog = ...;
// `pet` is an Animal, and Dog is a subtype of Animal,
// so this should be fine, right..?
*pet = spike;
}
fn main() {
let mut mr_snuggles: Cat = ...;
evil_feeder(&mut mr_snuggles); // Replaces mr_snuggles with a Dog
mr_snuggles.meow(); // OH NO, MEOWING DOG!
}
Так почему же работает неизменяемая версия child
, а не изменяемая версия ? В неизменяемой версии LogNode
содержит неизменную ссылку на LogNode
, поэтому по ковариации как времени жизни, так и параметра типа, LogNode
является ковариантным по своему параметру времени жизни. Если 'a: 'b
, то LogNode<'a>
является подтипом LogNode<'b>
.
У нас есть self: &'a LogNode<'n>
, что подразумевает 'n: 'a
(в противном случае этот заем превзойдет данные в LogNode<'n>
). Таким образом, поскольку LogNode
является ковариантным, LogNode<'n>
является подтипом LogNode<'a>
. Кроме того, ковариация в неизменяемых ссылках снова позволяет &'a LogNode<'n>
быть подтипом &'a LogNode<'a>
. Таким образом, self: &'a LogNode<'n>
может быть приведен к &'a LogNode<'a>
по мере необходимости для типа возврата в child
.
Для изменяемой версии LogNode<'n>
не является ковариантным в 'n
. Дисперсия здесь сводится к дисперсии &'n mut LogNode<'n>
. Но так как здесь есть время жизни в "T
" части изменяемой ссылки здесь, инвариантность изменяемых ссылок (в T
) подразумевает, что это также должно быть инвариантным.
Все это объединяется, чтобы показать, что self: &'a mut LogNode<'n>
не может быть приведен к &'a mut LogNode<'a>
. Таким образом, функция не компилируется.
Одним из решений этого является добавление границы времени жизни 'a: 'n
, хотя, как отмечалось выше, у нас уже есть 'n: 'a
, поэтому это заставляет два времени жизни к быть равным Это может или не может работать с остальной частью вашего кода, так что возьмите его с крошкой соли.