Rust 尝试在匹配中变异结构时出现借用检查器问题

Rust 尝试在匹配中变异结构时出现借用检查器问题,rust,borrow-checker,Rust,Borrow Checker,我试图在Rust中实现懒惰的“thunks”,但我就是不知道如何让我的代码通过借用检查器。基本思想是Thunk只能处于以下两种Thunk状态之一: Forced,其类型为T Unforced,它携带一个盒式闭包,返回一个T 我天真的代码是这样的: pub struct Thunk<T>(ThunkState<T>); enum ThunkState<T> { Forced(T), Unforced(Box<Fn() -> T>

我试图在Rust中实现懒惰的“thunks”,但我就是不知道如何让我的代码通过借用检查器。基本思想是
Thunk
只能处于以下两种
Thunk状态之一:

  • Forced
    ,其类型为
    T
  • Unforced
    ,它携带一个盒式闭包,返回一个
    T
  • 我天真的代码是这样的:

    pub struct Thunk<T>(ThunkState<T>);
    
    enum ThunkState<T> {
        Forced(T),
        Unforced(Box<Fn() -> T>),
    }
    
    impl<T> Thunk<T> {
        pub fn new<F>(f: F) -> Thunk<T>
        where
            F: Fn() -> T + 'static,
        {
            Thunk(ThunkState::Unforced(Box::new(f)))
        }
    
        pub fn get(&mut self) -> &T {
            match self.0 {
                ThunkState::Forced(ref t) => &t,
                ThunkState::Unforced(ref f) => {
                    // TROUBLE HERE
                    self.0 = ThunkState::Forced(f());
                    self.get()
                }
            }
        }
    }
    
    我已经尝试过各种各样的东西(例如,
    match*self.0
    ,在
    ThunkState
    模式中使用
    &mut
    ,以及一些变体),但是尽管我尝试过,我还是不知道如何修复它

  • 我是不是在试图做一些毫无意义的事情
  • 如果不是,是什么让这个例子如此棘手,我该如何正确处理

  • 再看一点,我已经制定了以下假设:分配给
    self.0
    将使匹配分支中的
    f
    引用无效。是这样吗?如果是这样的话,那么在使用闭包之后,我如何实现我想要做的呢?

    这确实是一个棘手的问题,但这是可能的。对于这样的东西,在中搜索有用的函数通常是个好主意

    我已经想出了一个解决办法,但我认为还有很大的改进空间

    pub fn get(&mut self) -> &T {
        let mut f = None;
    
        if let ThunkState::Unforced(ref mut f_ref) = self.0 {
            f = Some(std::mem::replace(f_ref, unsafe {
                std::mem::uninitialized()
            }));
        }
    
        if let Some(f) = f {
            self.0 = ThunkState::Forced(f());
        }
    
        match self.0 {
            ThunkState::Forced(ref t) => &t,
            _ => unreachable!(),
        }
    }
    

    这至少可以编译得很好。诀窍是使用
    mem::replace
    首先从
    self
    中获取重要值。此外,您可以通过创建某种类型的虚拟值(如
    Box::new(| | panic!())
    )来避免
    的不安全性

    pub struct Thunk<T>(ThunkState<T>);
    
    enum ThunkState<T> {
        Forced(T),
        Unforced(Box<Fn() -> T>),
    }
    
    impl<T> Thunk<T> {
        pub fn new<F>(f: F) -> Thunk<T>
        where
            F: Fn() -> T + 'static,
        {
            Thunk(ThunkState::Unforced(Box::new(f)))
        }
    
        pub fn get(&mut self) -> &T {
            match self.0 {
                ThunkState::Forced(ref t) => &t,
    
                // Don't actually bind a variable to the boxed closure here.
                ThunkState::Unforced(_) => {
                    self.0 = ThunkState::Forced(self.0.compute());
                    self.get()
                }
            }
        }
    }
    
    impl<T> ThunkState<T> {
        fn compute(&self) -> T {
            match self {
                &ThunkState::Unforced(ref f) => f(),
                // FIXME: get rid of this panic?
                &ThunkState::Forced(_) => panic!("case never used"),
            }
        }
    }
    
    pub-struct-Thunk(ThunkState);
    enum ThunkState{
    强制(T),
    非受迫(方框T>),
    }
    impl-Thunk{
    新酒吧(f:f)->Thunk
    哪里
    F:Fn()->T+'静态,
    {
    Thunk(ThunkState::非受迫(Box::new(f)))
    }
    发布fn获取(&mut self)->&T{
    匹配self.0{
    ThunkState::强制(参考t)=>&t,
    //这里不要将变量绑定到装箱闭包。
    ThunkState::非受迫()=>{
    self.0=ThunkState::Forced(self.0.compute());
    self.get()
    }
    }
    }
    }
    impl-ThunkState{
    fn计算(&self)->T{
    匹配自我{
    &ThunkState::非受迫(参考f)=>f(),
    //摆脱这种恐慌?
    &ThunkState::Forced()=>panic!(“从未使用过案例”),
    }
    }
    }
    

    它是编译的,但在尝试使用这种类型之后,我学到的是我可能需要的。

    问题是,您试图在
    匹配self.0{…}
    的词汇借用上下文中执行太多操作(为了避免
    返回)

    您可以做的是:

  • 将对需要从
    self.0
    借用的值执行的计算结果移动到外部范围中定义的变量中
  • 在不需要这些值的路径上尽早退出
  • 使用match语句后的值
  • 应用于您的示例,解决方案可以是:

    pub struct Thunk<T>(ThunkState<T>);
    
    enum ThunkState<T> {
        Forced(T),
        Unforced(Box<Fn() -> T>),
    }
    
    impl<T> Thunk<T> {
        pub fn new<F>(f: F) -> Thunk<T>
        where
            F: Fn() -> T + 'static,
        {
            Thunk(ThunkState::Unforced(Box::new(f)))
        }
    
        pub fn get(&mut self) -> &T {
            let func_result: T;
            match self.0 {
                ThunkState::Forced(ref t) => {
                    return &t;
                }
                ThunkState::Unforced(ref f) => {
                    func_result = f();
                }
            }
            self.0 = ThunkState::Forced(func_result);
            self.get()
        }
    }
    
    pub-struct-Thunk(ThunkState);
    enum ThunkState{
    强制(T),
    非受迫(方框T>),
    }
    impl-Thunk{
    新酒吧(f:f)->Thunk
    哪里
    F:Fn()->T+'静态,
    {
    Thunk(ThunkState::非受迫(Box::new(f)))
    }
    发布fn获取(&mut self)->&T{
    设func_结果:T;
    匹配self.0{
    ThunkState::强制(参考t)=>{
    返回&t;
    }
    ThunkState::非受迫(参考f)=>{
    func_result=f();
    }
    }
    self.0=ThunkState::Forced(函数结果);
    self.get()
    }
    }
    
    您的原始代码在启用后仍能正常工作(2018版提供):

    pub-struct-Thunk(ThunkState);
    enum ThunkState{
    强制(T),
    非受迫(方框T>),
    }
    impl-Thunk{
    新酒吧(f:f)->Thunk
    哪里
    F:Fn()->T+'静态,
    {
    Thunk(ThunkState::非受迫(Box::new(f)))
    }
    发布fn获取(&mut self)->&T{
    匹配self.0{
    ThunkState::强制(参考t)=>t,
    ThunkState::非受迫(参考f)=>{
    self.0=ThunkState::Forced(f());
    self.get()
    }
    }
    }
    }
    

    这一点现在得到了支持,因为对match arm中借用内容的跟踪现在更加精确。

    驱动注释:我看到的实现使用了三态、强制(T)、InProgress、Closure(F)@bluss。我看了一些人的代码,然后。如果你知道其他的例子,我也想看看。我不同意。我认为他们使用它是为了在
    replace
    调用中使用一些东西,在调用中,他们将其替换为EvaluationInProgress。如果您确定
    Forced
    案例不会发生,您可以更改
    恐慌!()
    无法访问!()
    。这很好,因为thunk使用的是
    Fn()
    (通过共享引用调用许多人),就像在问题中一样。太棒了,这个问题是我在语言方面最大的痛点之一,必须用这样明显安全的块来对抗借用检查器。对于其他感兴趣的人,NLL特性的进展正在@ayoon进行跟踪。请注意,此代码在2018年稳定运行。NLL的某些方面还没有完全实现,但大部分要点都是。
    pub struct Thunk<T>(ThunkState<T>);
    
    enum ThunkState<T> {
        Forced(T),
        Unforced(Box<Fn() -> T>),
    }
    
    impl<T> Thunk<T> {
        pub fn new<F>(f: F) -> Thunk<T>
        where
            F: Fn() -> T + 'static,
        {
            Thunk(ThunkState::Unforced(Box::new(f)))
        }
    
        pub fn get(&mut self) -> &T {
            match self.0 {
                ThunkState::Forced(ref t) => t,
                ThunkState::Unforced(ref f) => {
                    self.0 = ThunkState::Forced(f());
                    self.get()
                }
            }
        }
    }