【问题标题】:Lifetime of references in closures闭包中引用的生命周期
【发布时间】:2017-03-12 12:51:09
【问题描述】:

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

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

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();
}

这无法编译,正确说明&amp;string[0..1] 和其他人不得超过string。但是如果将string 移到闭包中,就没有问题了。有没有办法强制这种情况发生,或者有另一种方法可以让闭包引用在它之外创建的对象的一部分?

我还尝试创建具有相同功能的struct 以使移动完全明确,但doesn't compile either。同样,编译失败,错误为&amp;later[0..1] 和其他人只能活到函数结束,但是“借用的值必须在静态生命周期内有效”。

即使completely avoiding a Box 似乎也无济于事 - 编译器抱怨该对象的寿命不够长。

【问题讨论】:

    标签: rust


    【解决方案1】:

    这里没有什么特定于闭包的;相当于:

    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,而有未完成的借用。在我的例子中,它是另一个变量;在您的示例中,它针对闭包的环境。无论哪种方式,您仍在移动它。

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

    对象是在环境中创建的,并且作用域是它

    我不同意; stringsubstrings 在闭包环境之外创建,然后移入其中。正是这个动作让你绊倒。

    一旦创建,它就可以安全地移动到闭包中。

    this 的情况下是正确的,但这只是因为你,程序员,可以保证字符串数据的地址 inside String 将保持不变。您知道这一点有两个原因:

    • String 在内部使用堆分配实现,因此移动 String 不会移动字符串数据。
    • String 永远不会发生变异,这可能会导致字符串重新分配,从而使任何引用无效。

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

    否则,您符合Why can't I store a value and a reference to that value in the same struct? 中“存在生命周期跟踪过度的特殊情况”下列出的标准,因此您可以使用以下 crates:

    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();
    }
    

    请注意,我不是这两个 crate 的专家用户,因此这些示例可能不是它的最佳使用

    【讨论】:

    • 链接的答案演示了对自身的引用的两步创建。我认为它可以在这里工作,使用一个盒子来防止移动。但是it doesn't compile, either,我不确定我是否理解为什么——如果对象被装箱,肯定没有动作?还是编译器无法证明对象永远不会被移出盒子的情况?
    • @user4815162342 您的意思是链接答案中带有警告 “但创建的值受到高度限制 - 它永远不能移动。值得注意的是,这意味着它不能从函数返回"(强调我的)?您尝试通过从函数返回来移动的相同样式的结构?移动盒子算作移动。在这种情况下,编译器不知道Box 有什么特别之处。这就是 owning_ref 存在的原因。
    • 不用那么快,我显然没有意识到“移动盒子算作移动”(不是答案的一部分)。那么,为什么移动盒子算作移动呢?毕竟,它不会移动对象。
    • @user4815162342 并不想变得活泼,只是指出您正在谈论的完全相同上下文的确切问题已包含在副本中,就在您为示例改编的代码部分之后.无论出于何种原因,我有过人们不阅读已经回答问题的下一个句子的经历,所以我采取了再次指出的方式,将相关方面加粗,没有不尊重的意思。
    • 我通过摆脱向量解决了这个问题。将子字符串收集到向量中的代码现在是 Iterator,它在没有任何分配的情况下生成 &amp;str 项。这种改变使得将迭代移动到闭包中成为可能,现在只需要字符串就可以工作了。我接受了这个答案,因为它回答了我实际提出的问题,而且非常详细。
    猜你喜欢
    • 1970-01-01
    • 2018-07-28
    • 2020-12-10
    • 2019-12-18
    • 2016-04-11
    • 2017-06-10
    • 2014-08-11
    • 2015-11-18
    相关资源
    最近更新 更多