0

I came across a leetcode example https://leetcode.com/problems/rotate-list/ and wanted to implement it in Rust but I keep moving out of my variables. For instance if I use head to track down the end of the list I cannot move its value to the last next because it is already moved out of the variable. How should I think to avoid this kind of problems?

And this is my attempt in rust

// Definition for singly-linked list.
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct ListNode {
    pub val: i32,
    pub next: Option<Box<ListNode>>,
}

impl ListNode {
    #[inline]
    fn new(val: i32) -> Self {
        ListNode { next: None, val }
    }
}
pub fn rotate_right(head: Option<Box<ListNode>>, k: i32) -> Option<Box<ListNode>> {
    if k == 0 {
        return head;
    }
    let mut len = 0;
    let mut curr = &head;
    while let Some(n) = curr {
        curr = &n.next;
        len += 1;
    }
    drop(curr);
    let newk = len - ( k % len);
    println!("Rotate {} right is {} mod {}",k,newk,len);

    let mut node = head.as_ref();
    for _i in 0..newk {
        if let Some(n) = node {
            node = n.next.as_ref();
        }else {
            println!("Unexpected asswer fail");
        }
    }

    let mut newhead = None ;// node.unwrap().next; // node will be last and newhead will be first.
    if let Some(mut n) = node{        // node should have a value here
        newhead = n.next;
        n.next = None
    }
    drop(node);

    let node = newhead;
    if let Some(c) = node{
        let mut curr = c;
        while let Some(next) = curr.next {
            curr = next;
        }
        curr.next = head;
        newhead
    }else{
        println! ("Todo handle this corner case");
        None
    }
}

But this yields errors about moving and use of borrowed references.

 error[E0507]: cannot move out of `n.next` which is behind a shared reference
  --> src/main.rs:99:23
   |
99 |             newhead = n.next;
   |                       ^^^^^^
   |                       |
   |                       move occurs because `n.next` has type `Option<Box<ListNode>>`, which does not implement the `Copy` trait
   |                       help: consider borrowing the `Option`'s content: `n.next.as_ref()`

error[E0594]: cannot assign to `n.next` which is behind a `&` reference
   --> src/main.rs:100:13
    |
