Lifetimes in a function using a closure to borrow locals

In the following, foo uses an input closure to borrow a local with some lifetime internal to foo. The function body is just to demonstrate the type of use required. foo is generic over the return type K of the closure, and main demonstrates different examples of how I'd like to be able to call foo. Is there a way to write the generics (and possibly change the call site or closure definition) so that all those would work? Same code in the playground.

fn foo<K, F>(f: F)
where
    K: PartialEq,
    F: Fn(&String) -> K,
{
    let mut s = "test".to_string();
    {
        let a = f(&s);
        let b = f(&s);
        dbg!(a == b);
    }
    s.push_str(" borrows done");
}

fn main() {
    foo(|s| s.len());     // K = usize
    foo(|s| &s[0..1]);    // K = &str
    foo(|s| s.get(0..1)); // K = Option<&str>
}

What I think the bounds need to express is: There is some type K such that, for any lifetime 'a, foo takes &'a String and gives back a K that does not live longer than 'a. Can that be expressed?

If I manually monomorphise over the different types K, that does compile: playground Even changing the return type to a generic K, &K or Option<&K> works individually, but what I'm looking for is a way to be generic for all of them.

I don't think this is possible right now. All of the *_by_key functions in the standard library have the same limitation, that their callback can't borrow from its argument.

Oh, that is disappointing. Do you know what the limitation in this is?

What I had in mind was indeed quite close to those *_by_key style functions. No coincidence that I had picked K for "key".

I believe it's a problem of needing type constructors for that elided lifetime. Fn(&String) is essentially for<'a> Fn(&'a String), but to have a borrowed return type, it needs to say -> K<'a>.

Generic associated types (see RFC 1598 and a recent blog post) get us part way there, but I'm not sure if that can be applied in a function case like this.

1 Like

You can pretty much express the necessary bound using a helper trait, but using the function becomes a nightmare then, because type-inference for closures sucks.

Rust Playground

3 Likes

Thank you! That did exactly what I needed, and while I can agree the ergonomics aren't perfect, it's also way better than I expected from the trouble I had had.