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.