Rust 尝试在匹配中变异结构时出现借用检查器问题
我试图在Rust中实现懒惰的“thunks”,但我就是不知道如何让我的代码通过借用检查器。基本思想是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>
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()
}
}
}
}