7

I want to solve a leetcode question in Rust (Remove Nth Node From End of List). My solution uses two pointers to find the Node to remove:

#[derive(PartialEq, Eq, Debug)]
pub struct ListNode {
    pub val: i32,
    pub next: Option<Box<ListNode>>,
}

impl ListNode {
    #[inline]
    fn new(val: i32) -> Self {
        ListNode { next: None, val }
    }
}

// two-pointer sliding window
impl Solution {
    pub fn remove_nth_from_end(head: Option<Box<ListNode>>, n: i32) -> Option<Box<ListNode>> {
        let mut dummy_head = Some(Box::new(ListNode { val: 0, next: head }));
        let mut start = dummy_head.as_ref();
        let mut end = dummy_head.as_ref();
        for _ in 0..n {
            end = end.unwrap().next.as_ref();
        }
        while end.as_ref().unwrap().next.is_some() {
            end = end.unwrap().next.as_ref();
            start = start.unwrap().next.as_ref();
        }
        // TODO: fix the borrow problem
        // ERROR!
        // start.unwrap().next = start.unwrap().next.unwrap().next.take();
        dummy_head.unwrap().next
    }
}

I borrow two immutable references of the linked-list. After I find the target node to remove, I want to drop one and make the other mutable. Each of the following code examples leads to a compiler error:

// ERROR
drop(end); 
let next = start.as_mut().unwrap.next.take();

// ERROR
let mut node = *start.unwrap()

I don't know if this solution is possible to be written in Rust. If I can make an immutable reference mutable, how do I do it? If not, is there anyway to implement the same logic while making the borrow checker happy?

Peter Hall
  • 43,946
  • 11
  • 101
  • 168
Aylei
  • 188
  • 1
  • 7
  • 5
    Converting an immutable reference to a mutable one is pretty much never a good idea. You should borrow as mutable in the first place. – Bartek Banachewicz Jan 17 '19 at 14:21
  • 3
    Or else use interior mutability in your data structure, with something like `RefCell`. – Peter Hall Jan 17 '19 at 14:22
  • 3
    You might want to look at [Learning Rust with entirely too many linked lists](https://cglab.ca/~abeinges/blah/too-many-lists/book/) – Jmb Jan 17 '19 at 14:53
  • 3
    I don't think the downvotes are warranted. No, you can't do this without UB, but it's not an unreasonable question -- especially for a user coming from a language like C++ where `const`ness is really more of a *suggestion* than a *rule*. – trent Jan 17 '19 at 15:05
  • 4
    Translation: "Is it possible to shoot myself in the head?" – Jim Mischel Jan 17 '19 at 16:18
  • 1
    @trentcl even in C++ removing the const is UB https://en.cppreference.com/w/cpp/language/const_cast#Notes – hellow Jan 18 '19 at 07:54
  • @hellow From that page: "Modifying a const object through a non-const access path [...] results in undefined behavior." In C++ you can trigger UB by *modifying* a const object through a non-const pointer. In Rust you can trigger UB whether or not the referent is const, even *without* trying to modify it, because you can violate aliasing assumptions. This is not the case in C++ (unless perhaps you invoke C99-esque `restrict` pointers, but not the case of `const_cast`, anyway). – trent Jan 18 '19 at 12:27
  • Or, put another way: `const_cast` is valid to do *sometimes*. Casting `&T` to `&mut T` is valid *never*. – trent Jan 18 '19 at 12:30
  • Ah okay, I got that, sorry :) – hellow Jan 18 '19 at 12:56
  • Thanks all you guys (comments guideline told me not to comment for complimenting, but upvotes just cannot fully express my gratitude). I feel like I am dumber when I write Rust, but you guys show me why Rust was designed that way and the friendliness of the community. This inspired to learn more in Rust and thinking in the Rust way, then I can help someone like me someday ;) – Aylei Jan 18 '19 at 14:24

2 Answers2

11

Is there a way to make an immutable reference mutable?

No.

You could write unsafe Rust code to force the types to line up, but the code would actually be unsafe and lead to undefined behavior. You do not want this.


For your specific problem, see:

