Static output lifetime vs generic output lifetime

Are these just 2 ways of essentially writing the same function, or is there a subtle difference?

fn get_static_str() -> &'static str {
    "str"
}

fn get_a_str<'a>() -> &'a str {
    "str"
}

What I think the difference might be: the second function allows the caller to pick a lifetime shorter than 'static... however I'm not sure if that makes a meaningful difference since doesn't a 'static lifetime automatically coerce into shorter ones when necessary? So can't the first function be used for the same purpose?

1 Like

There is no meaningful difference between the two here. However, there are cases where a dangling lifetime is useful, for example,

impl<T: ?Sized> Box<T> {
    pub fn leak<'a>(self) -> &'a mut T where T: 'a { .. }
}

is far better than

impl<T: ?Sized> Box<T> {
    pub fn leak(self) -> &'static mut T where T: 'static { .. }
}

Because T may not be 'static. (But this only matters for generic code)

8 Likes

So if I understand correctly, a 'static is always better than an 'a in practice (since 'static is a subtype of any 'a) but when I'm working with some T I can design a more flexible function interface if I bind T by 'a instead of 'static?

This is too many levels of abstraction deep for me to fully process. Could I see a concrete example of where a "dangling" lifetime would be preferably used over a 'static lifetime?

Or wait, lemme try myself first:

fn main() {
    let a = 10;
    let boxed_a_ref = Box::new(&a);
    
    // this would not compile if
    // `fn leak(self) -> &'static mut T where T: 'static`
    // right?
    // it only works because
    // `fn leak(self) -> &'a mut T where T: 'a`
    // ?
    let leaked_a_ref = Box::leak(boxed_a_ref);
}
1 Like

Sort of, a dangling lifetime is usually better, but sometimes it's too verbose for no gain. It's always at least as flexible as 'static, because 'static will be inferred if there are no other constraints.

1 Like

Okay cool, was able to come up with a working example that illustrates the differences:

// 'static
fn leak_static<T>(b: Box<T>) -> &'static mut T {
    Box::leak(b)
    // &'static mut T imples T: 'static
}

// dangling
fn leak_generic<'a, T>(b: Box<T>) -> &'a mut T {
    Box::leak(b)
    // &'a mut T imples T: 'a
}

fn main() {
    let num = 10;
    let boxed_num_ref_1 = Box::new(&num);
    let boxed_num_ref_2 = Box::new(&num);
    
    // compiles, num_ref: 'a
    let leaked_num_ref = leak_generic(boxed_num_ref_1);
    
    // does not compile, num_ref: !'static
    let leaked_num_ref = leak_static(boxed_num_ref_2);
}

playground

8 Likes

Sorry to bump this kinda old-ish thread but I finally found an answer for this question. A reader originally raised this issue on my blog which unfortunately shows that there is indeed a difference between how for<'a, T> fn() -> &'a T and for<T> fn() -> &'static T can be used in practice. I've updated my article with a new section to describe the difference. I'll import the answer here anyway.

This compiles:

use rand;

fn generic_str_fn<'a>() -> &'a str {
    "str"
}

fn static_str_fn() -> &'static str {
    "str"
}

fn a_or_b<T>(a: T, b: T) -> T {
    if rand::random() {
        a
    } else {
        b
    }
}

fn main() {
    let some_string = "string".to_owned();
    let some_str = &some_string[..];
    let str_ref = a_or_b(some_str, generic_str_fn()); // compiles
    let str_ref = a_or_b(some_str, static_str_fn()); // compiles
}

This does not:

use rand;

fn generic_str_fn<'a>() -> &'a str {
    "str"
}

fn static_str_fn() -> &'static str {
    "str"
}

fn a_or_b_fn<T, F>(a: T, b_fn: F) -> T
    where F: Fn() -> T
{
    if rand::random() {
        a
    } else {
        b_fn()
    }
}

fn main() {
    let some_string = "string".to_owned();
    let some_str = &some_string[..];
    let str_ref = a_or_b_fn(some_str, generic_str_fn); // compiles
    let str_ref = a_or_b_fn(some_str, static_str_fn); // compile error
}

A more concise example that also does not compile:

fn static_bar() -> &'static str { "bar" }
fn generic_bar<'a>() -> &'a str { "bar" }

fn main() {
    let s = String::from("foo");
    Some(&s[..]).unwrap_or_else(generic_bar); // compiles
    Some(&s[..]).unwrap_or_else(static_bar); // compile error
}

Rust can coerce &'static T into &'a T since they are values. Rust cannot coerce for<T> fn() -> &'static T into for<'a, T> fn() -> &'a T since they are types. They key takeaway here I believe is to prefer using the latter since it's more flexible and can be used in more scenarios.

6 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.