`ref` and `ref mut` for pattern matching RefCell and Cell like Box

There is currently the unstable box syntax that can be used in, among other ways, to pattern match into T of a Box<T>. This is an extremely useful feature and I think could very well be extended (in the sense of how someone might use such a feature) to Cell's and RefCell's.

Proposal:

The ability to do something like the follow (I don’t know if this is unambiguous):

match value {
    some_enum::Var1(box ref other_enum::A(inner)) => {},
    some_enum::Var2(box ref mut other_enum::A(inner)) => {},
}

This would borrow() (in the case of box ref) and borrow_mut() (in the case of box ref mut) and then continue to pattern match the inner type like the box syntax does currently.

Problems:

  • It hides the borrow() and borrow_mut() calls. Making them less explicit and thus could still fail.
  • Adds more complexity to the compiler but there seems to have been enough wish for the box syntax and this solves a very similar problem.

Because RefCell::borrow and RefCell::borrow_mut can panic, I wouldn’t want this. Also Cell doesn’t support immutable references into it’s inner value, so that is also out.

The box syntax is different because Box is already magical, and box cannot fail.


on a less important note:

This would also make RefCell and Cell lang_items, which would be unfortunate. I think it is nice that they can be simple library items that anyone can implement.

1 Like

Nothing would preclude having an RFC for some kind of language feature that would allow this to be generalized through traits (maybe even with TryInto). I could see something along the lines of the following being very useful:

match value {
    some_enum::Var1(auto other_enum::A(inner)) => {},
    some_enum::Var2(auto mut other_enum::A(inner)) => {},
}

Yes, but there are more fundamental issues with this proposal, and as I said, that was the least important point. For example, hiding RefCell::borrow also hides a panic point inside of a match statement. I would never expect a match statement to panic. This would make debugging a nightmare.

1 Like

I don’t think it would make debugging a nightmare because in debug mode you are told which are panicked.

Sebastian Malton

Although I agree, there's precedent in the language for hard-to-spot panic code. Namely, slice indexing with square brackets.

Just because there is precedent, doesn’t mean that we need to add more. I would like to minimize this as much as possible. Also, this is harder to spot than slice indexing, because slice indexing is quite a bit less noisy compared to arbitrary patterns. With patterns, you could accidentally borrow the same RefCell multiple times which could lead to confusing panics.

let a = RefCell::new(10);

// ... many lines later ...

let b = (&a, &a); // something that borrows a twice, doesn't have to be a tuple

// ... many lines later ...

match b {
    (box ref c, box ref mut d) => {
        // this code is garuenteed to panic, because of overlapping borrows
        // What should the error message be? The stack trace?
    }
}

This is equivalent to

use std::cell::RefCell;

fn main() {
    let a = RefCell::new(0);
    
    let b = (&a, &a);
    
    let c = a.borrow();
    let d = a.borrow_mut();
}

But in this case the panic message can more easily point to what went wrong, because the line numbers are more useful.


Now, lets imagine a more complex pattern that is using this, with more references to the RefCell and it is easy to see how this can turn into a debugging nightmare.

1 Like

Would you be less against as solution like this if it utilized something similar to try_borrow() and try_borrow_mut()?

Edit: Your example from above reworked:

let a = RefCell::new(10);

// ... many lines later ...

let b = (&a, &a); // something that borrows a twice, doesn't have to be a tuple

// ... many lines later ...

match b {
    (box ref Some(c), box ref mut Some(d)) => {
        // This won't panic, this branch won't even be entered
    },
    (box ref Some(c), box ref mut None) => {
        // This branch would be entered though
    }
}
1 Like

That would be better, but at that point why not split it up and actually use try_borrow. This feels really magical.

1 Like

Well I would say that it is would be worth it to do this instead for several reasons:

  1. Easier to read: both because of less indenting/scoping and because it is more terse without leaving out any critical information
  2. It is easier to write: programming languages should help the programmer write both what he means and what is easy to understand and being able to simplify the code without any loss of understanding I feel would be of a great boon. I think that the try operator is an excellent example of this.
  3. Pattern matching is “magical”: it allows you to very elegantly describe what you want very concisely. I think that box already helps with this and cell-like structs are necessary for certain types of problems so I don’t see why we shouldn’t add in that area too.

And if it does mean something like try_ versions of the methods I don’t see why it shouldn’t be try ref and try ref mut.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.