如何将创建包含具有生命周期的Cell的结构的函数传递给另一个函数?

Drg*_*ble 12 lifetime rust

我正在尝试将构造函数作为参数传递给另一个函数.该函数创建一个具有关联生命周期的结构.我创建了一些此结构可以引用的其他对象后,我需要从this指针创建一个结构.以下示例似乎有效:

struct Bar<'a> {
    number: Option<&'a usize>,
}

impl<'a> Bar<'a> {
    pub fn new() -> Bar<'a> {
        Bar { number: None }
    }
}

fn foo<'a, F>(func: &F)
where
    F: Fn() -> Bar<'a>,
{
    let number = 42;
    let mut bar = (func)();
    bar.number = Some(&number);
}

fn main() {
    foo(&Bar::new);
}
Run Code Online (Sandbox Code Playgroud)

当我添加一个Cell内部可变性时,它不会编译:

use std::cell::Cell;

struct Bar<'a> {
    number: Cell<Option<&'a usize>>,
}

impl<'a> Bar<'a> {
    pub fn new() -> Bar<'a> {
        Bar {
            number: Cell::new(None),
        }
    }
}

fn foo<'a, F>(func: &F)
where
    F: Fn() -> Bar<'a>,
{
    let number = 42;
    let bar = (func)();
    bar.number.set(Some(&number));
}

fn main() {
    foo(&Bar::new);
}
Run Code Online (Sandbox Code Playgroud)

给我以下错误:

error[E0597]: `number` does not live long enough
  --> src/main.rs:21:26
   |
21 |     bar.number.set(Some(&number));
   |                          ^^^^^^ borrowed value does not live long enough
22 | }
   | - borrowed value only lives until here
   |
note: borrowed value must be valid for the lifetime 'a as defined on the function body at 15:1...
  --> src/main.rs:15:1
   |
15 | / fn foo<'a, F>(func: &F)
16 | | where
17 | |     F: Fn() -> Bar<'a>,
18 | | {
...  |
21 | |     bar.number.set(Some(&number));
22 | | }
   | |_^
Run Code Online (Sandbox Code Playgroud)

为什么第一个例子工作而不是第二个?有没有办法为let mut bar函数指定一个存在的生命周期,直到函数结束,而不是'a包含整个函数?如果没有非词汇生命周期或更高类型的构造函数等,这是不可能的?

She*_*ter 7

编译器比你给它的功能更聪明.它阻止了你引入内存不安全:

fn foo<'a, F>(func: &F)
where
    F: Fn() -> Bar<'a>,
{
    let number = 42;
    let bar = (func)();
    bar.number.set(Some(&number));
}
Run Code Online (Sandbox Code Playgroud)

此代码表示调用者foo可以为其指定生命周期'a,但是该方法的主体会将引用存储到该值中.存储的引用不能保证存活那么久.作为一个明显的例子,调用者可能需要'a== 'static,但是函数不可能完成:

fn b() -> Bar<'static> {
    Bar {
        number: Cell::new(None),
    }
}

fn main() {
    foo(&b);
}
Run Code Online (Sandbox Code Playgroud)

请注意,这与闭包或函数无关:

use std::cell::Cell;

fn main() {
    let number = Cell::new(None);
    let x = 1;
    number.set(Some(&x));
    let y = 2;
    number.set(Some(&y));
}
Run Code Online (Sandbox Code Playgroud)
error[E0597]: `x` does not live long enough
 --> src/main.rs:6:22
  |
6 |     number.set(Some(&x));
  |                      ^ borrowed value does not live long enough
...
9 | }
  | - `x` dropped here while still borrowed
  |
  = note: values in a scope are dropped in the opposite order they are created
Run Code Online (Sandbox Code Playgroud)

为什么第一个例子工作而不是第二个?

因为编译器知道Cell(确实UnsafeCell)需要考虑您将在创建的类型中存储值的可能性.

来自Nomicon,强调我的:

  • UnsafeCell<T>,Cell<T>,RefCell<T>,Mutex<T>和所有其他内部可变性的类型是在不变的T(这是*mut T由隐喻)

方差是一个密集的话题,我无法简洁地解释.

@trentcl提供了这个示例,表明您的原始代码可能没有按照您的想法执行.

如果没有Cell,编译器就会知道将返回类型的生命周期自动调整为更短的一个是安全的.'a但是,如果我们强制类型更长,我们会得到相同的错误:

fn foo<'a, F>(func: F)
where
    F: Fn() -> Bar<'a>,
{
    let number = 42;
    let mut bar: Bar<'a> = func();
    //           ^^^^^^^
    bar.number = Some(&number);
}
Run Code Online (Sandbox Code Playgroud)
error[E0597]: `number` does not live long enough
  --> src/main.rs:17:24
   |
17 |     bar.number = Some(&number);
   |                        ^^^^^^ borrowed value does not live long enough
18 | }
   | - borrowed value only lives until here
   |
note: borrowed value must be valid for the lifetime 'a as defined on the function body at 11:1...
  --> src/main.rs:11:1
   |
11 | / fn foo<'a, F>(func: F)
12 | | where
13 | |     F: Fn() -> Bar<'a>,
14 | | {
...  |
17 | |     bar.number = Some(&number);
18 | | }
   | |_^
Run Code Online (Sandbox Code Playgroud)

没有[...]这是不可能的

是的,但我不确定它究竟会是什么.我相信它需要来自RFC 1598的通用关联类型(GAT).

我的第一个想法是尝试更高等级的特质界限(HRTB):

fn foo<F>(func: F)
where
    F: for<'a> Fn() -> Bar<'a>,
{
    let number = 42;
    let bar = func();
    bar.number.set(Some(&number));
}
Run Code Online (Sandbox Code Playgroud)

这会触发E0582:

error[E0582]: binding for associated type `Output` references lifetime `'a`, which does not appear in the trait input types
  --> src/main.rs:17:25
   |
17 |     F: for <'a> Fn() -> Bar<'a>,
   |                         ^^^^^^^
Run Code Online (Sandbox Code Playgroud)

说实话,我根据提供的示例看不到代码中的值.如果你返回一个Bar值,你可以使它变得可变,消除任何内部可变性的需要.

您还可以根据需要更改闭包以获取值:

fn foo<F>(func: F)
where
    F: for<'a> Fn(&'a i32) -> Bar<'a>,
{
    let number = 42;
    let bar = func(&number);
}
Run Code Online (Sandbox Code Playgroud)

也可以看看: