Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upMatch Ergonomics Using Default Binding Modes #2005
Conversation
cramertj
force-pushed the
cramertj:match-ergo-take-2
branch
from
a87d489
to
5400aac
May 22, 2017
cramertj
changed the title
Match Ergonomics Take 2
Match Ergonomics Using Default Binding Modes
May 22, 2017
This comment has been minimized.
This comment has been minimized.
|
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 |
pnkfelix
added
the
T-lang
label
May 22, 2017
This comment has been minimized.
This comment has been minimized.
My assumption is the following:
That is, we will dereference any number of |
This comment has been minimized.
This comment has been minimized.
|
@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 comment has been minimized.
This comment has been minimized.
|
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. |
This comment has been minimized.
This comment has been minimized.
phaylon
commented
May 22, 2017
|
I'd like to make a case for allowing explicitly specifying |
This comment has been minimized.
This comment has been minimized.
|
@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; |
This comment has been minimized.
This comment has been minimized.
phaylon
commented
May 22, 2017
|
Ah, so as long as I don't leave off the |
This comment has been minimized.
This comment has been minimized.
|
@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. |
This comment has been minimized.
This comment has been minimized.
|
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 |
This comment has been minimized.
This comment has been minimized.
|
(FWIW, I still think |
This comment has been minimized.
This comment has been minimized.
|
Beyond pedagogy, I think I find |
This comment has been minimized.
This comment has been minimized.
|
@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 |
nrc
self-assigned this
May 22, 2017
rkruppe
reviewed
May 22, 2017
| 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`). |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
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 |
This comment has been minimized.
This comment has been minimized.
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; |
This comment has been minimized.
This comment has been minimized.
repax
commented
May 22, 2017
•
|
I suppose, and hope, that auto-dereferencing could work on user |
This comment has been minimized.
This comment has been minimized.
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. |
This comment has been minimized.
This comment has been minimized.
burdges
commented
May 23, 2017
•
|
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 |
This comment has been minimized.
This comment has been minimized.
|
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 ( |
This comment has been minimized.
This comment has been minimized.
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. |
This comment has been minimized.
This comment has been minimized.
|
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. |
This comment has been minimized.
This comment has been minimized.
|
@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 |
This comment has been minimized.
This comment has been minimized.
|
@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. |
This comment has been minimized.
This comment has been minimized.
|
When we first went through this RFC, I missed an interesting comment by @phaylon. The crux is that if you have an @cramertj @nrc @nikomatsakis I'm curious what you think about this use case. Can you see another way to achieve a similar result if we eventually fully deprecate |
This comment has been minimized.
This comment has been minimized.
|
Copying @phaylon's example just for easy reference: let Select {
ref selector,
ref stream,
ref mut builder,
ref mut state,
ref mut restrict,
} = *self;At worst, you could express it as: let Select {
selector,
stream,
builder,
state,
restrict,
} = self;
let (selector, stream) = (&*selector, &*stream);So you're "explicitly discarding" your privileges on |
This comment has been minimized.
This comment has been minimized.
|
Hmm, to be honest, I think that the use case of "intermingled" let pair: &mut (i32, i32) = &mut (0, 1);
let x = match *pair { (ref x, _) => x };
let p: &(i32, i32) = &*pair;while the following "erasure" does not: let pair: &mut (i32, i32) = &mut (0, 1);
let x = match pair { (x, _) => x };
let p: &(i32, i32) = &*pair;To do something equivalent, you would have to explicitly use a let pair: &mut (i32, i32) = &mut (0, 1);
let x = match &pair { (x, _) => x };
let p: &(i32, i32) = &*pair;I'm not sure this is bad, but it is not something I had fully realized before. It seems related to the reasons that it is useful to have |
This comment has been minimized.
This comment has been minimized.
|
One could imagine requiring the
Right now I think that |
This comment has been minimized.
This comment has been minimized.
|
@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". |
This comment has been minimized.
This comment has been minimized.
|
@joshtriplett what do you mean by "implicit modification"? You mean that |
This comment has been minimized.
This comment has been minimized.
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. |
This comment has been minimized.
This comment has been minimized.
|
I think @phaylon's example is why I was personally in favor of requiring |
This comment has been minimized.
This comment has been minimized.
|
@withoutboats I definitely wouldn't want |
This comment has been minimized.
This comment has been minimized.
It doesn't create one where there wasn't one previously-- if you write |
This comment has been minimized.
This comment has been minimized.
|
@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 |
This comment has been minimized.
This comment has been minimized.
|
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 |
This comment has been minimized.
This comment has been minimized.
burdges
commented
Aug 16, 2017
|
I think this RFC creates only the second place where There is a convention throughout most of Avoiding the |
petrochenkov
referenced this pull request
Sep 3, 2017
Merged
RFC: Clarify and streamline paths and visibility #2126
This comment has been minimized.
This comment has been minimized.
phaylon
commented
Sep 20, 2017
|
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 |
This comment has been minimized.
This comment has been minimized.
|
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 |
This comment has been minimized.
This comment has been minimized.
phaylon
commented
Sep 20, 2017
|
@glaebhoerl Yes, I would. Similar to if |
cramertj commentedMay 22, 2017
•
edited by mbrubeck
Rendered.
[edited to link to final rendered version]