98  |         if let Some(mut n) = node{        // node should have a value here
    |                     ----- help: consider changing this to be a mutable reference: `&mut Box<ListNode>`
99  |             newhead = n.next;
100 |             n.next = None
    |             ^^^^^^ `n` is a `&` reference, so the data it refers to cannot be written

error[E0382]: use of moved value: `newhead`
   --> src/main.rs:111:13
    |
97  |         let mut newhead = None ;// node.unwrap().next; // node will be last and newhead will be first.
    |             ----------- move occurs because `newhead` has type `Option<Box<ListNode>>`, which does not implement the `Copy` trait
...
104 |         let node = newhead;
    |                    ------- value moved here
...
111 |             newhead
    |             ^^^^^^^ value used here after move

Finlay this is reference code in c of what I intended it to look like:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */


struct ListNode* rotateRight(struct ListNode* head, int k){
    struct ListNode* node = head;

    int len = 1;
    if (k == 0 || !head){
        return head;
    }
    while (node->next){
        node = node->next;
        len++;
    }

    int newk = len - (k % len) -1;

    node = head;
    while (newk--){
        if (node->next) {
            node = node->next;
        }
    }
    if ( !node->next) {
        return head; // do not move anything
    }
    struct ListNode* newhead = node->next; // node will be last and newhead will be first.
    struct ListNode* n = node->next;
    node->next = 0;
    node = n;
    while (node->next != 0) {
        node = node->next;
    }
    node->next = head;
    return newhead;
}

So my question is what technique can be used to avoid this move use trap?

11
  • 1
    Please add the definition of ListNode to your code snippet. Commented Oct 8, 2020 at 9:44
  • 4
    A mandatory reading here is Learning Rust With Entirely Too Many Linked Lists. Commented Oct 8, 2020 at 9:45
  • 1
    @Simson Uh, no, it absolutely isn’t. It’s an excellent resource for implementing linked lists in Rust. Commented Oct 8, 2020 at 9:52
  • 2
    @Simson no it's not, it's an entire beginner-intermediate level book on how to properly and efficiently represent Linked Lists in Rust without losing your mind. The writer has opinions (that are, IMO, largely correct) about Linked Lists. Don't confuse the landing page with the content of the site. The table of contents is on the left. Commented Oct 8, 2020 at 9:52
  • 1
    I'm kind of feelings torn about answering the question, because the answer effectively ends up being "don't do this this way at all", and gets into caveats about Rust Linked List minutiae that I could type out but would ultimately just be repeating the book in a worse way. Commented Oct 8, 2020 at 10:14

1 Answer 1

4

I'd like to preface this post by saying that this type of Linked List in Rust is not a particularly good representation. Unfortunately, safe Rust's ownership model makes reference chain structures such as Linked Lists problematic and hard to reason about for any more than simple operations, despite Linked Lists being very simple in other languages. While for beginners, simple but inefficient implementations of simple concepts are often important and useful, I would also like to emphasize that the safe Rust "sentinel model" for trees and linked lists also has a heavy performance cost. Learning Rust With Entirely Too Many Linked Lists is the de facto book for learning how to properly deal with these structures, and I recommend it to all beginners and intermediate Rust programmers who haven't worked through it (or at least skimmed it).

However, this particular operation is possible on sentinel model singly linked lists in purely safe Rust. The whole version of the code I'm about to present is available on the Playground, which includes some things like test cases and an iterator implementation I'll be glossing over (since it doesn't get to the core of the problem and is only used in test cases and to derive the entire size of the Linked List). I also won't be presenting the ListNode struct or the new function, since they are in the question itself. Note that I also took some design liberties (instead of a free function, I made rotate_right and rotate_left member functions, you can freely convert but this is more Rusty design).

The "obvious' implementation here is to drain the entire list and reconstruct it. This is a perfectly good solution! However it's likely inefficient (it can be more or less inefficient depending on whether you totally destroy the nodes or not). Instead we're going to be doing what you largely do in other languages: only breaking the link and then reassigning the pointers at the boundaries. In fact, once you break it down into these two steps, the problem becomes much easier! All that's required is knowledge about Rust's mutable borrowing semantics, and how to overcome the pesky requirement that references always have a definite value and cannot be moved out of.


1. Breaking the Link: mutable reference uniqueness & memory integrity

/// Splits the list at a given index, returning the new split off tail.
/// Self remains unmodified.
/// The list is zero indexed, so,
/// for instance, a value of 2 in the list [1,2,3,4,5]
/// will cause self to be [1,2,3] and the return value to be
/// Some([4,5]). I.E. the link is "broken" AT index 2.
///
/// If there's nothing to split off, `None` is returned.
fn split_at(&mut self, n: usize) -> Option<Self> {
    use std::mem;

    let mut split = Some(self);
    for _ in 0..n {
        if let Some(node) = split.map(|v| v.next.as_deref_mut()) {
            split = node;
        } else {
            return None;
        }
    }

    match split {
        Some(node) => {
            let mut new_head = None;
            mem::swap(&mut new_head, &mut node.next);
            new_head.map(|v| *v)
        }
        None => None
    }
}

The logic is straightforward (if you've broken off a list in any other language it's the exact same algorithm). The important thing for Rust is the ownership system and the mem::swap function. Rust ensures two things that are problematic here:

  1. You're not mutably borrowing the same thing twice.
  2. No piece of memory is invalid, even temporarily (particularly in self).

For 1., we use the code

if let Some(node) = split.map(|v| v.next.as_deref_mut()) {
    split = node;
}

What this does is simply advances the pointer, making sure to immediately "forget" our current mutable pointer, and never do anything that directly causes "self" to be used at the same time as "node". (Note that if you tried this in an earlier version of Rust, I don't believe this was possible before Non-Lexical Lifetimes (NLLs), I think it would both alias self and node under the old rules). The map is simply to allow us to reassign to split by "reborrowing" the inner Box as an actual reference, so we can recycle the seed variable we start as self (since you can't reassign self in this way).

For solving 2. we use:

match split {
    Some(node) => {
        let mut new_head = None;
        mem::swap(&mut new_head, &mut node.next);
        new_head.map(|v| *v)
    }
    None => None
}

The key here is the mem::swap line. Without this Rust will complain about you moving node.next. Safe Rust requires you to ensure there is always a value in every variable. The only exception is if you're permanently destroying a value, which doesn't work on references. I think there's some QOL work on the compiler at the moment to allow you to move out of a mutable reference if you immediately put something in its place and the compiler can prove there will be no panic or return between these operations (i.e. memory cannot become corrupted during a stack unwind), but as of Rust 1.47 this is not available.

2. Stitching the list back together: memory integrity

/// Attaches the linked list `new_tail` to the end
/// of `self`. For instance
/// `[1,2,3]` and `[4,5]` will make self a list containing
/// `[1,2,3,4,5]`.
fn extend_from_list(&mut self, new_tail: ListNode) {
    let mut tail = self;
    while tail.next.is_some() {
        tail = tail.next.as_deref_mut().unwrap();
    }

    tail.next = Some(Box::new(new_tail))
}

This method isn't too complicated, it just assigns the next reference to the head node of the passed in list. If you've written a function to attach a new node it's functionally the same. Again, the key is that we advance the pointer to the end, being very careful we never allow a mutable reference to alias, which allows us to appease the Borrow Checker.

From there, we can finish up!

/// Rotates the list `k` elements left. So
/// `[1,2,3]` rotated 2 left is `[3,1,2]`.
///
/// The function handles rotating an arbitrary number of steps
/// (i.e. rotating a size 3 list by 5 is the same as rotating by 2).
fn rotate_left(&mut self, k: usize) {
    use std::mem;

    let k = k % self.iter().count();

    if k == 0 {
        return;
    }
    let k = k-1;

    if let Some(mut new_head) = self.split_at(k) {
        mem::swap(self, &mut new_head);
        let new_tail = new_head;
        self.extend_from_list(new_tail);
    } else {
        unreachable!("We should handle this with the mod!")
    }
}

/// Rotates the list `k` elements right. So
/// `[1,2,3]` rotated 2 right is `[2,3,1]`.
///
/// The function handles rotating an arbitrary number of steps
/// (i.e. rotating a size 3 list by 5 is the same as rotating by 2).
fn rotate_right(&mut self, k: usize) {
    self.rotate_left(self.iter().count() - k)
}

Rotating left is conceptually easiest, since it's just splitting a list at the given index (k-1 because to rotate k places we need to break the link after counting one fewer indices). Again, we need to make use of mem::swap, the only tricky thing about this code, because the part we split off is the new head and Rust won't let us use temp variables due to the memory integrity guarantee.

Also, it turns out rotating right k places is just rotating left size-k places, and is much easier to conceptualize that way.

This is all somewhat inefficient though because we're marching to the end of the list each time (particularly for the list fusion), because we're not holding onto the tail pointer and instead re-iterating. I'm fairly certain you can fix this, but it may be easier to make it one large function instead of sub-functions, since returning a downstream tail pointer would require some lifetime flagging. I think if you want to take a next step (besides the Linked List book, which is probably a better use of your time), it would be good to see if you can preserve the tail pointer to eliminate the constant marching down the list.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.