2
votes

I'm quite newbie in the Rust world and still not fully understand how ownership/borrowing/lifetime works. I have this example to demonstrate a struggle:

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

I don't understand why move this way works:

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() */)
}

But another way doesn't:

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())
 }

How to proper transfer ownership (like in example above) without copying but with borrowing mutating next reference (and not adding extra parameters)? I'm still not clear with this part...

2

2 Answers

2
votes

If you want populate to return a reference to the new Node placed inside next, the reference needs to be part of the return type. You can't move (transfer ownership of) the node into next while also returning it; that's not how ownership works:

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

You might try to return Some(&mut *next.unwrap()), but that won't work because unwrap takes self by value. Fortunately, there's a convenient function on Option that will take you straight from &mut Option<Box<Node>> to Option<&mut Node>, as_deref_mut:

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()
}

Also read

2
votes
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() */)
}

Massaging the code as the compiler suggests may lead to something you wrote. Now, taking it, introducing intermediate variables and annotating types (to see what's going on) gives this:

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; fails, because next_as_ref_unwrap is a borrowed Box<Node>, i.e. a &Box<Node>. Dereferencing (i.e. *) next_as_ref_unwrap tries to move, which cannot be done from a borrowed variable.

The problem is that you have next, which contains (essentially) a Node, however, you want to return a Node. This poses the question: Do you want to return another (i.e. new Node), or do you want to extract (i.e. take) the Node from next and return it. In case you want to take and return it:

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

The above code compiles, but is - at least - dubious, as it accepts a next that is essentially used as a local variable and made None afterwards (because we take from it).

You probably want to decide what populate actually should do.

  • Should it modify None? Why the return value Option<None>? Should it return next's old value? (Why return Option<Node> instead of Option<Box<Node>> then?)

Code:

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