TL;DR: If it's good enough, I'd probably stick with what you have. The workarounds usually aren't great.
This part is an explanation (hopefully).
Looking at the playground, I think you're trying to emulate a higher-kinded type or generic type constructors:
trait Convertable<'i> {
fn convert(input: &mut Input<'i>) -> Self;
}
impl<'i> Convertable<'i> for S<'i> { ... }
impl Registry{
fn register<A>(&self) where
// Added the `for<'a> ... <'a>`
A: for<'a> Convertable<'a>,
A: Trait
{
let mut registry = self.0.lock().unwrap();
registry.push(|input| fn_generic::<A>(input));
}
}
fn main() {
REGISTRY.register::<S>();
}
I believe you wish that meant to register something like
// Made up syntax
for<'i> |input: &mut Input<'i>| Box::new(S::<'i>::convert(input))
But type parameters like the A
on register
can only resolve to a single type, and types that differ by lifetimes are distinct types. There is no Vec
type that covers every Vec<T>
, and there is no S
type that covers every S<'x>
in your code. S
is a type constructor; you have to fill in the lifetime parameter with a concrete lifetime in order to get a type. This:
REGISTRY.register::<S>();
Is short for this:
REGISTRY.register::<S<'_>>();
// ^^^^
// "Compiler, please infer one concrete lifetime for me"
You can't use type parameters directly to represent a type constructor like S
.
The compiling code:
fn main() {
let mut registry = REGISTRY.0.lock().unwrap();
registry.push(|input| fn_generic::<S>(input));
}
Works because the compiler can infer a different lifetime parameter for S<'_>
based on the input lifetimes to the closure. The limitations of type parameters (no generic type constructors) gets in your way when trying to move this into an API signature (fn register
).
You're trying to change
// vvvvvvvvvv outside closure
ForAll<'i> Exists S<'i> such that S<'i>: ...
// ^^^^^^^^^^^^ inside ::<S<'_>>
into
// vvvvv outside type parameter
Exists<S<'*>> such that ForAll<'i> S<'i>: ...
// ^^^^^^^^^^ inside clsoure
and there's no straightforward way to do so in Rust.
Ways do exist though.
Let's back up. It looks like to me you want an ability like so:
trait Convertable {
type Output<'i>: Trait + 'i;
fn convert<'i>(input: &mut Input<'i>) -> Self::Output<'i>;
}
Which is sort of similar to
// The old trait
trait Convertable<'i> {
fn convert(input: &mut Input<'i>) -> Self;
}
// satisfiable trait bound...
for<'i> S<'i>: Convertable<'i> + Trait + 'i
// ^^^^^ ...but our type constructor is "stuck" under the for<..>
// (causing type parameter barriers)
Except by using a generic associated type (Output<'i>
), we can express a type constructor indirectly. By breaking up Self
and Output<'i>
, we can represent "S<'*>
" by some other, non-parameterized type:
struct SRepresentative;
impl Convertable for SRepresentative {
type Output<'i> = S<'i>;
fn convert<'i>(input: &mut Input<'i>) -> Self::Output<'i> {
S(input.0)
}
}
And now we can represent every S<'_>
with SRepresentative
in trait bounds:
SRepresentative: Convertable
// Similar to
for<'i> SRepresentative::Output<'i>: OldConvertable<'i> + Trait + 'i
// Or with a generic <T: Convertable>
for<'i> <T as Convertable>::Output<'i>: OldConvertable<'i> + Trait + 'i
// aka
// Exists<T> such that ForAll<'i> <T as Convertable>::Output<'i>: ...
The bounds become nicer (we've moved some of them onto the GAT, so they don't need restated) but you have to use representatives in some places.
impl Registry {
fn register<A>(&self)
where
A: Convertable,
{
let mut registry = self.0.lock().unwrap();
registry.push(fn_generic::<A>);
}
}
fn main() {
REGISTRY.register::<SRepresentative>();
}
This may still not be a full solution: You have to define Output<'i>
for all lifetimes, including 'static
. GATs also aren't trait object safe. If that's too restrictive for your use case, things get even trickier (or impossible). Here's a blog post about it. And, since you're macro'ing this, you also need to figure out how to introduce those representatives.
If you don't want to get rid of your original trait, you don't necessarily have to. But you still need separate implementations (representatives) for all implementing type constructors.