-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Match Ergonomics Using Default Binding Modes #2005
Conversation
a87d489
to
5400aac
Compare
There are examples of nested patterns, but no examples with nested/multiple references. match &&&Some(0) {
Some(x)
}
=>
match &&&Some(0) {
&&&Some(ref x) // typeof(x) == &i32
}
or
match &&&Some(0) {
&&&Some(/* equivalent to */ ref ref ref x) // typeof(x) == &&&i32
} From some details in the text (e.g. "we don't know whether |
My assumption is the following:
That is, we will dereference any number of |
@petrochenkov If the compiler can determine that the value being matched is a reference, and it is being matched by a non-reference pattern, it will dereference it and shift the default binding mode. This process should repeat until there are no references left, or the type is successfully matched. Note that this does not mean that it will pile up Example: match &&&Some(0) {
Some(x) => {},
None => {},
}
// will desugar to
match &&&Some(0) {
&&&Some(ref x) => {},
&&&None => {},
} Similarly, I would expect a pattern with too few references to also compile: // If the user writes this `match`:
match &&&Some(0) {
&Some(ref x) => {},
&None => {},
}
// the compiler sees this:
match &(&(&(Some(0)))) {
&(Some(ref x)) => {},
&(None) => {},
}
// and will desugar to
match &&&Some(0) {
&(&&Some(ref x)) => {},
&(&&None) => {},
} When it sees the first |
This reads great! I think there is one sort of "ambiguity" in the RFC text, and the examples don't cover it. In particular, what sort of types are "auto-dereferenceable"? I see two interesting examples; one where the auto-deref occurs at the outermost level, and another with nested cases. // EXAMPLE A -- deref at outermost level
//
// This could be made to work relatively easily, I think.
// If this works, I would expect `y` to be
// a `ref` binding.
let x: Rc<i32>;
match &x {
Some(y) => ...,
None => ...,
}
// More explicit form:
match &*x {
&Some(ref y) => ...,
&None => ...,
} and: // EXAMPLE B -- deref within
//
// I do not expect this to work.
let x: Option<Rc<Option<i32>>>;
match x {
Some(Some(y)) => ...,
None => ...,
} My expectation is that we could support the first, but trying to support the second will be controversial and challenging. For one thing, it would require executing user-defined code (the I think I'd be in favor of supporting the first but not the second, but it does introduce a sort of asymmetry. |
I'd like to make a case for allowing explicitly specifying |
@phaylon For copying inner struct Foo<T>(T);
let foo = Foo(10);
let foo_ref = &foo;
let &Foo(foo_inner_copy) = foo_ref; // Copies `10`
// or
let Foo(foo_inner_copy) = *foo_ref; |
Ah, so as long as I don't leave off the |
@phaylon Correct, and that's an important point-- this RFC doesn't change the behavior of any existing code. It only allows for code that would not have compiled previously. |
I think that @withoutboats in the past specifically wanted to include fn main() {
match &Some(3) {
Some(move v) => // v is copied out
None => ...,
}
} I think that I personally favor allowing |
(FWIW, I still think |
Beyond pedagogy, I think I find |
@nikomatsakis I left a note about this at the very end of the RFC under alternatives. Personally, I think that I might be amenable to a different keyword. None of the entries on the existing keyword list seem good to me. In a Rust 2.0/epoch world where we can add new keywords, I think |
text/0000-match-ergonomics.md
Outdated
match &Some(3) { | ||
p => { | ||
// `p` is a variable binding. Hence, this is **not** a ref-defaulting | ||
// match, and `p` is bound with `move` semantics (and has type `&i32`). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this say "and has type Option<&i32>
"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it should.
I think by "only works for I agree to some uncertainty. I think the argument in favor of it is that I would rather teach people about |
That seems reasonable, but I probably wouldn't do either-- I'd just have them dereference the value: struct Foo<T>(T);
let foo_ref = &Foo(10);
let Foo(inner) = foo_ref; // This gives an `&i32`, they want an `i32`.
// In simple, non-nested cases, just do this:
let Foo(inner) = *foo_ref;
// In complex, nested cases, this will always work:
let Foo(inner_ref) = foo_ref;
let inner = *inner_ref; |
I suppose, and hope, that auto-dereferencing could work on user |
This is an interesting question. I think the answer is that we should work with
In the previous RFC I proposed this should follow the rules for deref coercions and only deref if the discriminant is already borrowed. Some people felt we could follow the dot operator and deref all types. |
I'm slightly nervous about the new mutable reference behavior, but it's what I do think any book that covers style should explain that, due to this convenience, functions that return mutable references, and data structures that contain mutable references, should indicate this fact with their suffix, like |
I'm working on gathering my thoughts with respect to the custom The following code seams reasonable: let mut x = Box::new(Some(5));
match x {
// The `DerefMut` impl of `Box` is used to get `&mut Option<i32>`,
// which is then matched against `Some(5)`
Some(5) => {},
// Here, the `DerefMut` impl of `Box` is used to get `&mut Option<i32>`
Some(y) => {
*y = 6;
},
// The `DerefMut` impl of `Box` is used to get `&mut Option<i32>`,
// which is then matched against `None`
None => {},
} But what should happen in the case when let x = Box::new(Some(5)); // no `mut`
match x {
// What happens here? We would use the `DerefMut` impl, but `x` is immutable.
// So instead we could infer to use `Deref` here based on the immutability of `x`.
Some(5) => {},
// Here, we again see that `x` is immutable, and so use the `Deref` impl of `Box`
// to get `&Option<i32>`
Some(_) => {},
// The `Deref` impl of `Box` is used to get `&Option<i32>`,
// which is then matched against `None`
None => {},
} This obviously isn't an insurmountable issue, I just thought it was interesting that we would potentially have to dispatch to different methods ( |
That's an option, but it does lead to longer than necessary borrows, which in the general case can be a problem (although with NLL, less so). I sort of prefer to move the deref into the pattern match myself. |
Hmm, the let mut x = Box::new(Some(5));
match x {
Some(y) => ...
None => ...
} I do not expect let mut x = Box::new(Some(5));
match &mut x {
Some(y) => ... // y: &mut i32
None => ...
}
Basically I think we would pick based on the default binding mode, more or less. |
@nikomatsakis Oh, I see. I was thinking that you were suggesting that Your exact question was:
But I had mentally linked "auto-dereference" with "auto-dereference and shift the binding mode." If we only auto-dereference custom types without shifting the default binding mode, then yes, we can choose |
@cramertj so, thinking about it more, I think we could leave the "autoderef" questions for another RFC. I'd really like to see the "core" ideas land. If we leave that out, do you feel like there are major unresolved questions for this RFC? I feel like it's ready to go to FCP, myself. |
One could imagine requiring the
Right now I think that |
@nikomatsakis The implicit modification of the original pair still seems very confusing to me. Having "mut" there says "this is modifiable", not "touching this will modify something else". |
@joshtriplett what do you mean by "implicit modification"? You mean that |
This is an accurate summary of my concern, yes. When I see something like I have moderate concerns about making it harder to tell when something is referencing versus copying. I have much bigger concerns about making it harder to tell when something is mutably referencing. |
I think @phaylon's example is why I was personally in favor of requiring |
@withoutboats I definitely wouldn't want |
It doesn't create one where there wasn't one previously-- if you write |
@joshtriplett I think your concerns were already expressed in the RFC thread above. rather than re-open general concerns about the RFC I'd like it if we could focus on the specific concerns @aturon bumped the thread about (originally raised by @phaylon) about splitting an object into mutable and immutable parts. Is this something we want to support easily under this RFC? What are the ways to do that? To me it seems like "requiring |
IMO requiring let x = &mut 5; // `x` is `&mut`
let (x, y, z) = (&mut 1, &mut 2, &mut 3); // `x, y, z` are all `&mut`
for i in &mut vec![1, 2, 3] { ... } // `i` is `&mut` It's probably worth discussing if we want to interpret the above examples differently in a new epoch/version, but for the time being, I think it's better to stay consistent. I personally don't think the mixed |
I think this RFC creates only the second place where There is a convention throughout most of Avoiding the |
I believe the ability to bind parts of The big reduction in ergonomics for me without this feature would be that instead of a pattern arm like |
I can recognize the value there; at the same time, I also feel inclined to ask: if the language had started out with "default binding modes", and without the ability to express that, would we consider it worthwhile to add |
@glaebhoerl Yes, I would. Similar to if |
Rendered.
[edited to link to final rendered version]