Note that the only reasonable meaning of
// "I can make a reference to a `Struct` with any lifetime from nothing"
fn make<'a>() -> &'a Struct
is synonymous with
fn make() -> &'static Struct
as actually pulling a lifetime out of nowhere is generally unsound.
In fact, if you wanted to represent this as a function pointer or trait object, you will need the latter version:
// Fine
let fn_ptr: fn() -> &'static Struct = make;
// error[E0581]: return type references lifetime `'a`,
// which is not constrained by the fn input types
let fn_ptr: for<'a> fn() -> &'a Struct = make;
In short, if you ever find yourself wishing for a Box<for<'any> Fn() -> &'any Struct>
(that pulls some generic lifetime "out of nowhere"), use Box<Fn() -> &'static Struct>
instead.
The rest of this reply goes into the weeds, feel free to ignore it.
So, why is the function item allowed to have the first version, that returns 'a
"out of nowhere"? After all, it looks like function pointers and trait objects aren't. Is it really exactly the same as the version that returns &'static
, and this is some special sugar?
There actually is another difference, in that the former is something this:
// Function item `make` has a lifetime parameter
struct __make_fn_item<'a> { _pd: PhantomData<&'a ()> }
// Generic implementation
impl<'a> Fn() -> &'a Struct for __make_fn_item<'a> { /* ... */ }
(Which is also why you can turbofish make
in the playground -- it has a lifetime parameter.)
While the latter is something like this:
// Non-generic function item and implementation
struct __make_fn_item {}
impl Fn() -> &'static Struct for __make_fn_item { /* ... */ }
(If you try the 'static
version, you'll find you cannot turbofish it.)
In particular, neither of them are like
// Non-generic function item
struct __make_fn_item {}
// Generic implementation
impl<'a> Fn() -> &'a Struct for __make_fn_item { /* ... */ }
That would allow you to coerce to a higher-ranked for<'any> fn() -> &'any Struct
. This is also arguably the version that most intuitively corresponds to the signature ("I can make a reference to a Struct
with any lifetime from nothing").
As it turns out, this used to be the interpretation used by the compiler, but no longer is because it is unsound. (Note that you can't turbofish the older interpretation either.) If you look at the implementation of Fn
under this old (unsound) interpretation, 'a
is uncontrained in the implementation. But the 'static
returning version has no generic lifetimes, and in the current interpretation, the 'a
is constrained as a parameter of __make_fn_item<'_>
(and not really "out of nowhere").
The only way for function pointers to constrain that lifetime parameter is if it is also part of an input parameter to the function itself, hence the difference.
It would be a breaking change to not allow "lifetimes out of nowhere" in function declarations, and there are other cases where the lifetime needs to be a parameter of the function item anyway, so the non-&'static
version is unlikely to go away.