I can’t help but imagine what kind of language features would be needed to make this use-case safely possible… so here we go (and sorry if the reply might be a touch off-topic):
So for lifetime, I’m imagining a feature of “existential types” would be useful, and feasible (“relatively” straightforwardly) as lifetimes don’t influence how the type is represented.
So one could imagine some syntax for this like
struct Foo<'a><exists 'b: 'a>(Box<dyn Fn() -> Bar<'b>>, OnceCell<Bar<'b>>);
and then you could hide an invariant lifetime ('b
) in this type with only a covariant lifetime 'a
externally visible. Of course, the main difficulty is how exactly accessing the contents of this is type-checked, but generally, something like
let foo: Foo<'a> = …;
use_value(&foo.0, &foo.1);
would need to be checked so that use_value
can generically work with any type Foo<'b>
with 'b: 'a
.
Such a thing is by the way already possibly to pull off, but inefficiently (via trait objects) and unergonomically.
However, to make this work with more than just lifetimes, one would need to think bigger. Let’s replace 'b: 'a
with two types T :<: U
(where “:<:
” shall mean “subtype”), then one could imagine something like
struct Foo<U><exists T :<: U>(Box<dyn Fn() -> T>, OnceCell<T>);
making Foo<U>
covariant again, despite of the contained Cell
.
Except now… in order to access the contents, we need code that’s generic over types T
that are subtypes of U
. Worse it needs to be “polymorphic” (i.e. generic without monomorphization) because this appears in a context where we can no longer have monomorphization on T
. (By the way, there do exist types that are subtypes of one another but have different TypeId and monomorphization and everything, like Box<dyn for<'a> Fn(&'a ()) -> &'a str>
which is a subtype of Box<dyn Fn(&'static ()) -> &'static str>
.)
So we have a language feature that requires more complicated language features to even make sense… but to me it all seems – at least potentially / in principle – feasible, and it would mean that covariant, lazily initialized types might be possible to offer without unsafe
.
Let me get a bit more closely back on topic… as I mentioned above, subtyping & variance in Rust does not just cover types differing in lifetimes. So if you want full covariance, “NoLifetimes
” as described, in terms of “lifetimes are ignored”, is insufficient: