Lifetime of references in closures

后端 未结 1 1589
深忆病人
深忆病人 2021-01-22 22:08

I need a closure to refer to parts of an object in its enclosing environment. The object is created within the environment and is scoped to it, but once created it could be safe

相关标签:
1条回答
  • 2021-01-22 23:06

    There's nothing specific to closures here; it's the equivalent of:

    fn main() {
        let string = String::from("a:b:c");
        let substrings = vec![&string[0..1], &string[2..3], &string[4..5]];
        let string = string;
    }
    

    You are attempting to move the String while there are outstanding borrows. In my example here, it's to another variable; in your example it's to the closure's environment. Either way, you are still moving it.

    Additionally, you are trying to move the substrings into the same closure environment as the owning string. That's makes the entire problem equivalent to Why can't I store a value and a reference to that value in the same struct?:

    struct Environment<'a> {
        string: String,
        substrings: Vec<&'a str>,
    }
    
    fn thing<'a>() -> Environment<'a> {
        let string = String::from("a:b:c");
        let substrings = vec![&string[0..1], &string[2..3], &string[4..5]];
        Environment {
            string: string,
            substrings: substrings,
        }
    }
    

    The object is created within the environment and is scoped to it

    I'd disagree; string and substrings are created outside of the closure's environment and moved into it. It's that move that's tripping you up.

    once created it could be safely moved to the closure.

    In this case that's true, but only because you, the programmer, can guarantee that the address of the string data inside the String will remain constant. You know this for two reasons:

    • String is internally implemented with a heap allocation, so moving the String doesn't move the string data.
    • The String will never be mutated, which could cause the string to reallocate, invalidating any references.

    The easiest solution for your example is to simply convert the slices to Strings and let the closure own them completely. This may even be a net benefit if that means you can free a large string in favor of a few smaller strings.

    Otherwise, you meet the criteria laid out under "There is a special case where the lifetime tracking is overzealous" in Why can't I store a value and a reference to that value in the same struct?, so you can use crates like:

    owning_ref

    use owning_ref::RcRef; // 0.4.1
    use std::rc::Rc;
    
    fn stage_action() -> impl Fn() {
        let string = RcRef::new(Rc::new(String::from("a:b:c")));
    
        let substrings = vec![
            string.clone().map(|s| &s[0..1]),
            string.clone().map(|s| &s[2..3]),
            string.clone().map(|s| &s[4..5]),
        ];
    
        move || {
            for sub in &substrings {
                println!("{}", &**sub);
            }
        }
    }
    
    fn main() {
        let action = stage_action();
        action();
    }
    

    ouroboros

    use ouroboros::self_referencing; // 0.2.3
    
    fn stage_action() -> impl Fn() {
        #[self_referencing]
        struct Thing {
            string: String,
            #[borrows(string)]
            substrings: Vec<&'this str>,
        }
    
        let thing = ThingBuilder {
            string: String::from("a:b:c"),
            substrings_builder: |s| vec![&s[0..1], &s[2..3], &s[4..5]],
        }
        .build();
    
        move || {
            thing.with_substrings(|substrings| {
                for sub in substrings {
                    println!("{}", sub);
                }
            })
        }
    }
    
    fn main() {
        let action = stage_action();
        action();
    }
    

    Note that I'm no expert user of either of these crates, so these examples may not be the best use of it.

    0 讨论(0)
提交回复
热议问题