Ответ 1
Компилятор умнее, чем вы за него приписываете. Это предотвратило возникновение проблем с безопасностью памяти:
fn foo<'a, F>(func: &F) where F: Fn() -> Bar<'a>, { let number = 42; let bar = (func)(); bar.number.set(Some(&number)); }
В этом коде говорится, что вызывающий объект foo
может указать время жизни для 'a
, но затем тело метода сохраняет ссылку в значение. Эта сохраненная ссылка не гарантирует, что она так долго будет жить. В качестве очевидного примера, вызывающий может потребовать, чтобы 'a
== 'static
, но выполнение этой функции было бы невозможным:
fn b() -> Bar<'static> {
Bar {
number: Cell::new(None),
}
}
fn main() {
foo(&b);
}
Обратите внимание, что это не имеет ничего общего с закрытием или функциями:
use std::cell::Cell;
fn main() {
let number = Cell::new(None);
let x = 1;
number.set(Some(&x));
let y = 2;
number.set(Some(&y));
}
error[E0597]: 'x' does not live long enough
--> src/main.rs:6:22
|
6 | number.set(Some(&x));
| ^ borrowed value does not live long enough
...
9 | }
| - 'x' dropped here while still borrowed
|
= note: values in a scope are dropped in the opposite order they are created
Почему первый пример работал, а не второй?
Поскольку компилятор знает, что Cell
(действительно UnsafeCell
) должен учитывать возможность того, что вы будете хранить значение в созданном типе.
UnsafeCell<T>
,Cell<T>
,RefCell<T>
,Mutex<T>
и все другие типы внутренней изменчивости инвариантны относительноT
(как есть*mut T
по метафоре)
Разница - плотная тема, которую я не могу объяснить лаконично.
@trentcl предоставляет этот пример, который показывает, что ваш исходный код может не делать то, что вы думаете.
Без Cell
компилятор знает, что безопасно автоматически настроить срок службы возвращаемого типа на тот, который немного короче. Если заставить тип быть больше 'a
, тем не менее, мы получаем ту же ошибку:
fn foo<'a, F>(func: F)
where
F: Fn() -> Bar<'a>,
{
let number = 42;
let mut bar: Bar<'a> = func();
// ^^^^^^^
bar.number = Some(&number);
}
error[E0597]: 'number' does not live long enough
--> src/main.rs:17:24
|
17 | bar.number = Some(&number);
| ^^^^^^ borrowed value does not live long enough
18 | }
| - borrowed value only lives until here
|
note: borrowed value must be valid for the lifetime 'a as defined on the function body at 11:1...
--> src/main.rs:11:1
|
11 | / fn foo<'a, F>(func: F)
12 | | where
13 | | F: Fn() -> Bar<'a>,
14 | | {
... |
17 | | bar.number = Some(&number);
18 | | }
| |_^
Это невозможно без [...]
Да, но я точно не знаю, что это будет. Я считаю, что для RFC 1598 потребуются общие связанные типы (GAT).
Моя первая мысль заключалась в том, чтобы попытаться оценить границы верхнего уровня (HRTB):
fn foo<F>(func: F)
where
F: for<'a> Fn() -> Bar<'a>,
{
let number = 42;
let bar = func();
bar.number.set(Some(&number));
}
Это вызывает E0582:
error[E0582]: binding for associated type 'Output' references lifetime ''a', which does not appear in the trait input types
--> src/main.rs:17:25
|
17 | F: for <'a> Fn() -> Bar<'a>,
| ^^^^^^^
Честно говоря, я не вижу значения в коде на основе приведенного примера. Если вы возвращаете Bar
по значению, вы можете сделать его изменчивым, удалив любую потребность в внутренней изменчивости.
Вы также можете изменить закрытие, чтобы принять значение по мере необходимости:
fn foo<F>(func: F)
where
F: for<'a> Fn(&'a i32) -> Bar<'a>,
{
let number = 42;
let bar = func(&number);
}
Смотрите также: