Function where output lifetime has no relation to inputs?

I'm reading the lifetime misconceptions article, and it has this example:

// no relationship between input & output lifetimes
fn overlap<'a>(s: &str, t: &str) -> &'a str; 
// expands to:
fn overlap<'a, 'b, 'c>(s: &'a str, t: &'b str) -> &'c str;

I wonder how I would write or use such a function. If the output lifetime is not 'static and not determined by the inputs, where would it be coming from? Can someone give an example situation that uses such a function?

I think in this very case, there is no way to safely implement such a function without 'c depending on the other lifetimes or evaluating to 'static.

1 Like

the only safe implementation to return a reference of unrestricted lifetime is a 'static reference:

fn foo<'a>() -> &'a str {
    "hello, world!"
}

but practically, this is no different from just returning &'static str:

// WARNING: this is infinite recursion, don't do this in real code

fn foo<'a>() -> &'a str {
    bar()
}

fn bar() -> &'static str {
    foo()
}

It could be inferred, if, for example, function returns a &'a T, which is then put into Vec<&'some_specific_lifetime T>; or it could be specified explicitly, by calling overlap::<'some_specific_lifetime>(s, t). In practice, this is useful for functions which make reference from raw pointer, like slice::from_raw_parts - in this case, there's no input lifetime to tie the reference to, but 'static is usually the wrong one to pick.

2 Likes

I think this is again an area where one should prefer making foo polymorphic since subtyping can almost always be deferred to the call site. Personally, I find that thinking about lifetimes as type parameters can be quite useful even though they have special properties that don't apply to "normal" types (e.g., subtyping). When thought in this way, it's "obvious" that foo being polymorphic is "better" since it allows calling code to dictate what lifetime should be returned. Additionally, it doesn't expose undue burden to calling code since the lifetime argument that is passed is almost always going to be inferred.

For example the below code compiles:

fn main() {
    _ = Some(String::new().as_str()).unwrap_or_else(foo);
}
fn foo<'a>() -> &'a str {
    ""
}

but this does not:

fn main() {
    _ = Some(String::new().as_str()).unwrap_or_else(foo);
}
fn foo() -> &'static str {
    ""
}
1 Like

As you certainly will see -> &'static str functions which you do not control in practice, it is also useful to know how to remedy the situation.

 fn main() {
-    _ = Some(String::new().as_str()).unwrap_or_else(foo);
+    _ = Some(String::new().as_str()).unwrap_or_else(|| foo());
 }
4 Likes

Certainly, but one can almost always have some workarounds whether that is via newtypes or whatnot. Granted there is a lot less boilerplate with the foo example, but it is still extra work. I was remarking more as a library author though.

There is additional cognitive burden with foo being polymorphic, but I don't think it's enough to avoid since most calling code could just ignore it. I am just partial to making life a little bit easier for calling code. Of course if you don't have control, then a workaround is required.

1 Like

Sure, I was just pointing it out for the sake of thread readers. Not as a "there exists a workaround so don't bother as a library author" argument, but as a "you will run into the 'static version as a library consumer so here's a workaround" tip.

(Perhaps came across wrong since I replied directly.)

1 Like

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.