Borrowing and set inside mut ref

Let we have the code:

struct State {
    i: isize
}

impl State {
    fn next(self) -> Self {
        Self {i: self.i-1}
    }
    
    fn next_mut(&mut self) {
        let junk = Self {i: 0xDEAD};
        *self = std::mem::replace(self, junk).next();
        //*self = self.next();
    }
}

I want to uncomment this line, but I cant move value from mutable reference. I write a workaround using special junk value, but what if I use type without "default" or "junk" values with heavy destructor?
What should I write in this case.

Also this code works, I don't know why:

fn main() {
    let mut s = State{i: 42};
    while s.i > 0 {
        s = s.next();
    }
}

Why not just have next mutate self through &mut self instead of returning a new value?

1 Like

There can be a situation when method .next is in external library, and we need to write method (or free function) next_mut.

The reason this fails is that, if next panics, then next_mut leaves self in an invalid state, which is not allowed.

It is ok in your main function because the compiler understands that it shouldn't run the destructor of s when the next call panics.

3 Likes

See this article for various workarounds.

replace_with is an alternative to the take_mut mentioned in the article.

1 Like

The big point to ban exceptions - we dont need to think about such situations.
But what happened, why did we return to this problem?

Did you read this section? The borrow checker certainly needs to think about such situations in order to uphold Rust's guarantees, and thus you need to think about it when it causes an error such as this.

You may say "but I know next never panics"; however, function calls are an API boundary in Rust -- the compiler isn't going to examine the body of the function to see if it might panic or not, and instead always treats it like it might. That way the implementor of next is free to change the function body code without breaking upstream. It would be too annoying if adding any potentially-panicking operation in next suddenly caused far-away code to break.

And incidentally, your example next could panic on underflow: arithmetic on primitive integers are potentially-panicking operations.


Rust doesn't have anything like noexcept, at least not yet. Crates like borrow_with fill that niche instead, by turning any panics into aborts or similar.

I interpreted @T4r4sB's post as lamenting that Rust has (recoverable unwinding) panics at all, since if it didn't then the compiler wouldn't need to ban this case of temporarily moving out from a &mut.

1 Like

Ah, could be. That's a whole other discussion.

No, banning recoverable panics is not enough, you need to also ban unwinding, i.e. running destructors of local variables when a panic happens. If you don't do that then you'll end up running the destructor of the value that was moved out of, which is invalid. Also, the destructor of any value that was holding a reference to the moved out of value may also access it.

This effectively means turning panics into aborts.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.