Devil
Devil

Reputation: 959

Copying a value in a pattern match without owning it

I am going through the too-many-linked-lists tutorial, looking to implement a simple linked list:

use std::mem;

struct Node{
    elem: i32,
    next : Link,
}

enum Link {
    Empty,
    More(Box<Node>),
}

pub struct List{
    head: Link,
}

impl List{
    pub fn pop_node(&mut self) -> Link{
        let node = mem::replace(&mut self.head, Link::Empty);
        match node {
            Link::More(nd) => {self.head= nd.next;}
            _ => ()
        };
        node
    }
}

The pop_node function is to return the node at the head of the list. However it does not seem to compile complaining that I moved the variable node while accessing nd. Is there a way I can pass the bound variable in a pattern match without owning it?

This is the error I see:

error[E0382]: use of partially moved value: `node`
  --> src/first.rs:34:9
   |
31 |             Link::More(nd) => {self.head= nd.next;}
   |                        -- value partially moved here
...
34 |         node
   |         ^^^^ value used here after partial move
   |
   = note: partial move occurs because value has type `Box<Node>`, which does not implement the `Copy` trait
help: borrow this field in the pattern to avoid moving `node.0`
   |
31 |             Link::More(ref nd) => {self.head= nd.next;}
   |                        +++

For more information about this error, try `rustc --explain E0382`.

Any idea what I should be doing here? (I tried things like unpacking the node struct but they didn't seem to work.)

Upvotes: 2

Views: 682

Answers (1)

cdhowie
cdhowie

Reputation: 169008

Usually when you're performing this kind of operation, you want the actual element (the i32 here), so maybe return Option<i32> instead -- None would indicate that the list was empty. Doing this is much simpler than what you're trying to do. Within the match you can just return nd.elem.

impl List{
    pub fn pop_node(&mut self) -> Option<i32> {
        let node = mem::replace(&mut self.head, Link::Empty);
        match node {
            Link::More(nd) => {
                self.head = nd.next;
                Some(nd.elem)
            }
            Link::Empty => None
        }
    }
}

Note this operation should be called shift_node (pop_node would be expected to remove the last node, not the first).

I'd also consider replacing your Link type with Option<Box<Node>>. Then you can use utilities already present on Option. For example, your mem::replace() call could be replaced with self.head.take() and then you're just mapping the result. You can keep the Link name by making it an alias (type Link = Option<Box<Node>>;).

use std::mem;

struct Node{
    elem: i32,
    next: Link,
}

type Link = Option<Box<Node>>;

pub struct List {
    head: Link,
}

impl List{
    pub fn pop_node(&mut self) -> Option<i32> {
        self.head.take().map(|nd| {
            self.head = nd.next;
            nd.elem
        })
    }
}

Upvotes: 3

Related Questions