Xharlie
Xharlie

Reputation: 2540

How can you use an immutable Option by reference that contains a mutable reference?

Here's a Thing:

struct Thing(i32);

impl Thing {
    pub fn increment_self(&mut self) {
        self.0 += 1;
        println!("incremented: {}", self.0);
    }
}

And here's a function that tries to mutate a Thing and returns either true or false, depending on if a Thing is available:

fn try_increment(handle: Option<&mut Thing>) -> bool {
    if let Some(t) = handle {
        t.increment_self();
        true
    } else {
        println!("warning: increment failed");
        false
    }
}

Here's a sample of usage:

fn main() {
    try_increment(None);

    let mut thing = Thing(0);
    try_increment(Some(&mut thing));
    try_increment(Some(&mut thing));

    try_increment(None);
}

As written, above, it works just fine (link to Rust playground). Output below:

warning: increment failed
incremented: 1
incremented: 2
warning: increment failed

The problem arises when I want to write a function that mutates the Thing twice. For example, the following does not work:

fn try_increment_twice(handle: Option<&mut Thing>) {
    try_increment(handle);
    try_increment(handle);
}

fn main() {
    try_increment_twice(None);

    let mut thing = Thing(0);
    try_increment_twice(Some(&mut thing));

    try_increment_twice(None);
}

The error makes perfect sense. The first call to try_increment(handle) gives ownership of handle away and so the second call is illegal. As is often the case, the Rust compiler yields a sensible error message:

   |
24 |     try_increment(handle);
   |                   ------ value moved here
25 |     try_increment(handle);
   |                   ^^^^^^ value used here after move
   |

In an attempt to solve this, I thought it would make sense to pass handle by reference. It should be an immutable reference, mind, because I don't want try_increment to be able to change handle itself (assigning None to it, for example) only to be able to call mutations on its value.

My problem is that I couldn't figure out how to do this.

Here is the closest working version that I could get:

struct Thing(i32);

impl Thing {
    pub fn increment_self(&mut self) {
        self.0 += 1;
        println!("incremented: {}", self.0);
    }
}

fn try_increment(handle: &mut Option<&mut Thing>) -> bool {
    // PROBLEM: this line is allowed!
    // (*handle) = None;

    if let Some(ref mut t) = handle {
        t.increment_self();
        true
    } else {
        println!("warning: increment failed");
        false
    }
}

fn try_increment_twice(mut handle: Option<&mut Thing>) {
    try_increment(&mut handle);
    try_increment(&mut handle);
}

fn main() {
    try_increment_twice(None);

    let mut thing = Thing(0);
    try_increment_twice(Some(&mut thing));

    try_increment_twice(None);
}

This code runs, as expected, but the Option is now passed about by mutable reference and that is not what I want:

Is there any way to actually achieve what I want: passing an immutable Option around, by reference, and actually being able to use its contents?

Upvotes: 1

Views: 1271

Answers (2)

Xharlie
Xharlie

Reputation: 2540

TL;DR: The answer is No, I can't.

After the discussions with @Peter Hall and @Stargateur, I have come to understand why I need to use &mut Option<&mut Thing> everywhere. RefCell<> would also be a feasible work-around but it is no neater and does not really achieve the pattern I was originally seeking to implement.

The problem is this: if one were allowed to mutate the object for which one has only an immutable reference to an Option<&mut T> one could use this power to break the borrowing rules entirely. Concretely, you could, essentially, have many mutable references to the same object because you could have many such immutable references.

I knew there was only one mutable reference to the Thing (owned by the Option<>) but, as soon as I started taking references to the Option<>, the compiler no longer knew that there weren't many of those.

The best version of the pattern is as follows:

fn try_increment(handle: &mut Option<&mut Thing>) -> bool {
    if let Some(ref mut t) = handle {
        t.increment_self();
        true
    }
    else {
        println!("warning: increment failed");
        false
    }
}

fn try_increment_twice(mut handle: Option<&mut Thing>) {
    try_increment(&mut handle);
    try_increment(&mut handle);
}

fn main() {
    try_increment_twice(None);

    let mut thing = Thing(0);
    try_increment_twice(Some(&mut thing));

    try_increment_twice(None);
}

Notes:

  1. The Option<> holds the only extant mutable reference to the Thing
  2. try_increment_twice() takes ownership of the Option<>
  3. try_increment() must take the Option<> as &mut so that the compiler knows that it has the only mutable reference to the Option<>, during the call
  4. If the compiler knows that try_increment() has the only mutable reference to the Option<> which holds the unique mutable reference to the Thing, the compiler knows that the borrow rules have not been violated.

Another Experiment

The problem of the mutability of Option<> remains because one can call take() et al. on a mutable Option<>, breaking everything following.

To implement the pattern that I wanted, I need something that is like an Option<> but, even if it is mutable, it cannot be mutated. Something like this:

struct Handle<'a> {
    value: Option<&'a mut Thing>,
}

impl<'a> Handle<'a> {
    fn new(value: &'a mut Thing) -> Self {
        Self {
            value: Some(value),
        }
    }

    fn empty() -> Self {
        Self {
            value: None,
        }
    }

    fn try_mutate<T, F: Fn(&mut Thing) -> T>(&mut self, mutation: F) -> Option<T> {
        if let Some(ref mut v) = self.value {
            Some(mutation(v))
        }
        else {
            None
        }
    }
}

Now, I thought, I can pass around &mut Handle's all day long and know that someone who has a Handle can only mutate its contents, not the handle itself. (See Playground)

Unfortunately, even this gains nothing because, if you have a mutable reference, you can always reassign it with the dereferencing operator:

fn try_increment(handle: &mut Handle) -> bool {
    if let Some(_) = handle.try_mutate(|t| { t.increment_self() }) {
        // This breaks future calls:
        (*handle) = Handle::empty();

        true
    }
    else {
        println!("warning: increment failed");
        false
    }
}

Which is all fine and well.

Bottom line conclusion: just use &mut Option<&mut T>

Upvotes: 2

Peter Hall
Peter Hall

Reputation: 58835

You can't extract a mutable reference from an immutable one, even a reference to its internals. That's kind of the point! Multiple aliases of immutable references are allowed so, if Rust allowed you to do that, you could have a situation where two pieces of code are able to mutate the same data at the same time.

Rust provides several escape hatches for interior mutability, for example the RefCell:

use std::cell::RefCell;

fn try_increment(handle: &Option<RefCell<Thing>>) -> bool {
    if let Some(t) = handle {
        t.borrow_mut().increment_self();
        true
    } else {
        println!("warning: increment failed");
        false
    }
}

fn try_increment_twice(handle: Option<RefCell<Thing>>) {
    try_increment(&handle);
    try_increment(&handle);
}

fn main() {
    let mut thing = RefCell::new(Thing(0));
    try_increment_twice(Some(thing));
    try_increment_twice(None);
}

Upvotes: 3

Related Questions