Rust - изменить ссылку на собственное значение без клона - PullRequest
1 голос
/ 06 октября 2019

Я практикую концепции Rust, которые я выучил, читая Книгу. Я смог перебрать мой список List, скопировав Box и присвоив list скопированному блоку, но интуитивно я чувствую, что должен быть способ просто «заставить его указывать на следующий указатель влиния".

Если я попытаюсь сделать это без bx.clone(), например, так: self.list = **bx, я получу «не могу выйти из **bx, который находится за изменяемой ссылкой». Это означает, что мне нужно, чтобы он был в собственности, но я не могу получить принадлежащий bx, потому что мне нужно переместить его как ссылку, когда я разыменую его в if let.

IsВозможно или желательно переместить ссылку, не копируя ее?

#[derive(Clone)]
enum List {
    Cons(u32, Box<List>),
    Nil
}
struct ListHolder {
    list: List
}

impl Iterator for ListHolder {
    type Item = u32;
    fn next(&mut self) -> Option<u32> {
        if let Cons(num, bx) = &mut self.list{
            let val = *num;
            self.list = *bx.clone(); // This is the key line
            Some(val)
        }else {
            None
        }
    }
}

use List::*;

fn main() {
    let list_inst = ListHolder {
        list: Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))))
    };
    for i in list_inst.into_iter(){
        println!("{}", i);  // Prints 1, 2, 3 as expected
    }
}

1 Ответ

4 голосов
/ 06 октября 2019

Я думаю, что ключевая проблема вашей ментальной модели в том, что вы думаете о Box<T> как о просто указателе. Ссылки на Rust (и большинство умных указателей, таких как Box<T>) - это не просто указатели, а действительные указатели. То есть нет нулевых ссылок, и ссылки должны всегда указывать на действительные данные.

Когда мы пытаемся сделать self.list = **bx;, мы перемещаем данные из bx в self.list. Однако bx не владеет своими данными. Когда изменяемый заем bx заканчивается, фактический владелец будет содержать недействительные данные.

Так что же нам делать? Самый простой способ - это то, что иногда называют уловка Джонса , где мы переключаем данные в bx для некоторого фиктивного значения. Теперь фактический владелец данных в bx не будет содержать недействительные данные. Итак, как мы это сделаем? Это сфера действия функции std::mem::replace, которая принимает изменяемую ссылку и значение и заменяет данные, стоящие за изменяемой ссылкой, на это значение, возвращая то, что раньше было за изменяемой ссылкой (включая владение!). Это именно то, что мы хотим сделать здесь с self.list = std::mem::replace(&mut **bx, List::Nil). Опять же, List::Nil это просто фиктивные данные;любой List вообще будет работать точно так же.

enum List {
    Cons(u32, Box<List>),
    Nil,
}
struct ListHolder {
    list: List,
}

impl Iterator for ListHolder {
    type Item = u32;
    fn next(&mut self) -> Option<u32> {
        if let Cons(num, bx) = &mut self.list {
            let val = *num;
            self.list = std::mem::replace(&mut **bx, List::Nil); // This is the key line
            Some(val)
        } else {
            None
        }
    }
}

use List::*;

fn main() {
    let list_inst = ListHolder {
        list: Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil)))))),
    };
    for i in list_inst.into_iter() {
        println!("{}", i); // Prints 1, 2, 3 as expected
    }
}

(детская площадка)

Чтобы быть чуть более идиоматичным, вместо &mut **bx, мы могли быпросто используйте bx.as_mut(), чтобы получить изменяемую ссылку из коробки. Кроме того, вызов into_iter для list_inst не требуется, поскольку ListHolder уже реализует Iterator, поэтому его не нужно превращать в один. Вам также может быть интересно узнать о num и val и о том, почему мы все еще должны сделать временную переменную для этого.

Причина в том, что это значение по-прежнему является справочным, и мы не владеемдоступ к владельцу (self.list). Это означает, что мы должны сделать копию, чтобы вернуться. u32 реализует Copy, так что на самом деле это не проблема, но если вы попытаетесь сделать связанный список общим по типу его элементов, он просто не будет работать. let val = *num; - это то же самое «перемещение из заемного контента», которое мы не могли сделать раньше.

Решение состоит в том, чтобы использовать std::mem::replace, чтобы получить право владения не только данными, стоящими за bx,но из всего списка. Таким образом, если мы используем std::mem::replace(&mut self.list, List::Nil) перед деструктуризацией, self.list будет заменен фиктивным значением, и мы будем владеть фактическим списком, включая значение и хвост списка. Это также означает, что мы можем просто иметь self.list = *bx, как я уверен, что вы изначально хотели.

impl Iterator for ListHolder {
    type Item = u32;
    fn next(&mut self) -> Option<u32> {
        if let Cons(num, bx) = std::mem::replace(&mut self.list, List::Nil) {
            self.list = *bx;
            Some(num)
        } else {
            None
        }
    }
}

(детская площадка)

Результат теперь выможете сделать список общим без особых усилий.

Если вы хотите узнать больше о том, как модель владения Rust влияет на реализацию связанных списков, вы можете сделать не лучше, чем отличная серия Learn Rust со слишком большим количеством связанных списков . Серия охватывает все детали в деталях, а также множество вариаций.

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