Первое, что нужно понять, это то, что &|| ()
имеет время жизни 'static
:
fn main() {
let closure: &'static dyn Fn() = &|| (); // compiles
}
Еще одна вещь, о которой стоит упомянуть, заключается в том, что время жизни закрытия не может превышать время жизни любой из переменных, которые оно захватывает из своей среды, поэтому, если мы попытаемся передать нестатическую переменную нашему статическому закрытию, она не сможет скомпилировать:
fn main() {
let x = 0; // non-static temporary variable
let closure: &'static dyn Fn() = &|| {
println!("{}", x); // x reference captured by closure
}; // error: trying to capture non-static variable in static closure
}
Мы вернемся к этому. В любом случае, если у меня есть структура, которая является универсальной по ссылкам, и я передаю ей ссылку 'static
, у меня будет 'static
экземпляр этой структуры:
struct Dog<'a> {
format: &'a dyn Fn(),
}
fn main() {
let dog: Dog<'static> = Dog { format: &|| () }; // compiles
}
Во-вторых, необходимо понять, что как только вы создадите экземпляр типа, вы не сможете его изменить. Сюда входят любые его общие параметры, включая время жизни. Если у вас есть Dog<'static>
, он всегда будет Dog<'static>
, вы не можете преобразовать его в Dog<'1>
для некоторого анонимного времени жизни '1
, которое короче, чем 'static
.
Это имеет серьезные последствия, одно из которых состоит в том, что ваш set_formatter
метод, вероятно, ведет себя не так, как вы думаете. Если у вас есть Dog<'static>
, вы можете только передать 'static
форматеры в set_formatter
. Метод выглядит так:
impl<'a> Dog<'a> {
fn set_formatter(&mut self, _fmt: &'a dyn Fn()) {}
}
Но поскольку мы знаем, что работаем с Dog<'static>
, мы можем заменить общий параметр времени жизни 'a
на 'static
, чтобы увидеть, с чем мы действительно работаем:
// what the impl would be for Dog<'static>
impl Dog {
fn set_formatter(&mut self, _fmt: &'static dyn Fn()) {}
}
Итак, теперь, когда мы разобрались со всем этим контекстом, давайте перейдем к вашим актуальным вопросам.
Почему проблема исчезает, если я закомментировал println!("{}", x);
внутри закрытия? Я все еще передаю временное сообщение, на которое я ожидаю, что компилятор будет жаловаться, но это не так.
Почему это не удается, с комментариями:
struct Dog<'a> {
format: &'a dyn Fn(),
}
impl<'a> Dog<'a> {
fn set_formatter(&mut self, _fmt: &'a dyn Fn()) {}
}
fn main() {
let mut dog: Dog<'static> = Dog { format: &|| () };
// x is a temporary value on the stack with a non-'static lifetime
let x = 0;
// this closure captures x by reference which ALSO gives it a non-'static lifetime
// and you CANNOT pass a non-'static closure to a Dog<'static>
dog.set_formatter(&|| {
println!("{}", x);
});
}
Причина, по которой эта ошибка исправляется путем комментирования строки println!("{}", x);
, заключается в том, что она снова дает замыканию время жизни 'static
, поскольку оно больше не заимствует переменную, отличную от 'static
, x
.
Почему проблема исчезает, если я закомментировал dog.bark();
в конце? Опять же, я все еще прохожу временное закрытие, которое освобождено, но теперь компилятор доволен. Почему?
Этот странный крайний случай, кажется, возникает только тогда, когда мы явно не аннотируем переменную dog
с помощью Dog<'static>
. Когда переменная не имеет явной аннотации типа, компилятор пытается вывести ее тип, но делает это лениво и пытается быть как можно более гибким, давая программисту преимущество сомнения, чтобы сделать код компилировать. Он действительно должен выдать ошибку компиляции даже без dog.bark()
, но этого не происходит по каким-то загадочным причинам. Дело в том, что это не строка dog.bark()
, из-за которой код не компилируется, он должен не компилироваться в строке set_formatter
независимо от того, но по какой-либо причине компилятор не потрудится выдать ошибку, пока вы не попытаетесь снова использовать dog
после этой оскорбительной строки. Даже простое падение dog
вызовет ошибку:
struct Dog<'a> {
format: &'a dyn Fn(),
}
impl<'a> Dog<'a> {
fn set_formatter(&mut self, _fmt: &'a dyn Fn()) {}
}
fn main() {
let mut dog = Dog { format: &|| () };
let x = 0;
dog.set_formatter(&|| {
println!("{}", x);
});
drop(dog); // triggers "temp freed" error above
}
И раз уж мы зашли так далеко, давайте ответим на ваш неофициальный третий вопрос, перефразированный мной:
Почему избавление от 'a
в методе set_formatter
удовлетворяет компилятор?
Потому что это фактически меняет то, что для Dog<'static>
:
// what the impl would be for Dog<'static>
impl Dog {
fn set_formatter(&mut self, _fmt: &'static dyn Fn()) {}
}
В это:
// what the impl would now be for Dog<'static>
impl Dog {
fn set_formatter(&mut self, _fmt: &dyn Fn()) {}
}
Итак, теперь вы можете передавать закрытие, отличное от 'static
, в Dog<'static>
, хотя это бессмысленно, поскольку метод на самом деле ничего не делает, и компилятор снова будет жаловаться, когда вы действительно попытаетесь установить закрытие в структуре Dog<'static>
.
person
pretzelhammer
schedule
31.01.2021