Безопасно возвращать множественные ссылки на внутренние узлы, в то же время допуская мутацию других узлов - PullRequest
0 голосов
/ 09 января 2019

Предположим, например, у меня есть связанный список, который не позволяет удалять узлы.

Можно ли вернуть общие ссылки на значения, которые уже были вставлены, при этом позволяя изменять относительный порядок узлов или вставлять новые узлы?

Даже мутация через один из узлов должна быть безопасной «на бумаге», если только один узел используется для изменения списка одновременно. Можно ли представить это в системе собственности ржавчины?

Я особенно заинтересован в том, чтобы сделать это без накладных расходов времени выполнения (возможно, с использованием небезопасного в реализации, но не в интерфейсе).

РЕДАКТИРОВАТЬ: В соответствии с просьбой, вот пример, который дает схему того, о чем я думаю.

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 голосов
/ 10 января 2019

Другими словами, данные, содержащиеся в узлах списка, обрабатываются как один ресурс, который может быть заимствован совместно / изменчиво, а ссылки между узлами являются другим ресурсом, который может быть заимствован совместно / изменчиво.

Идея иметь дело с таким пространственным разделением состоит в том, чтобы ввести разные «ключи» для каждого раздела; это легко, так как они статичны. Это было названо шаблоном PassKey.

При отсутствии брендов все равно потребуется проверка во время выполнения: проверка того, что ключ-элемент привязан к этому конкретному экземпляру списка, является обязательной для обеспечения безопасности. Это, однако, сравнение только для чтения, которое всегда будет true, поэтому производительность примерно настолько же хороша, насколько это возможно для проверки во время выполнения.

Идея, в двух словах:

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

В вашем случае использования:

  • Всегда можно изменить ссылки, поэтому мы будем использовать для этого внутреннюю изменчивость.
  • Проверка заимствования элементов внутри ручек будет выполняться путем заимствования elements.

Полную реализацию можно найти здесь . Он интенсивно использует 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 операции:

  • строительство,
  • разрушения
  • раздает ключи.

Две клавиши Handles и Elements будут взаимно заимствовать список, гарантируя, что один из них (каждый из них) может существовать одновременно. Проверка заимствования предотвратит создание новых Handles или Elements, если какой-либо их экземпляр все еще живет для этого списка:

  • 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: проверка того, что оба Handle и Handles / Elements говорят об одном и том же экземпляре list. Очень важно, чтобы get_mut был безопасным, а иначе помогает избежать ошибок.

Есть тонкая причина для Handle<'a, T>, связанного с LinkedList. У меня был соблазн удалить его, однако это позволило бы создать дескриптор из списка, уничтожить список, а затем воссоздать список по тому же адресу ... и handle.node теперь будет зависать!

Кроме того, нам нужно реализовать только те методы, которые нам нужны на Handles и Elements. Несколько образцов:

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));
    }
}
...