Shepmaster
  • 326,504
  • 69
  • 892
  • 1,159
  • 6
    The [Nomicon](https://doc.rust-lang.org/nomicon/transmutes.html) has a related passage which I appreciate very much: _"transmuting an & to &mut is UB; Transmuting an & to &mut is always UB; No you can't do it; No you're not special"_ – E_net4 - Krabbe mit Hüten Jan 17 '19 at 15:14
9

The correct answer is that you should not be doing this. This is undefined behavior, and breaks many assumptions made by the compiler when compiling your program.

However, it is possible to do this. Other people have also mentioned why this is not a good idea, but they haven't actually shown what the code to do something like this would look like. Even though you should not do this, this is what it would look like:

unsafe fn very_bad_function<T>(reference: &T) -> &mut T {
    let const_ptr = reference as *const T;
    let mut_ptr = const_ptr as *mut T;
    &mut *mut_ptr
}

Essentially, you convert a constant pointer into a mutable one, and then make the mutable pointer into a reference.

Here's one example why this is very unsafe and unpredictable:

fn main() {
    static THIS_IS_IMMUTABLE: i32 = 0;
    unsafe {
        let mut bad_reference = very_bad_function(&THIS_IS_IMMUTABLE);
        *bad_reference = 5;
    }
}

If you run this... you get a segfault. What happened? Essentially, you invalidated memory rules by trying to write to an area of memory that had been marked as immutable. Essentially, when you use a function like this, you break the trust the compiler has made with you to not mess with constant memory.

Which is why you should never use this, especially in a public API, because if someone passes an innocent immutable reference to your function, and your function mutates it, and the reference is to an area of memory not meant to be written to, you'll get a segfault.

In short: don't try to cheat the borrow checker. It's there for a reason.

EDIT: In addition to the reasons I just mentioned on why this is undefined behavior, another reason is breaking reference aliasing rules. That is, since you can have both a mutable and immutable reference to a variable at the same time with this, it causes loads of problems when you pass them in separately to the same function, which assumes the immutable and mutable references are unique. Read this page from the Rust docs for more information about this.

  • 2
    This answer is misleading. In Rust, unlike C/C++, it's unsound just to convert a `&` reference into a `&mut` reference even if the underlying object could be mutated. This is because `&mut` references are like `restrict`-qualified pointers in C, and casting a `T*` to a `T* restrict` can lead to undefined behavior as the compiler is allowed to optimize loads and stores based around the `restrict` assumption. – trent Jan 17 '19 at 18:38
  • 1
    In other words, the answer to [this question](https://stackoverflow.com/questions/9079104/is-it-undefined-behaviour-to-cast-away-the-constness-of-a-function-parameter) does not apply in Rust. More than guaranteeing the referent is mutable, you have to guarantee the `&T` is *unaliased*. – trent Jan 17 '19 at 18:44
  • 2
    This function is **undefined behavior**; there is **no** possible usage of it that is safe to use. [Try running it with Miri](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=10369463c3af5edd7f4d2bd3048ab8cc). There's a reason my answer did not provide the code that can never be safely used. – Shepmaster Jan 17 '19 at 18:54
  • This answer was meant to show why this undefined behavior, I wasn't trying to suggest this should ever be used. – ThatOneDeveloper Jan 17 '19 at 19:39
  • 3
    *show why this undefined behavior* — you cannot show that something is undefined behavior. By definition, UB means that anything can happen. Running that code is allowed to erase your hard drive or summon nasal demons. The fact that it happens to segfault is basically luck. – Shepmaster Jan 18 '19 at 00:47
  • 3
    The assumption "If you run this... you get a segfault" suggests that the compiler is actually bound to compile the code to something *meaningful, but incorrect given the values provided at runtime*. In fact, `very_bad_function` may not be compiled to anything meaningful at all, as the optimizer may make assumptions about reachability based on the non-aliasing of `&mut T`. Breaking aliasing rules is not an *extra* reason "in addition to" the problem of writing to read-only memory -- it's the *only* reason why this code has undefined behavior... – trent Jan 18 '19 at 01:37
  • ... since, in order to claim the code writes to read-only memory, you have to believe that it has a meaningful translation into machine code, which it does not. – trent Jan 18 '19 at 01:37