在结构体中存储闭包 — 无法推断出适当的生命周期。

6
我正在尝试在Rust中实现State单子( State 实际上是一个包装器,它将原始状态作为输入并返回修改后的状态和一些结果的函数)。以下是如何在Haskell中实现 State (为简洁起见,单子操作被重命名为 unit bind ):
data State s u = State { run :: s -> (u, s) }

-- return
unit :: u -> State s u
unit u = State $ \s -> (u, s)

-- (>>=)
bind :: State s u -> (u -> State s a) -> State s a
bind m f = State $ \s ->
             let (u, s') = run m s
             in run (f u) s'

所以我尝试用Rust重写它:

pub struct State<'r, S, U> {
    priv run: 'r |S| -> (U, S)
}

pub fn unit<'r, S, U>(value: U) -> State<'r, S, U> {
    State {
        run: |state| (value, state)
    }
}

实际上,我不确定run字段的定义是否合法——said这是一个错误。

这段代码无法编译:

/some/path/lib.rs:31:12: 31:36 error: cannot infer an appropriate lifetime due to conflicting requirements
/some/path/lib.rs:31         run: |state| (value, state)
                                ^~~~~~~~~~~~~~~~~~~~~~~~
/some/path/lib.rs:29:52: 33:2 note: first, the lifetime cannot outlive the block at 29:51...
/some/path/lib.rs:29 pub fn unit<'r, S, U>(value: U) -> State<'r, S, U> {
/some/path/lib.rs:30     State {
/some/path/lib.rs:31         run: |state| (value, state)
/some/path/lib.rs:32     }
/some/path/lib.rs:33 }
/some/path/lib.rs:31:12: 31:36 note: ...so that closure does not outlive its stack frame
/some/path/lib.rs:31         run: |state| (value, state)
                                ^~~~~~~~~~~~~~~~~~~~~~~~
/some/path/lib.rs:29:52: 33:2 note: but, the lifetime must be valid for the lifetime &'r  as defined on the block at 29:51...
/some/path/lib.rs:29 pub fn unit<'r, S, U>(value: U) -> State<'r, S, U> {
/some/path/lib.rs:30     State {
/some/path/lib.rs:31         run: |state| (value, state)
/some/path/lib.rs:32     }
/some/path/lib.rs:33 }
/some/path/lib.rs:30:5: 30:10 note: ...so that types are compatible (expected `State<'r,S,U>` but found `State<,S,U>`)
/some/path/lib.rs:30     State {
                         ^~~~~
error: aborting due to previous error

似乎我需要在实例化“unit”中的“State”时明确指定闭包表达式的生命周期,但我不知道该怎么做,所以我需要帮助。谢谢。

编辑:不幸的是,我不能使用proc(正如Vladimir建议的那样),因为一个State可以被任意执行多次。

1个回答

7
错误完全合理。目前,从函数中返回可以调用多次的闭包是完全不可能的(除非它们被作为参数传递给这些函数)。
当您查找生命周期注释位置时,您可以轻松发现您在使用生命周期方面犯了错误。当生命周期参数仅用于参数或仅用于返回值时,那么您的代码就有错误。这正是你的情况—— unit 具有'r生命周期参数,但只用于返回值。
您的代码无法编译,因为您正在创建一个堆栈闭包,并尝试将其存储在将返回给调用者的对象中。但是闭包是在堆栈上创建的,当您从函数返回时,该堆栈空间将失效,即您的闭包将被销毁。借用检查器会防止这种情况发生。
目前,在Rust中只有两种类型的闭包——堆栈盒闭包和一次性堆盒闭包(procs)。您可以使用proc来执行所需操作,但只能调用它一次。我不确定这是否符合您的需求,但是这里是代码:
pub struct State<S, U> {
    priv run: proc(S) -> (U, S)
}

pub fn unit<S: Send, U: Send>(value: U) -> State<S, U> {
    State {
        run: proc(state) (value, state)
    }
}

我不得不添加Send类型,因为procs需要它们的环境可发送。

未来,如果动态大小的类型得到支持,您将能够创建常规的盒式闭包,可以多次调用并拥有自己的环境。但是,DST仍在设计和开发中。


另外,我对闭包类型进行了一些研究。由于我是 Rust 的新手,所以不能完全确定,但我相信可以使用管理的闭包来实现所需的行为,这些闭包最近已从语言中删除。 - nameless
2
是的,你说得对。此外,还有一些已拥有的闭包也被移除了。希望 DST 能够把它们带回来。 - Vladimir Matveev
3
就我所知,Rust社区并不称||为非盒式闭包:||相当于一个&Fn<...>特质对象(对于一个假设的Fn特质),也就是说,它已经被盒装成了一个特质对象,其中数据(在这种情况下是闭包环境)位于堆栈上。一个“非盒式闭包”是&x as &Fn<...>中的x,它目前还无法创建,但正在进行相关工作。(我认为这项工作比DST更相关。讨论的开始。) - huon
@dbaupp,谢谢,你说得完全正确。我会修正答案的。 - Vladimir Matveev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接