所有权,关闭,财务:大量混乱

时间:2019-06-24 21:10:07

标签: rust closures move mutable borrowing

我有以下代码段:

import {connect} from "react-redux";
import {updateStuf} from "./actions/projectActions";



class App extends Component {
    componentWillMount() {
        this.props.updateStuf();
    }

    render() {
        const {stuff} = this.props;
        return (
                <div className="stuff">

                </div>
        );
    }
}

const mapStateToProps = (state) => {
    return {
        stuff: state.project.stuff
    }
}
export default connect(mapStateToProps, {updateStuf})(App);

疑问1

我什至无法执行关闭操作

疑问2

...但是,只要我通过fn f<T: FnOnce() -> u32>(c: T) { println!("Hello {}", c()); } fn main() { let mut x = 32; let g = move || { x = 33; x }; g(); // Error: cannot borrow as mutable. Doubt 1 f(g); // Instead, this would work. Doubt 2 println!("{}", x); // 32 } 调用该闭包,我就可以多次调用它。有趣的是,如果我将其声明为f,则会收到与疑问1相同的错误。

疑问3

FnMutselfFnFnMut特征定义中指的是什么?那是封闭本身吗?还是环境? 例如。从文档中:

FnOnce

2 个答案:

答案 0 :(得分:2)

您在这里处理两种不同类型的闭包-FnOnceFnMut。两种类型的闭包都有不同的调用约定。

如果您将闭包定义为

let mut x = 32;
let g  = move || {
    x = 33;
    x
};

编译器将推断闭包的类型为FnMut。尽管闭包返回拥有的变量x,但由于xCopy,它仍然可以多次调用,因此编译器选择FnMut作为最通用的类​​型。

在调用FnMut闭包时,闭包本身通过可变引用传递。这就解释了您的第一个问题-除非将其变为可变状态,否则直接调用g无效,因为否则将无法对其进行可变引用。我还在这里隐式回答了您的第三个问题-self特征的调用方法中的Fn指的是闭包本身,可以将其视为包含所有捕获变量的结构。

调用f(g)时,您将FnMut闭包g作为FnOnce闭包传递给f()。之所以允许这样做,是因为所有FnOnce都是FnMut的特征,因此每个实现FnMut的闭包也都实现了FnOnce。现在,闭包已转换为FnOnce,也根据FnOnce调用约定对其进行了调用:

pub trait FnOnce<Args> {
    type Output;
    extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}

在这种情况下,闭包由 value 传递,因此调用会消耗闭包。您可以放弃拥有的任何价值的所有权-它不需要可变就可以起作用。

通过g调用f()可以多次调用的原因是gCopy。它仅捕获单个整数,因此可以根据需要复制多次。每次对f()的调用都会创建一个g的新副本,当在f()内部调用该副本时会使用该副本。

答案 1 :(得分:2)

需要一些有关Fn*特质家族的基础知识,以了解闭包实际上是如何工作的。您具有以下特征:

  • FnOnce,顾名思义,只能运行一次。如果查看docs页面,将会看到trait定义与您在问题中指定的定义几乎相同。不过,最重要的是以下内容:“调用”函数采用self,这意味着它消耗实现FnOnce的对象,就像任何将self用作trait的函数一样参数,它获取对象的所有权。
  • FnMut,它允许捕获的变量发生突变,换句话说,它需要&mut self。这意味着,当您创建move || {}闭包时,它将引用的所有超出闭包范围的变量都移到闭包的对象中。闭包的对象具有无法命名的类型,这意味着它对于每个闭包都是唯一的。这确实迫使用户采用某种可变的闭包版本,因此&mut impl FnMut() -> ()mut x: impl FnMut() -> ()
  • Fn,通常被认为是最灵活的。这允许用户获取实现特征的对象的不变版本。该特征的“调用”函数的函数签名是这三个特征中最简单的理解,因为它仅引用闭包,这意味着在传递或调用闭包时不必担心所有权。

要解决您的个人疑问:

  • 问题1:如上所示,当您将move放入闭包中时,该变量现在由闭包拥有。本质上,编译器生成的内容类似于以下伪代码:
struct g_Impl {
    x: usize
}
impl FnOnce() -> usize for g_Impl {
    fn call_once(mut self) -> usize {

    }
}
impl FnMut() -> usize for g_Impl {
    fn call_mut(&mut self) -> usize {
        //Here starts your actual code:
        self.x = 33;
        self.x
    }
}
//No impl Fn() -> usize.

默认情况下,它调用FnMut() -> usize实现。

  • 问题2:这里发生的是,只要closures are Copy的捕获变量均为Copy,这意味着生成的闭包将被复制到f中,因此f最终占据其中的Copy。当您将f的定义改为使用FnMut时,会出现错误,因为您面临着与怀疑1类似的情况:您正试图调用接收&mut self的函数而您已将参数声明为c: T,而不是mut c: Tc: &mut T,在&mut self眼中,这两个参数都符合FnMut的要求。
  • 最后,怀疑3,self参数是闭包本身,它已将某些变量捕获或移动到自身中,因此它现在拥有它们。