闭包中引用的生命周期 [英] Lifetime of references in closures

查看:70
本文介绍了闭包中引用的生命周期的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

fn stage_action() ->Box()>{//将新分配的字符串拆分为多个片段let string = String::from("a:b:c");let substrings = vec![&string[0..1], &string[2..3], &string[4..5]];//返回的闭包是指的子串向量//切片没有任何进一步的分配或修改盒子::新(移动||{对于 sub 中的 substrings.iter() {println!("{}", sub);}})}fn 主(){让动作 = stage_action();//...一段时间后执行:行动();}

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

我也尝试创建一个具有相同功能的 struct 来使移动完全明确,但是 也不编译.同样,编译失败,错误是 &later[0..1] 和其他人只存活到函数结束,但借用的值必须在静态生命周期内有效".

甚至 完全避免使用 Box 似乎没有帮助 - 编译器抱怨对象的寿命不够长.

解决方案

这里没有特定于闭包的内容;它相当于:

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

您正在尝试移动String,而有未偿还的借用.在我这里的例子中,它是另一个变量;在您的示例中,它是关闭的环境.无论哪种方式,您仍在移动它.

此外,您正在尝试将子字符串移动到与拥有字符串相同的闭包环境中.这使得整个问题等同于 为什么我不能在同一个结构中存储一个值和对该值的引用?:

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

<块引用>

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

我不同意;stringsubstrings 在闭包环境的外部创建并移动到其中.正是这个举动让你绊倒了.

<块引用>

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

这种情况下确实如此,但只是因为您,程序员,可以保证里面String中的字符串数据的地址将保持不变.您知道这一点有两个原因:

  • String 在内部通过堆分配实现,因此移动 String 不会移动字符串数据.
  • String 永远不会改变,这可能会导致字符串重新分配,使任何引用无效.

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

否则,您将满足生命周期跟踪过于热心的特殊情况"下列出的标准.在 为什么我不能在同一个结构中存储一个值和对该值的引用?,所以你可以使用 crates喜欢:

owning_ref

use owning_ref::RcRef;//0.4.1使用 std::rc::Rc;fn stage_action() ->实现 Fn() {let string = RcRef::new(Rc::new(String::from("a:b:c")));让子串 = vec![string.clone().map(|s| &s[0..1]),string.clone().map(|s| &s[2..3]),string.clone().map(|s| &s[4..5]),];动||{为 sub in &substrings {println!("{}", &**sub);}}}fn 主(){让动作 = stage_action();行动();}

ouroboros

使用ouroboros::self_referencing;//0.2.3fn stage_action() ->实现 Fn() {#[self_reference]结构物{字符串:字符串,#[借用(字符串)]子串:Vec<&'this str>,}让东西 = ThingBuilder {字符串:String::from("a:b:c"),substrings_builder: |s|vec![&s[0..1], &s[2..3], &s[4..5]],}.建造();动||{thing.with_substrings(|子字符串| {对于子字符串中的子{println!("{}", sub);}})}}fn 主(){让动作 = stage_action();行动();}


请注意,我不是这些 crate 中的任何一个的专家用户,因此这些示例可能不是最好使用它.

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 safely moved to the closure.

The use case is a function that does some preparatory work and returns a closure that will do the rest of the work. The reason for this design are execution constraints: the first part of the work involves allocation, and the remainder must do no allocation. Here is a minimal example:

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

This fails to compile, correctly stating that &string[0..1] and others must not outlive string. But if string were moved into the closure, there would be no problem. Is there a way to force that to happen, or another approach that would allow the closure to refer to parts of an object created just outside of it?

I've also tried creating a struct with the same functionality to make the move fully explicit, but that doesn't compile either. Again, compilation fails with the error that &later[0..1] and others only live until the end of function, but "borrowed value must be valid for the static lifetime".

Even completely avoiding a Box doesn't appear to help - the compiler complains that the object doesn't live long enough.

解决方案

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.

这篇关于闭包中引用的生命周期的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