尝试在匹配中改变结构时借用检查器问题

时间:2016-09-06 20:37:49

标签: rust borrow-checker

我正在尝试在Rust中实现懒惰的“thunks”,我无法弄清楚如何让我的代码通过借用检查器。基本的想法是Thunk<T>只能在两个ThunkState s中的一个:

  1. Forced,其值为T;
  2. Unforced,带有一个带框的闭包,返回T
  3. 我天真的代码是这样的:

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

    我收到以下两个编译错误:

    error[E0506]: cannot assign to `self.0` because it is borrowed
      --> src/main.rs:21:17
       |
    19 |             ThunkState::Unforced(ref f) => {
       |                                  ----- borrow of `self.0` occurs here
    20 |                 // TROUBLE HERE
    21 |                 self.0 = ThunkState::Forced(f());
       |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ assignment to borrowed `self.0` occurs here
    
    error[E0502]: cannot borrow `*self` as mutable because `self.0.0` is also borrowed as immutable
      --> src/main.rs:22:17
       |
    19 |             ThunkState::Unforced(ref f) => {
       |                                  ----- immutable borrow occurs here
    ...
    22 |                 self.get()
       |                 ^^^^ mutable borrow occurs here
    23 |             }
    24 |         }
       |         - immutable borrow ends here
    

    我经历了各种迭代尝试的东西(例如,match *self.0,使用&mut模式中的ThunkState以及一些变体),但我尽量尝试,我无法弄清楚如何解决它。

    1. 我试图做一些没有意义的事情吗?
    2. 如果没有,是什么让这个例子变得如此棘手,我该如何才能做到这一点?
    3. 稍微盯着它,我已经制定了以下假设:self.0的赋值将使该匹配分支中的f引用无效。这是正确的吗?如果是这样,那么我如何实现我正在尝试做的事情 - 在使用它之后丢弃闭包?

4 个答案:

答案 0 :(得分:2)

这确实是一个棘手的问题,但它是可能的。对于这样的事情,在mem module中搜索有用的功能通常是个好主意。

我已经提出了解决方案,但我认为还有很大的改进空间。

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::replaceself中获取重要值。此外,您可以通过创建某种虚拟值(例如unsafe)来避免Box::new(|| panic!())

答案 1 :(得分:2)

您的原始代码按原样使用non-lexical lifetimes启用(2018年版):

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

现在支持这一点,因为跟踪哪个匹配臂的借用现在更加精确。

答案 2 :(得分:0)

我找到了半工作的东西:

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"),
        }
    }
}

它编译,但在尝试使用此类型后,我所学到的是我可能需要interior mutability

答案 3 :(得分:0)

问题是你在class ExampleScrubber < Rails::Html::PermitScrubber def initialize super self.tags = %w( div ) end def allowed_node?(node) @tags.include?(node.name) && node.attributes.values.any? do |attribute_value| attribute_value.value.include?('highlight') end end end body = "<ul>\n<li class='highlight'>yes</li>\n<li>tests</li>\n</ul>\n\n<p><img src=\"something.jpeg\" alt=\"something\"></p>\n<div class='highlight'>Something</div><div>Else</div>" results = ActionController::Base.helpers.sanitize(body, scrubber: ExampleScrubber.new) # => "\nyes\ntests\n\n\n\n<div class=\"highlight\">Something</div>Else" 的词汇借用环境中试图做太多(为了避免return)。

你能做的是:

  1. 将您需要借用的值的计算结果从match self.0 { ... }移动到外部范围中定义的变量中。
  2. 在不需要这些值的路径上提前退出
  3. 在匹配声明
  4. 之后使用值

    应用于您的示例,解决方案可能是:

    self.0