3

I want to create a simple linked list and add a value into it. How should the add method be implemented to make this code output 100 50 10 5 at line 42, the second root.print() call?

use std::rc::Rc;

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

impl Node {
    fn print(&self) {
        let mut current = self;
        loop {
            println!("{}", current.value);
            match current.next {
                Some(ref next) => {
                    current = &**next;
                }
                None => break,
            }
        }
    }

    fn add(&mut self, node: Node) {
        let item = Some(Box::new(node));
        let mut current = self;
        loop {
            match current.next {
                None => current.next = item,
                _ => {} 
                //Some(next) => { current = next; }
            }
        }
    }
}

fn main() {
    let leaf = Node {
        value: 10,
        next: None,
    };
    let branch = Node {
        value: 50,
        next: Some(Box::new(leaf)),
    };
    let mut root = Node {
        value: 100,
        next: Some(Box::new(branch)),
    };
    root.print();

    let new_leaf = Node {
        value: 5,
        next: None,
    };
    root.add(new_leaf);
    root.print();
}

(Playground)

I rewrote the function like this:

fn add(&mut self, node: Node) {
    let item = Some(Box::new(node));
    let mut current = self;
    loop {
        match current {
            &mut Node {
                     value: _,
                     next: None,
                 } => current.next = item,
            _ => {} 
            //Some(next) => { current = next; }
        }
    }
}

but the compiler says

error[E0382]: use of moved value: `item`
  --> <anon>:28:40
   |
28 |                 None => current.next = item,
   |                                        ^^^^ value moved here in previous iteration of loop
   |
   = note: move occurs because `item` has type `std::option::Option<std::boxed::Box<Node>>`, which does not implement the `Copy` trait

