Closure lifetime question

I can't seem to figure out how to express the right constraints to get the following example to work.

struct Type;

impl Type {
    fn get_some_strings(&self) -> Vec<String> {
        Vec::new()
    }
    
    fn with_args_v1<'a, F>(&'a self, f: F)
    where
        for<'b> F: FnOnce(Vec<&'b str>),
    {
        let strings: Vec<String> = self.get_some_strings();
        let mut args = vec!["some", "static", "str"];
        for string in &strings {
            args.push(string);
        }
        f(args)
    }
    
    fn with_args_v2<'a, F>(&'a self, f: F)
    where
        for<'b, 'c> F: FnOnce(&'b mut Vec<&'c str>),
    {
        let strings: Vec<String> = self.get_some_strings();
        let mut args = vec!["some", "static", "str"];
        for string in &strings {
            args.push(string);
        }
        f(&mut args)
    }
}

fn main() {
    let more_strings = Vec::<String>::new();
    let t = Type;
    
    // This doesn't work
    t.with_args_v1(|mut args| {
        args.extend(more_strings.iter().map(String::as_str));
    });
    
    // Neither does this
    t.with_args_v2(|args| {
        args.extend(more_strings.iter().map(String::as_str));
    });
    
    // But this does
    let strings: Vec<String> = t.get_some_strings();
    let mut args = vec!["some", "static", "str"];
    for string in &strings {
        args.push(string);
    }
    args.extend(more_strings.iter().map(String::as_str));
}

I think I need the HRTBs on F, because I need the function, not the caller, to choose the lifetimes 'b and 'c (is that right?). But I don't know how to communicate that the borrow of more_strings should last until the end of the closure call. I also tried using the move keyword with no luck.

Can anyone explain what I'm not understanding or how to express this properly?

You seem to have the same issue (expressing maximum lifetimes for HRTBs) as in Argument requires that ... is borrowed for 'static. The solutions explained there should work here as well, for example something like this (note the extra zero sized argument to the closure).

2 Likes

There may be some convoluted work-around, but basically there's no good way to convey "my Fn is valid for the extent of this function call and thus able to modify your temporary variables."

The versions that work do so because all of the sources that are collected from are local. If more_strings was part of your method, you could add references to within the method, too.


If you can have the function handle its own temporary values by changing the closure type to return values with a nameable lifetime, that's relatively easy to express.

impl Type {
    fn with_args_v3<'foreign, F>(&self, mut f: F)
    where
        // This is practically F: Iterator<Item=&'foreign str>
        // Or you could do something like:
        //    F: FnOnce() -> I, I: Iterator<Item=&'foreign str>
        // etc.
        F: FnMut() -> Option<&'foreign str>,
    {
        let strings = self.get_some_strings();
        let mut args = vec!["some", "static", "str"];
        for string in &strings {
            args.push(string);
        }
        while let Some(string) = f() {
            args.push(string);
        }
    }
}

fn main() {
    let more_strings = Vec::<String>::new();
    let t = Type;
    
    let mut iter = more_strings.iter().map(String::as_ref);
    t.with_args_v3(move || iter.next());
}

Do you have a more thorough use-case to explain the motivation? Generating locals, taking reference to them in another local, and then adding more references via closure is a somewhat strange pattern. The purpose of strings or args isn't clear to me.

2 Likes

@Lej77, yes, I saw that post and skimmed through it a bit. I guess I didn't realize it was the same issue. I'll read through it more closely. Thanks.

@quinedot, I don't have a particularly strong motivation for the pattern. It arose out of an attempt to refactor some code, but there's probably a better way to do it. I was mostly trying to figure out what I didn't understand. It seemed like it should be possible -- and it is, thanks to the solution from @Lej77 -- but, as you say, it's not very straightforward to do. I'll re-evaluate my problem to see what I could do instead.

Thanks for the explanations.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.