Fn that accepts closure that returns a &str or a String

Is it possible to write a function that accepts a closure that will returns a &str or a String?

struct Data<'a> {
    data: &'a str,
}

type GetterStr    = Box<dyn for<'r,'e> Fn(&'r Data<'e>) -> &'e str>;
type GetterString = Box<dyn for<'r,'e> Fn(&'r Data<'e>) -> String>;
type Getter<T>    = Box<dyn for<'r,'e> Fn(&'r Data<'e>) -> T>;

fn fun1(d: &Data, g: &GetterStr) {
    println!("{}", g(d));
}

fn fun2(d: &Data, g: &GetterString) {
    println!("{}", g(d));
}

fn fun3<T: AsRef<str>>(d: &Data, g: &Getter<T>) {
    println!("{}", g(d).as_ref());
}

fn main() {
    let data = "hello world";
    let data = Data { data };

    let get1: GetterStr = Box::new(|d: &Data| d.data);
    fun1(&data, &get1);

    let get2: GetterString = Box::new(|d: &Data| d.data.to_uppercase());
    fun2(&data, &get2);
    
    // Fails with: expected concrete lifetime, found bound lifetime parameter 'e
    //fun3(&data, &get1);
    
    fun3(&data, &get2);
}

In the above fun3 fails to accept the GetterStr argument. The compiler complains with "expected concrete lifetime, found bound lifetime parameter 'e".

type Getter<T> = Box<dyn for<'r,'e> Fn(&'r Data<'e>) -> T + e> is not acceptable syntax and type Getter<T> = Box<dyn for<'r,'e> Fn(&'r Data<'e>) -> T<'e>> does not work either.

Is there a way to specify that the returned generic has a lifetime of of at least 'e?

You can't because bounds on for<'lifetimes> statements aren't valid in that context. You could move the lifetime out of the for clause though. Because you've got a type declaration it's easy:

type Getter<T>     = Box<dyn for<'r,'e> Fn(&'r Data<'e>) -> T>;
// To
type Getter<'e, T> = Box<dyn for<'r> Fn(&'r Data<'e>) -> T>;

Then, we can say:

fn fun3<'a: 'b + 'c, 'b, 'c, T: AsRef<str>>(d: &'a Data<'b>, g: &'c Getter<'b, T>) {
    println!("{}", g(d).as_ref());
}

More clearly:

fn fun3<
    'data: 'my_str + 'function,
    'my_str,
    'function,
    T: AsRef<str>
>(d: &'data Data<'my_str>, g: &'function Getter<'my_str, T>) {
    println!("{}", g(d).as_ref());
}

Such that you can substitute 'my_str into the Fn declaration without ever having to use a for clause.


Playground

Take a look at std::borrow::Cow<'a, str>. That probably do what you need.

Thank you. Taking advantage of lifetime elision, I simplified it to:

struct Data<'a> { data: &'a str, }

type GetterStr<'e>  = Box<dyn Fn(&Data<'e>) -> &'e str>;
type GetterString   = Box<dyn Fn(&Data) -> String>;
type Getter<'e, T>  = Box<dyn Fn(&Data<'e>) -> T>;

fn fun3<'b, T: AsRef<str>>(d: &Data<'b>, g: &Getter<'b, T>) {
    println!("{}", g(d).as_ref());
}

fn main() {
    let data = Data { data: "hello world" };
    let get1: GetterStr    = Box::new(|d: &Data| d.data);
    let get2: GetterString = Box::new(|d: &Data| d.data.to_uppercase());
    fun3(&data, &get1);    
    fun3(&data, &get2);
}

It still feels odd that the lifetime of the return type has to be bound at the function call rather than being able to specify it via a higher-rank trait bounds, but this has been pretty helpful.

Thanks!

Yes, this, I believe, is because the one that returns a string knows it does not have any tied lifetimes, the one with a str does have tied lifetimes, but the ambiguity of there being either or none (as is the case when using generics) is when the compiler gets confused.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.