I don't understand why it says that item was previously moved if it's used only once, and how the Some(_) branch should be implemented to iterate through the list?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Alex Zhukovskiy
  • 9,565
  • 11
  • 75
  • 151
  • 3
    What makes you think that `kind` should be a reference (`&mut T`) instead of a plain value (`T`) ? – Matthieu M. May 25 '15 at 15:27
  • Because leaf becomes a branch after value is inserted. In code above leaf with `value` should be a branch after `newLeaf` added – Alex Zhukovskiy May 25 '15 at 15:28
  • @MatthieuM. I believe OP wants to specify that `kind` is mutable; but it is forbidden to simply write `kind: mut NodeKind`. The solution is to understand that mutability is inherited: nothing in the struct is `mut` *per se*, but a given instance *of the whole struct* is either `mut` or not. (I tried to find a good doc resource about that, but I couldn't find one?) – mdup May 25 '15 at 15:35
  • @AlexZhukovskiy: A reference is not necessary for this; I guess this is the source of your issue. – Matthieu M. May 25 '15 at 15:35
  • @mdup perhaps http://doc.rust-lang.org/stable/book/mutability.html#field-level-mutability ? – Shepmaster May 25 '15 at 15:38
  • @Shepmaster ok, I agree, see edit, I tried to explain as legibly as possible – Alex Zhukovskiy May 25 '15 at 15:38
  • @mdup: and then the next hurdle is that you cannot modify the content of a `Rc`, you need either a `Box` or `Rc>` – Matthieu M. May 25 '15 at 15:39
  • @Shepmaster perfect; for some reason it didn't show up while googling "rust mutability inheritance". – mdup May 25 '15 at 15:42
  • @mdup yeah, it doesn't say "inheritance" on that page :-) – Shepmaster May 25 '15 at 15:46
  • @mdup: if you feel like putting together an explanation (I'm working on something else right now), please feel free to use: https://play.rust-lang.org/?gist=420bf067ca0ca572bdbc&version=stable (the compiling version); I moved to `Rc>` and moved print to recursive. – Matthieu M. May 25 '15 at 15:46
  • Perhaps a duplicate of http://stackoverflow.com/q/27750985/155423 then? The answer explains how to create a singly-linked list. – Shepmaster May 25 '15 at 15:48
  • 2
    @Shepmaster: I would be reluctant to call it a duplicate; I would be surprised if Alex was not more interested in understanding why rather than getting some linked-list code. Here, it appears the issue is (first and foremost) an issue of inherited mutability and lifetime. I also noted extraneous "mut" sprinkled in main, so maybe deep down a misunderstanding of what requires mutability and what does not. That moving does not require mutability can be surprising too, I guess. – Matthieu M. May 25 '15 at 15:59
  • @MatthieuM. yeah, that's why I just added it as a comment, not a close vote (which I can't do anymore, it turns out — yay gold badge!). – Shepmaster May 25 '15 at 16:01
  • @MatthieuM. thanks for understanding, there is lack of info about Rust and those things, so only cut and try method is left and I'm grateful for any help. – Alex Zhukovskiy May 25 '15 at 16:13
  • 1
    @AlexZhukovskiy: no worries, we're all in the same boat :) – Matthieu M. May 25 '15 at 17:17
  • Just a small nitpick: You *prepend*, *append* or *insert* into a list. Addition is usually a commutative and associative operation. – llogiq May 26 '15 at 09:18
  • @llogiq append, of course. In most cases it's standard name for this method. `List.Add` in .Net or `ArrayList.Add` in Java, etc... – Alex Zhukovskiy May 26 '15 at 09:22
  • 1
    In the rust dictionary (see `std::collections::LinkedList`) it says you `push_back` an element but you can `append` one list to another.. – bluss May 26 '15 at 16:50
  • @bluss thanks, it sounds like something new to me, but i'l consider it the next time – Alex Zhukovskiy May 26 '15 at 17:50

1 Answers1

5

This is how you need to write it (playground link)

fn add(&mut self, node: Node) {
    let item = Some(Box::new(node));
    let mut current = self;
    loop {
        match moving(current).next {
            ref mut slot @ None => {
                *slot = item;
                return;
            }
            Some(ref mut next) => current = next,
        };
    }
}

Ok, so what is this?

Step 1, we need to return immediately after using the value item. Then the compiler correctly sees that it is only moved from once.

ref mut slot @ None => {
    *slot = item;
    return;
}

Step 2, to loop with a &mut pointer that we update along the way is tricky.

By default, Rust will reborrow a &mut that is dereferenced. It doesn't consume the reference, it just considers it borrowed, as long as the product of the borrow is still alive.

Obviously, this doesn't work very well here. We want a “hand off” from the old current to the new current. We can force the &mut pointer to obey move semantics instead.

We need this (the identity function forces move!):

match moving(current).next 

we can also write it like this:

let tmp = current;
match tmp.next

or this:

match {current}.next

Step 3, we have no current pointer after we looked up inside it, so adapt the code to that.

  • Use ref mut slot to get a hold on the location of the next value.
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
bluss
  • 12,472
  • 1
  • 49
  • 48
  • 1
    Well, I've checked that `Some(ref mut next) => current = next` can be used as well. First step is clear too, but I can't understand the second step with braces. Why does they change behaviour so much? – Alex Zhukovskiy May 26 '15 at 11:28
  • 1
    Oh good point! I'll update the answer with more explanation I think – bluss May 26 '15 at 11:32
  • Thanks, I got the point with Identity function - afaik function always "consume" an argument if it isn't passed with `&` borrowed reference symbol, so it's clear, but I still don't understand why temp variable or code block works. I'm surprised that code block is valid because of lifetimes and all this stuff (it creates a temp variable that lives in this block only, but it's finishing right in the same moment as variable is created). Sorry if I'm annoying or my grammar is bad - I'm working hard on it :) – Alex Zhukovskiy May 26 '15 at 12:41
  • 1
    The temporary variable forces a move, that feels natural. *Why* the block works is not clear, it's natural to move there, but I think it could also reborrow. Not sure there is a *why*. – bluss May 26 '15 at 12:50