Как правильно управлять собственностью с помощью заимствования в Rust?

Я новичок в мире Rust и до сих пор не совсем понимаю, как работает владение / заимствование / время жизни. У меня есть этот пример, чтобы продемонстрировать борьбу:

struct Node {
  value: bool,
  next: Option<Box<Node>>
}

fn populate(next: &mut Option<Box<Node>>) -> Option<Node> {
  let node = Node { value: true, next: None };
  let result = Some(Box::new(node));
  *next = result;
  Some(*next.unwrap())
}

fn main() {
  let mut node = Node {
    value: false,
    next: None
  };
  let result = populate(&mut node.next);
  println!("{}", node.unwrap().value);
  println!("{}", result.unwrap().value);
}

Я не понимаю, почему так работает ход:

fn populate(next: &mut Option<Box<Node>>) -> Option<Node> {
  let node = Node { value: true, next: None };
  let result = Some(Box::new(node));
  // *next = result;
  Some(*result.unwrap() /* *next.unwrap() */)
}

Но другой способ - нет:

fn populate(next: &mut Option<Box<Node>>) -> Option<Node> {
      let node = Node { value: true, next: None };
      let result = Some(Box::new(node));
      *next = result;
      Some(*(*next.as_ref().unwrap())) // or Some(*next.unwrap())
 }

Как правильно передать право собственности (как в примере выше) без копирования, но с заимствованием, изменяя следующую ссылку (без добавления дополнительных параметров)? Я до сих пор не разбираюсь в этой части ...


person Arsenius    schedule 02.05.2020    source источник


Ответы (2)


Если вы хотите, чтобы populate возвращал ссылку на новый Node, помещенный внутри next, ссылка должна быть частью возвращаемого типа. Вы не можете переместить (передать право собственности) узел в next, одновременно вернув его; это не то, как работает владение:

fn populate(next: &mut Option<Box<Node>>) -> Option<&mut Node> {
//                                            here: ^^^^

Вы можете попытаться вернуть Some(&mut *next.unwrap()), но это не сработает, потому что unwrap принимает self по значению. К счастью, на Option есть удобная функция, которая перенесет вас прямо с &mut Option<Box<Node>> на Option<&mut Node>, _ 12_:

fn populate(next: &mut Option<Box<Node>>) -> Option<&mut Node> {
    let node = Node {
        value: true,
        next: None,
    };
    *next = Some(Box::new(node));
    next.as_deref_mut()
}

Также читайте

person trentcl    schedule 02.05.2020
comment
Для задачи обхода бинарных деревьев и десериализации этот подход работает. Спасибо. - person Arsenius; 03.05.2020
comment
@Arsenius FWIW Я настоятельно рекомендую прочитать эту последнюю ссылку, прежде чем пытаться сделать что-либо с рекурсивно определенными структурами данных (деревья, списки, графики) в Rust - он охватывает множество особенностей владения и заимствования, с которыми вы, вероятно, столкнетесь, и показывает способы работы с ними и вокруг них. Rust - сложный язык для написания структур данных, в некоторых отношениях даже более сложный, чем C. - person trentcl; 03.05.2020

fn populate(next: &mut Option<Box<Node>>) -> Option<Node> {
  let node = Node { value: true, next: None };
  let result = Some(Box::new(node));
  *next = result;
  Some(*result.unwrap() /* *next.unwrap() */)
}

Обработка кода, как предлагает компилятор, может привести к тому, что вы написали. Теперь, взяв это, введение промежуточных переменных и типов аннотаций (чтобы увидеть, что происходит) дает следующее:

fn populate2(next: &mut Option<Box<Node>>) -> Option<Node> {
    let node : Node = Node { value: true, next: None };
    let result : Option<Box<Node>> = Some(Box::new(node));
    *next = result;
    let next_as_ref : Option<&Box<Node>> = next.as_ref();
    let next_as_ref_unwrap : &Box<Node> = next_as_ref.unwrap(); 
    let next_as_ref_unwrap_deref : Box<Node> = *next_as_ref_unwrap; // <- error here!
    Some(*next_as_ref_unwrap_deref) // or Some(*next.unwrap())
}

let next_as_ref_unwrap_deref : Box<Node> = *next_as_ref_unwrap; терпит неудачу, потому что next_as_ref_unwrap является заимствованным Box<Node>, то есть &Box<Node>. При разыменовании (т.е. *) next_as_ref_unwrap пытается переместиться, чего нельзя сделать из заимствованной переменной.

Проблема в том, что у вас есть next, который (по сути) содержит Node, однако вы хотите вернуть Node. Возникает вопрос: хотите ли вы вернуть другой (т. Е. новый Node) или вы хотите извлечь (т. Е. take) Node из next и вернуть его. Если вы хотите take и вернуть его:

fn populate(next: &mut Option<Box<Node>>) -> Option<Node> {
  let node = Node { value: true, next: None };
  let result = Some(Box::new(node));
  *next = result;
  next.take().map(|boxed_node| *boxed_node)
}

Приведенный выше код компилируется, но, по крайней мере, сомнительно, поскольку он принимает next, который по существу используется как локальная переменная и впоследствии создается None (потому что мы take из нее).

Вы, вероятно, захотите решить, что populate на самом деле должно делать.

  • Следует ли изменить None? Почему возвращается значение Option<None>? Должен ли он возвращать старое значение next? (Зачем тогда возвращать Option<Node> вместо Option<Box<Node>>?)

Код:

fn populate_returning_old_val(next: &mut Option<Box<Node>>) -> Option<Node> {
  std::mem::replace(
    next,
    Some(Box::new(Node { value: true, next: None }))
  ).take().map(|boxed_node| *boxed_node)
}
person phimuemue    schedule 02.05.2020
comment
Настоящая проблема, над которой я работаю, - это обход и десериализация бинарных деревьев. Когда мне нужно пройти предварительный заказ и обход порядка, я столкнулся с такой проблемой, когда мне не удалось извлечь существующее значение. Итак, ответ - да, мне нужно взять узел из следующего (а не создавать копию). Возможно, пример не так хорош, потому что реальное решение слишком сложное и слишком много кода, я просто упростил его настолько, насколько мог, но потерял смысл почему, но вопрос на самом деле больше связан с тем, как. В любом случае благодарю за помощь. Спасибо! - person Arsenius; 03.05.2020
comment
Но возможно ли, что и node.next.is_some(), и result.is_some() верны? Примеры кода работают только для node.next.is_some() == true или result.is_some() == true, но никогда не подходят для обоих. Как это решить? - person Arsenius; 03.05.2020