1

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:

  • I'm allowed to mutate the Option by reassigning None to it, breaking all following mutations. (Uncomment line 12 ((*handle) = None;) for example.)
  • It's messy: There are a whole lot of extraneous &mut's lying about.
  • It's doubly messy: Heaven only knows why I must use ref mut in the if let statement while the convention is to use &mut everywhere else.
  • It defeats the purpose of having the complicated borrow-checking and mutability checking rules in the compiler.

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?

4
  • 1
    "how can you use an immutable Option<>, by reference, that contains a mutable reference?" you can't as explain by the compiler "t is a & reference, so the data it refers to cannot be borrowed as mutable" Commented Feb 26, 2019 at 11:58
  • @Stargateur - .Line 12 is (*handle) = None; Commented Feb 26, 2019 at 12:41
  • @Stargateur - I'm not mutating the option, though, because it contains a reference and I am not changing the reference. After my mutation, the reference is still pointing to the same thing. I am mutating something internal to that thing. Commented Feb 26, 2019 at 12:42
  • @Stargateur - Furthermore, using RefCell would not achieve the pattern that I want because I could change the RefCell to contain a different Thing entirely -- essentially the same as reassigning the Option to none. I could rewrite Thing to use RefCell, internally, but that would mean that I have to restructure my entire library and incur the cost of runtime-borrow-checking, everywhere. Commented Feb 26, 2019 at 12:50

2 Answers 2

3

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);
}
Sign up to request clarification or add additional context in comments.

7 Comments

RefCell is there for deferring mutability checks to runtime. I want mutability checks at compile time and there should be no reason why that wouldn't be possible because I am not trying to mutate the Option<>. I am not event trying to change the options value because, after my mutations, the reference itself is not changed, only the interior state of the referenced object.
Furthermore, using RefCell would not achieve the pattern that I want because I could change the RefCell to contain a different Thing entirely -- essentially the same as reassigning the Option to none. I could rewrite Thing to use RefCell, internally, but that would mean that I have to restructure my entire library and incur the cost of runtime-borrow-checking, everywhere.
@Xharlie You could reassign the "different Thing entirely" through a &mut reference too.
In that case, I don't gain anything by using RefCell as opposed to using mutable Option's.
@Xharlie Correct. Mutable Options might be better because they encode exactly what is going on and let the type-checker do it's thing. I suggested interior mutability only because you specifically asked for a solution with an immutable outer type.
|
2

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>

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.