安全地返回对内部节点的多个引用,同时仍然允许其他节点的变异

时间:2019-01-09 07:55:33

标签: data-structures rust mutability interior-mutability

例如,假设我有一个不允许删除节点的链表。

是否有可能返回对已插入值的共享引用,同时仍允许更改节点的相对顺序或插入新节点?

即使一次仅使用一个节点对列表进行变异,即使通过一个节点进行的变异也应该是“安全的”。可以在rust的所有权系统中体现这一点吗?

我特别想在没有运行时开销的情况下这样做(可能在实现中使用不安全,但在接口中不使用)。

编辑:根据要求,下面是一个示例,概述了我的想法。

let list = MyLinkedList::<i32>::new()
let handle1 = list.insert(1); // Returns a handle to the inserted element.
let handle2 = list.insert(2);

let value1 : &i32 = handle1.get();
let value2 : &i32 = handle2.prev().get(); // Ok to have two immutable references to the same element.
list.insert(3); // Also ok to insert and swap nodes, while the references are held.
list.swap(handle1,handl2);
foo(value1,value2);

let exclusive_value: &mut i32 = handle1.get_mut(); // While this reference is held, no other handles can be used, but insertion and permutation are ok 
handle5 = list.insert(4);
list.swap(handle1, handle2);

换句话说,列表节点内包含的数据被视为可以共享/可变借用的一种资源,而节点之间的链接是可以共享/可变借用的另一种资源。

1 个答案:

答案 0 :(得分:2)

  

换句话说,列表节点内包含的数据被视为可以共享/可变借用的一种资源,而节点之间的链接是可以共享/可变借用的另一种资源。

处理这种空间分区的想法是为每个分区引入一个不同的“键”。因为它们是静态的,所以很容易。这被称为PassKey模式。

在没有 brand 的情况下,仍然需要进行运行时检查:为安全起见,必须确保将elements-key绑定到此特定列表实例。但是,这是一个只读比较,将始终为true,因此性能与运行时检查所达到的效果差不多。

简而言之:

let (handles, elements) = list.keys();
let h0 = handles.create(4);
handles.swap(h0, h1);
let e = elements.get(h0);

在您的用例中:

  • 总是可以更改链接,因此我们将为此使用内部可变性。
  • 通过借用elements对句柄中的元素进行借入检查。

完整的实现可以在here中找到。它大量使用unsafe,我不保证它是完全安全的,但希望足以进行演示。


在此实现中,我选择了哑句柄并在键类型本身上实现了操作。这限制了需要从主列表中借用的类型的数量,并简化了借用。

核心思想,然后:

struct LinkedList<T> {
    head: *mut Node<T>,
    tail: *mut Node<T>
}

struct Handles<'a, T> {
    list: ptr::NonNull<LinkedList<T>>,
    _marker: PhantomData<&'a mut LinkedList<T>>,
}

struct Elements<'a, T> {
    list: ptr::NonNull<LinkedList<T>>,
    _marker: PhantomData<&'a mut LinkedList<T>>,
}

LinkedList<T>将充当存储设备,但是仅实现3个操作:

  • 建筑
  • 破坏,
  • 分发密钥。

两个键HandlesElements都将可变地借用该列表,从而保证其中一个(每个)可以同时存在。如果借用检查仍然存在于该列表中,则借用检查将阻止创建新的HandlesElements

  • list:授予对列表存储的访问权限; Elements仅将其用于检查(必要的)运行时不变量,而不会取消引用它。
  • _marker:这是借贷检查实际上保证排他性的关键。

听起来很酷吗?为了完成,最后两个结构如下:

struct Handle<'a, T> {
    node: ptr::NonNull<Node<T>>,
    list: ptr::NonNull<LinkedList<T>>,
    _marker: PhantomData<&'a LinkedList<T>>,
}

struct Node<T> {
    data: T,
    prev: *mut Node<T>,
    next: *mut Node<T>,
}

Node是双向链表中最明显的表示形式,因此我们正在做正确的事情。 list中的Handle<T>Elements中的目的完全相同:验证HandleHandles / Elements均是谈论list的相同实例。 get_mut的安全至关重要,否则有助于避免错误。

Handle<'a, T>的生命周期与LinkedList绑定是一个微妙的原因。我很想删除它,但是这将允许从列表中创建一个句柄,销毁该列表,然后在相同的地址处重新创建一个列表...而handle.node现在将变得悬而未决!

并且,我们只需要在HandlesElements上实现所需的方法。一些示例:

impl<'a, T> Handles<'a, T> {
    pub fn push_front(&self, data: T) -> Handle<'a, T> {
        let list = unsafe { &mut *self.list.as_ptr() };

        let node = Box::into_raw(Box::new(Node { data, prev: ptr::null_mut(), next: list.head }));
        unsafe { &mut *node }.set_neighbours();

        list.head = node;

        if list.tail.is_null() {
            list.tail = node;
        }

        Handle {
            node: unsafe { ptr::NonNull::new_unchecked(node) },
            list: self.list, _marker: PhantomData,
        }
    }

    pub fn prev(&self, handle: Handle<'a, T>) -> Option<Handle<'a, T>> {
        unsafe { handle.node.as_ref() }.prev().map(|node| Handle {
            node,
            list: self.list,
            _marker: PhantomData
        })
    }
}

并且:

impl<'a, T> Elements<'a, T> {
    pub fn get<'b>(&'b self, handle: Handle<'a, T>) -> &'b T {
        assert_eq!(self.list, handle.list);

        let node = unsafe { &*handle.node.as_ptr() };
        &node.data
    }

    pub fn get_mut<'b>(&'b mut self, handle: Handle<'a, T>) -> &'b mut T {
        assert_eq!(self.list, handle.list);

        let node = unsafe { &mut *handle.node.as_ptr() };
        &mut node.data
    }
}

这应该是安全的,因为:

  • Handles,在创建新的句柄之后,永远只能访问其链接。
  • Elements仅返回对data的引用,并且访问链接时不能对其进行修改。

用法示例:

fn main() {
    let mut linked_list = LinkedList::default();
    {
        let (handles, mut elements) = linked_list.access();
        let h0 = handles.push_front("Hello".to_string());

        assert!(handles.prev(h0).is_none());
        assert!(handles.next(h0).is_none());

        println!("{}", elements.get(h0));

        let h1 = {
            let first = elements.get_mut(h0);
            first.replace_range(.., "Hallo");

            let h1 = handles.push_front("World".to_string());
            assert!(handles.prev(h0).is_some());

            first.replace_range(.., "Goodbye");

            h1
        };

        println!("{} {}", elements.get(h0), elements.get(h1));

        handles.swap(h0, h1);

        println!("{} {}", elements.get(h0), elements.get(h1));
    }
    {
        let (handles, elements) = linked_list.access();

        let h0 = handles.front().unwrap();
        let h1 = handles.back().unwrap();
        let h2 = handles.push_back("And thanks for the fish!".to_string());

        println!("{} {}! {}", elements.get(h0), elements.get(h1), elements.get(h2));
    }
}