我正在尝试在Rust中实现懒惰的“thunks”,我无法弄清楚如何让我的代码通过借用检查器。基本的想法是Thunk<T>
只能在两个ThunkState
s中的一个:
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()
}
}
}
}
我收到以下两个编译错误:
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
以及一些变体),但我尽量尝试,我无法弄清楚如何解决它。
稍微盯着它,我已经制定了以下假设:self.0
的赋值将使该匹配分支中的f
引用无效。这是正确的吗?如果是这样,那么我如何实现我正在尝试做的事情 - 在使用它之后丢弃闭包?
答案 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::replace
从self
中获取重要值。此外,您可以通过创建某种虚拟值(例如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
)。
你能做的是:
match self.0 { ... }
移动到外部范围中定义的变量中。应用于您的示例,解决方案可能是:
self.0