Ответ 1
RefCell<T>
содержит UnsafeCell<T>
, который является специальным lang item. Это ошибка UnsafeCell
вызывает ошибку. Вы можете проверить:
fn error<'a>(foo: &UnsafeCell<Option<&'a mut String>>, s: &'a mut String) {}
...
let bar = UnsafeCell::new(None);
error(&bar, &mut s);
Но ошибка связана не с тем, что компилятор признает, что UnsafeCell вводит внутреннюю изменчивость, но UnsafeCell
- это инвариант в T. В факт, мы могли бы воспроизвести ошибку, используя PhantomData:
struct Contravariant<T>(PhantomData<fn(T)>);
fn error<'a>(foo: Contravariant<&'a i32>, s: &'a mut String) {}
...
let bar = Contravariant(PhantomData);
error(bar, &mut s);
или даже что-либо, что контравариантно или инвариантно в течение жизни 'a
:
fn error<'a>(foo: Option<fn(&'a i32)>, s: &'a mut String) {}
let bar = None;
error(bar, &mut s);
Причина, по которой вы не можете скрыть RefCell, заключается в том, что дисперсия выводится через поля структуры. Как только вы использовали RefCell<T>
где-нибудь, независимо от того, насколько глубока, компилятор будет вычислять T
, является инвариантным.
Теперь посмотрим, как компилятор определит ошибку E0502. Во-первых, важно помнить, что компилятор должен выбрать два конкретных срока службы здесь: время жизни в типе выражения &mut s
('a
) и время жизни в типе bar
(позвоните ему 'x
). Оба ограничены: предыдущее время жизни 'a
должно быть короче, чем область s
, в противном случае мы получим ссылку, живущую дольше, чем исходная строка. 'x
должен быть больше, чем область bar
, иначе мы могли бы получить доступ к оборванному указателю через bar
(если у типа есть параметр lifetime, компилятор предполагает, что тип может получить доступ к значению с этим временем жизни).
С этими двумя основными ограничениями компилятор выполняет следующие шаги:
- Тип
bar
равенContravariant<&'x i32>
. - Функция
error
принимает любой подтипContravariant<&'a i32>
, где'a
- время жизни этого выражения&mut s
. - Таким образом,
bar
должен быть подтипомContravariant<&'a i32>
-
Contravariant<T>
контравариантно надT
, т.е. еслиU <: T
, тоContravariant<T> <: Contravariant<U>
. - Таким образом, отношение подтипирования может быть выполнено, если
&'x i32
является супертипом&'a i32
. - Таким образом,
'x
должен быть короче'a
, т.е.'a
должен пережить'x
.
Аналогично, для инвариантного типа производное отношение 'a == 'x
, а для convariant 'x
переживает 'a
.
Теперь проблема заключается в том, что время жизни в типе bar
живет до конца области (в соответствии с указанным выше ограничением):
let bar = Contravariant(PhantomData); // <--- 'x starts here -----+
error(bar, // |
&mut s); // <- 'a starts here ---+ |
s.len(); // | |
// <--- 'x ends here¹ --+---+
// |
// <--- 'a ends here² --+
}
// ¹ when `bar` goes out of scope
// ² 'a has to outlive 'x
В обоих контравариантных и инвариантных случаях 'a
переживает (или равно) 'x
означает, что оператор s.len()
должен быть включен в диапазон, вызывая ошибку заемщика.
Только в ковариантном случае мы могли бы сделать диапазон 'a
короче, чем 'x
, позволяя исключить временный объект &mut s
до того, как будет вызван s.len()
(что означает: at s.len()
, s
is не считаются заимствованными больше):
let bar = Covariant(PhantomData); // <--- 'x starts here -----+
// |
error(bar, // |
&mut s); // <- 'a starts here --+ |
// | |
// <- 'a ends here ----+ |
s.len(); // |
} // <--- 'x ends here -------+