首页 文章

闭包中引用的生命周期

提问于
浏览
2

我需要一个闭包来引用其封闭环境中的对象部分 . 该对象是在环境中创建的,并且作用于它,但是一旦创建它就可以安全地移动到闭包 .

用例是一个执行一些准备工作的函数,并返回一个将完成其余工作的闭包 . 这种设计的原因是执行约束:工作的第一部分涉及分配,其余部分必须不进行分配 . 这是一个最小的例子:

fn stage_action() -> Box<Fn() -> ()> {
    // split a freshly allocated string into pieces
    let string = String::from("a:b:c");
    let substrings = vec![&string[0..1], &string[2..3], &string[4..5]];

    // the returned closure refers to the subtrings vector of
    // slices without any further allocation or modification
    Box::new(move || {
        for sub in substrings.iter() {
            println!("{}", sub);
        }
    })
}

fn main() {
    let action = stage_action();
    // ...executed some time later:
    action();
}

这无法编译,正确地说明 &string[0..1] 和其他人不得超过 string . 但如果 string 被移入封闭,那就不会有问题 . 有没有办法强制这种情况发生,或者另一种允许闭包引用在其外部创建的对象部分的方法?

我也尝试创建一个具有相同功能的 struct 来使移动完全显式,但是doesn't compile either . 同样,编译失败,错误是 &later[0..1] 和其他人只能活到函数结束,但"borrowed value must be valid for the static lifetime" .

即使completely avoiding a Box也没有足够长的时间 .

1 回答

  • 2

    这里的封闭没有什么特别之处;它相当于:

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

    您正试图移动 String ,而有优秀的借款 . 在我的例子中,它是关闭环境的's to another variable; in your example it' . 无论哪种方式,你仍然在移动它 .

    此外,您正在尝试将子字符串移动到与拥有字符串相同的闭包环境中 . 这使整个问题等同于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,
        }
    }
    
    fn main() {}
    

    该对象在环境中创建,并以其为范围

    我不同意; stringsubstrings 在闭包的环境之外创建并移入其中 . 它绊倒了你 .

    一旦创建,它可以安全地移动到关闭 .

    在这种情况下,这是真的,但这只是因为程序员可以保证 String 内的字符串数据的地址保持不变 . 你知道这有两个原因:

    • String 在内部使用堆分配实现,因此移动 String 不会移动字符串数据 .

    • 永远不会突变 String ,这可能导致字符串重新分配,使任何引用无效 .

    对于您的示例,最简单的解决方案是简单地将切片转换为 String 并让闭包完全拥有它们 . 如果这意味着你可以释放一个大字符串而不是一些较小的字符串,这甚至可能是一个净收益 .

    否则,您符合Why can't I store a value and a reference to that value in the same struct?中"There is a special case where the lifetime tracking is overzealous"下的标准,因此您可以使用owning_ref

    extern crate owning_ref;
    
    use std::rc::Rc;
    use owning_ref::RcRef;
    
    fn stage_action() -> Box<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]),
        ];
    
        Box::new(move || {
            for sub in substrings.iter() {
                println!("{}", &**sub);
            }
        })
    }
    
    fn main() {
        let action = stage_action();
        action();
    }
    

    请注意,我不是owning_ref的专家用户,因此这可能不是最佳用途 .

相关问题