Search code examples
rustpolymorphismlifetimehigher-kinded-typesquantified-constraints

Function as trait with output type polymorphic in lifetime


How do I express a constraint like

fn<F: 'static> apply_to_foos(f: F)
where
for<'a> F: fn(&'a mut Foo) -> impl Future<Output = Bar> + 'a
{
...
}

(The above expression doesn't work because constraints can't have impl in them)

If this isn't possible, what can I do instead?


Solution

  • Using a heap allocated trait object we already can express that constraint with current compiler featuers (Rust 1.61).

    fn apply_to_foos<F>(f: F)
    where
        for<'a> F: Fn(&'a mut Foo) -> Box<dyn Future<Output = Bar> + 'a>
    { /*...*/ }
    

    Maybe this is already what we want to do, but for fun and games, let's say we want to use compile time polymorphy:

    It is concievable that a future version of Rust would just allow us to replace the dyn in the above signature and replace it with an impl, while dropping the Box altogether. FYI: The Box is not needed for above signature to compile, but most real world usecase would require it in order to keep the trait object alive long enough.

    Using an impl here would require the compiler to support traits which can in turn depend on other traits. Rust cannot do that, but traits can depend on concrete types. Let's introduce a type O into our signature.

    fn apply_to_foos<F, O>(f: F)
    where
        for<'a> F: Fn(&'a mut Foo) -> O, O: Future<Output = Bar>
    { /*...*/ }
    

    This almost does what we want, but we have no way to transfer the lifetime requirement of 'a to O, since &'a is local to the constraint of F. We can factor out the entire constraint into its own trait however:

    fn apply_to_foos<F, O>(f: F) where F: for<'a> FooToBar<'a> {}
    

    with FooToBar being declared like this (for example):

    trait FooToBar<'a> {
        type O: Future<Output = Bar> + 'a;
    
        fn foo_to_bar(&self, foo: &'a mut Foo) -> Self::O;
    }
    

    Wether avoiding the heap allocation is worth the code bloat likely depends on your domain. I'd probably stick with the Box<dyn> version most of the time.