保存可变引用,以备日后使用别名

时间:2017-11-06 06:03:23

标签: rust borrow-checker

我试图实现类似拉链的东西,但是利用可变引用来避免在我浏览数据结构时解构和重构数据结构。我已经获得了使用链表进行尝试的示例代码,尽管我非常希望将其应用于其他结构,例如树。

    @Tranasactioal
    public void write(List<T> modelObjectList) throws Exception{
            Session session = sessionFactory.getCurrentSession();
            for(T t : modelObjectList){
                session.save(t);
            }
        }

借阅检查员对此不满意:

pub enum List<T> {
    Empty,
    Cons { head: T, tail: Box<List<T>> },
}

pub struct Zipper<'a, T: 'a> {
    trail: Option<Box<Zipper<'a, T>>>,
    focus: &'a mut List<T>,
}

impl<'a, T: 'a> Zipper<'a, T> {
    pub fn down(&'a mut self) {
        match self.focus {
            &mut List::Empty => (),
            &mut List::Cons {
                tail: ref mut xs, ..
            } => {
                //We need a way to convince rust that we won't use oldZipper
                //until xs goes out of scope
                let oldZipper = std::mem::replace(
                    self,
                    Zipper {
                        trail: None,
                        focus: xs,
                    },
                );
                self.trail = Some(Box::new(oldZipper));
            }
        }
    }
}

这并不令人惊讶:如果我们有一个专注于列表的拉链并在其上调用error[E0499]: cannot borrow `*self` as mutable more than once at a time --> src/main.rs:21:21 | 16 | tail: ref mut xs, .. | ---------- first mutable borrow occurs here ... 21 | self, | ^^^^ second mutable borrow occurs here ... 30 | } | - first borrow ends here ,我们会得到一个带有对该列表尾部的可变引用的拉链,因此我们有可变别名。

但是,如果我们在down超出范围之前从不使用Zipper trail,我们将永远无法看到&#34;&#34;可变别名。这似乎类似于正常的可变借用:你不能使用你借来的变量,直到借用超出范围。

有没有办法向借阅检查员解释这个?如果你想&#34;解释&#34;借用检查器从数组中借用两个不重叠的切片是可以的,你可以使用split_at:是否有一些相应的函数强制focustrail之前永远不会被使用超出范围,这样做,是否满足借阅检查员?

1 个答案:

答案 0 :(得分:3)

为了实现您的目标,我们需要摆脱Zipper结构中的可变引用。我们可以使用可变的原始指针:它们让我们改变它们的指示对象,我们可以有多个这样的指针指向一个特定的对象,但是取消引用它们是不安全的。

以下是代码:

use std::mem;
use std::marker::PhantomData;

pub enum List<T> {
    Empty,
    Cons { head: T, tail: Box<List<T>> },
}

pub struct Zipper<'a, T: 'a> {
    trail: Option<Box<Zipper<'a, T>>>,
    focus: *mut List<T>,
    _list: PhantomData<&'a mut List<T>>,
}

impl<'a, T: 'a> Zipper<'a, T> {
    pub fn new(list: &'a mut List<T>) -> Zipper<'a, T> {
        Zipper {
            trail: None,
            focus: list as *mut List<T>,
            _list: PhantomData,
        }
    }

    pub fn down(&mut self) {
        unsafe {
            match *self.focus {
                List::Empty => (),
                List::Cons {
                    tail: ref mut xs, ..
                } => {
                    let old_zipper = mem::replace(
                        self,
                        Zipper::new(xs),
                    );
                    self.trail = Some(Box::new(old_zipper));
                }
            }
        }
    }
}

fn main() {
    let mut list = List::Cons { head: 1, tail: Box::new(List::Empty) };
    let mut zipper = Zipper::new(&mut list);
    zipper.down();
    zipper.down();
}

focus结构中的Zipper字段现在为*mut List<T>。因为这是一个原始指针,我们可以自由地复制它。这解决了Zipper::down中的编译器错误。还有一个_list类型的新字段PhantomData<&'a mut List<T>>PhantomData是一种特殊类型,旨在告诉编译器“假装我正在存储/拥有T,即使我不是”。如果没有此字段,编译器会抱怨生命周期参数'a未使用。

请注意,Zipper::new仍然需要&'a mut List<T>作为参数:这允许Zipper通过要求调用者具有对{{1}的唯一可变引用来提供安全接口我们可以用来声明结构中的其他不安全操作确实是安全的,因为我们完全了解可用的可变引用。就编译器而言,List<T> 可变地借用Zipper;如果您在List的{​​{1}}范围内尝试变异List,则会收到Zipper已经可变借用的错误。

您尚未显示任何可让用户获得对List焦点的引用的代码。我一直在想一个可能不安全的实现,并且很容易走这条路,但编译器不会告诉你这是错误的。让我告诉你:

List

回归Zipper很诱人,因为这就是我们所得到的。但是,这是错误的,因为返回值的生命周期没有以任何方式绑定到impl<'a, T: 'a> Zipper<'a, T> { pub fn focus(&mut self) -> &'a mut List<T> { unsafe { &mut *self.focus } } } ,这意味着我们可以调用&'a mut List<T>两次以获得对同一self的两个可变引用。如果focus中仍有List<T>,编译器会告诉我们是否尝试返回&'a mut List<T>(除非我们使用Zipper代码来解决它)。正确的实施方式是:

&'a mut List<T>

在此实施中,只要返回的unsafe已经存在,impl<'a, T: 'a> Zipper<'a, T> { pub fn focus(&mut self) -> &mut List<T> { unsafe { &mut *self.focus } } } 就会被相互借用,这意味着我们无法调用Zipper(或&mut List<T>)直到focus超出范围。