I'm trying to define a trait with a method that consumes an implementation (say: Impl<'this>) of the trait and is supposed to produce a new instance of the same implementation (say: Impl<'next>) with an updated lifetime.
trait TraitWithLifetime<'this> {
fn switch_to_next<'next>(
self,
next: Something<'next>,
// How can I return Self with an updated lifetime?
// - Self<'next> isn't allowed.
// - impl TraitWithLifetime<'next> is not strong enough as
// I need to save the result in a struct, ie. I need a specific
// return type.
// - Self + 'next doesn't work because it will return Impl<'this>
// rather than Impl<'next>.
// - Let Impl return Impl<'next> while the trait defines
// impl TraitWithLifetime<'next> (refined return type) doesn't
// work because the caller would then have to be aware of
// the specific implementation.
) -> ???;
}
I had tried this before and unfortunately it doesn't seem to work. Introducing an associated type with a lifetime parameter requires crazy restrictions on that lifetime parameter that contradict the requirement.
The error produced by the compiler is:
error: missing required bounds on `NextState`
--> mod.rs:14:5
|
14 | type NextState<'new_rx, 'new_tx>: TraitWithLifetime<'new_rx, 'new_tx>;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
| |
| help: add the required where clauses: `where Self: 'rx, Self: 'tx, 'tx: 'new_tx, Self: 'new_rx, 'new_rx: 'rx, Self: 'new_tx, 'new_tx: 'tx`
|
= note: these bounds are currently required to ensure that impls have maximum flexibility
= note: we are soliciting feedback, see issue #87479 <https://github.com/rust-lang/rust/issues/87479> for more information
I simplified my example a bit, that's why you see two lifetime parameters in the error message.
UPDATE: And even if if that worked then you get back type TraitWithLifetime::NextState which the caller knows basically nothing about, especially it won't enforce the same implementation with different generics only.
This doesn't work, because I need a specific return type to be saved in a struct.
But you can emulate them do various degrees with associated types.
// This version requires the type constructor to have at most one lifetime
// parameter
type Constructor {
Ty<'a>;
}
impl Constructor for &str {
Ty<'a> = &'a str;
}
impl Constructor for () {
Ty<'a> = ();
}
Things get trickier, sometimes significantly so, if you need to support type constructors with multiple lifetimes, or otherwise effectively need conditional higher-ranked bounds in any way.
// Imaginary conditional higher-ranked bound language feature
where T: for<'a where 'b: 'a> ...
you must have spent considerable time to answer my question, thank you! I especially like the detailed example, you provided. Very helpful, much more than I thought would be possible, probably similar to what @kornel actually had in mind without me recognizing the potential. Looks promising.
Alternatively I might be able to organize information differently, keeping long lived structs separate from short lived references and keeping the latter in async functions rather than in the base object. This might allow me to cross await points without having to "switch" lifetimes. We'll see.
In any case your responses helped me to unblock my thinking.