Building an intuition
Consider this somewhat contrived function that takes a static string and makes its lifetime shorter:
#![allow(dead_code)] fn main() {} fn lifetime_shortener<'a>(s: &'static str) -> &'a str { s }
Intuitively, this feels like it should compile: if a string lasts for the whole process it should also last for any part of it. And it does!
Now let's make it slightly more complicated. Let's introduce a Cell
into the
picture. As a reminder, a Cell
allows for the data inside it to be changed.
#![allow(dead_code)] fn main() {} use std::cell::Cell; fn cell_shortener<'a, 'b>(s: &'a Cell<&'static str>) -> &'a Cell<&'b str> { s }
cell_shortener
doesn't compile :( Can you tell why? Think about it for a minute,
try using your intuition...
#![allow(dead_code)] fn main() {} use std::cell::Cell; fn cell_example() { // Consider this Cell. It holds a static string. let foo: Cell<&'static str> = Cell::new("foo"); // Do you think this can work? let owned_string: String = "non_static".to_owned(); foo.replace(&owned_string); // Doesn't seem like it can, right? foo promises that what's inside it is // a &'static str, but we tried to put in an owned string scoped to this // function. }
#![allow(dead_code)] fn main() {} use std::cell::Cell; fn cell_shortener<'a, 'b>(s: &'a Cell<&'static str>) -> &'a Cell<&'b str> { s } fn cell_counterexample() { let foo: Cell<&'static str> = Cell::new("foo"); let owned_string: String = "non_static".to_owned(); // If we pretend that cell_shortener works let shorter_foo = cell_shortener(&foo); // then `shorter_foo` and `foo` would be aliases of each other, which would // mean that you could use `shorter_foo` to replace `foo`s `Cell` with a // non-static string: shorter_foo.replace(&owned_string); // Now `foo`, which is an alias of `shorter_foo`, has a non-static string // in it! Whoops. }
It isn't just Cell
which is problematic in this way. RefCell
, OnceCell
,
Mutex
, &mut
references -- anything "inside" some sort of mutable context has
this issue.
Now, what about a hypothetical "lengthener" function?
#![allow(dead_code)] fn main() {} fn lifetime_lengthener<'a>(s: &'a str) -> &'static str { s }
This is obviously bogus, right? You can't just turn an arbitrary borrowed string and make it last the duration of the entire process. Similarly:
#![allow(dead_code)] fn main() {} use std::cell::Cell; fn cell_lengthener<'a, 'b>(s: &'a Cell<&'b str>) -> &'a Cell<&'static str> { s }
But what about this? fn is a pointer to a function that takes an arbitrary borrowed string.
#![allow(dead_code)] fn main() {} fn fn_ptr_lengthener<'a>(f: fn(&'a str) -> ()) -> fn(&'static str) -> () { f }
Ahhh, intuitively, this should work. And it does. You can take a callback that takes an arbitrary borrowed string and turn it into one that takes in a static string.