Tracking issue for `box_patterns` feature
aturon opened this issue Β· 44 comments
Will it be possible to use box patterns with smart pointers?
Odd question: I've seen a few fn method(self: Box<Self>)
type things in libstd; is there any plan to eventually turn this into fn method(box self)
?
Types will be written Box<Foo>
and not box Foo
even if box_patterns is stabilized. Allowing box Foo
would be a new feature that is not planned I think. box
is useful to box values (shorter then Box::new(value)
and allows some pattern matching impossible before.
Oh, I understand that in general, box Foo
won't be a type. I was just wondering because I was under the assumption that &self
in method definitions was more of a pattern than a type description, which means that box self
in methods makes sense.
&self
in methods does not work like a pattern. For example in fn foo(x: &u32) { let &y = x }
the &y
pattern "removes" one pointer indirection and make y
have type u32
, whereas in a impl SomTrait for u32 { fn bar(&self) { β¦ }}
method self
has type &u32
with the &_
indirection included.
Are 'Box patterns' enabled, without flags, on Nightly yet?
@jadbox No. Unless someone missed something, this issue would be closed if they were.
rustc 1.18.0-nightly (2b4c911 2017-04-25)
error: box pattern syntax is experimental (see issue #29641)
--> a.rs:2:9
|
2 | let box a = Box::new(1);
| ^^^^^
|
= help: add #![feature(box_patterns)] to the crate attributes to enable
error: aborting due to previous error
thanks @SimonSapin
What's left to do on the feature before it can be considered 'stable' enough for nightly?
What's left to do on the feature before it can be considered 'stable' enough for nightly?
This is related to box syntax more generally, so the questions around that need to be resolved first.
This is really critical to matching recursive enums. I have some code like:
match act {
Subcondition(box Always(act)) => simplify(act),
Subcondition(box cond) => Subcondition(Box::new(simplify(cond))),
otherwise => otherwise
}
And basically, I'm stuck on nightly.
Is this box pattern syntax really super tied to the placement syntax in the other issues that are also using the box
keyword? The resemblance seems cosmetic (also, the tracking issue linked here doesn't mention the pattern syntax except in passing). Boxing recursive enums is a basic thing to do, and being able to deeply match those recursive enums seems like a pure win, regardless of how placement works.
Maybe changing the keyword from box
would help get this feature going again?
As discussed on the RFC for default binding modes in match
(tracking issue #42640), I think the best solution here would not be box
patterns, but simply to have the ref
"default binding mode" automatically propagate through all types with Deref
implementations (not just through &
itself), and likewise the ref mut
mode through all DerefMut
types (and not just &mut
).
So you could do things like:
let foo = Rc::new(Some(true)); // or Box, or Arc, or ...
match &foo {
Some(b) => { /* b is of type `&bool` here */ }
None => { ... }
}
There may or may not be complications around things like DerefPure
here, I can't remember. (If the complications don't implicate safety then I think we should just accept them with, "don't write surprising Deref
implementations or you'll get surprising results".)
Would it also be possible for &
in patterns to go through Deref
, to preserve the ability to write the "default binding mode" stuff explicitly? Or would it be better to keep box
as the explicit version, and let it be stabilized on its own?
I would prefer to deprecate &
and &mut
(and ref
and ref mut
) in pattern contexts and just do this kind of adjustment on the RHS when necessary (in expression context) - so in this case, just use *
to deref as normal - which may not always be as ideal as possible from a code-golf perspective, but is generally much clearer and less of a learning hazard.
I don't think binding modes are as expressive on their own, though. We would need something beyond the RFC in order to fully remove &
/&mut
, no?
Or did you mean forcing people to add *
in the match arm after the pattern? I feel like that's a step backwards and we don't have a clear path to avoiding that either.
There was some discussion about these things on the RFC, but I don't really remember it. In either case, this is getting into the weeds; I think the part that's important for now is just that the use case that was going to be satisfied by box
patterns should be satisfied by extending default binding modes instead. After that I would like to also be able to remove all the other things from patterns, if there aren't any major obstacles (e.g. strictly reduced expressivity), but we can attend to those details later in the process (e.g. I'm imagining the first thing would end up as one RFC / feature flag and the second as another, later on, or something).
@glaebhoerl So the best place to discuss this is default binding modes then? Is there much momentum there?
@deontologician Yes I'd think so (go ahead and drop a line there if the use case is important to you). Default binding modes itself (based on the tracking issue) is implemented but has a ways to go before stabilization; there hasn't been any "official" discussion about doing the thing I described, but a few people have seemed to agree that it seems like a promising direction to move in.
Yeah, looks like discussion of that part is picked up in rust-lang/rfcs#2099 which doesn't have an RFC yet. I'll move my focus there, thanks!
Bumping this since it's been 11 months and I'm also interested in this (don't have any specific use case, just interested)
As discussed on the RFC for default binding modes in
match
(tracking issue #42640), I think the best solution here would not bebox
patterns, but simply to have theref
"default binding mode" automatically propagate through all types withDeref
implementations (not just through&
itself), and likewise theref mut
mode through allDerefMut
types (and not just&mut
).So you could do things like:
let foo = Rc::new(Some(true)); // or Box, or Arc, or ... match &foo { Some(b) => { /* b is of type `&bool` here */ } None => { ... } }There may or may not be complications around things like
DerefPure
here, I can't remember. (If the complications don't implicate safety then I think we should just accept them with, "don't write surprisingDeref
implementations or you'll get surprising results".)
"Default binding mode" sounds incredibly good! Is there anything related to this that I can take a look at?
I was looking forward to box patterns, but now you've provided an alternative that sounds even better.
As discussed on the RFC for default binding modes in
match
(tracking issue #42640), I think the best solution here would not bebox
patterns, but simply to have theref
"default binding mode" automatically propagate through all types withDeref
implementations (not just through&
itself), and likewise theref mut
mode through allDerefMut
types (and not just&mut
).
So you could do things like:let foo = Rc::new(Some(true)); // or Box, or Arc, or ... match &foo { Some(b) => { /* b is of type `&bool` here */ } None => { ... } }There may or may not be complications around things like
DerefPure
here, I can't remember. (If the complications don't implicate safety then I think we should just accept them with, "don't write surprisingDeref
implementations or you'll get surprising results".)"Default binding mode" sounds incredibly good! Is there anything related to this that I can take a look at?
I was looking forward to box patterns, but now you've provided an alternative that sounds even better.
I use the box_patterns
feature quite extensively in here: https://github.com/Robbepop/stevia/blob/master/simplifier/src/simplifications/term_const_prop.rs#L37
Especially conditional move out of a structure guided by a pattern is done very commonly there.
As far as I understood the proposed DerefPure
would be used in that case behind the curtains of default binding modes, am I right about this? If not I would really like to know how this use case is covered.
@ice1000 There haven't been any developments on this front that I know of since then, if that's what you're thinking of.
@ice1000 There haven't been any developments on this front that I know of since then, if that's what you're thinking of.
Sad to hear that. Thanks for your information!
Another gentle bump to this issue β what's the current path towards stabilization? As far as I'm aware, this is still the only way to destructure across a Box
.
Yet another bump. Currently there's no way to destructure a box inside an enum variant in 1 step except by using this feature. Is there really no path towards stabilization?
Triage?
I understand the frustration, but at this point I think itβs pretty clear that this feature is not gonna be stabilized without another round of design work. This not a case of a feature thatβs basically done and we just need to press the button.
The last formal Language Team decision around this feature was RFC 469 in 2014, which made the feature unstable before Rust 1.0. (Or perhaps this was before there was a language team.) So the path forward if there is one likely involves a high-level proposal for how this feature should change (or why it shouldnβt), finding a member of the language team willing to champion this, and submitting a new RFC.
Regarding changes, the unstabilization RFC discusses extending the feature to types other than Box
. This may be more tricky than simply calling deref
: thereβs precedent for matching a pattern that refers to a const
being βstructuralβ (fields are matched recursively) rather than calling PartialEq::eq
. The trait is required to be derived, because it would be weird if matching and ==
gave different results.
#[derive(Eq)]
pub struct Foo(u32);
impl PartialEq for Foo {
fn eq(&self, other: &Self) -> bool {
(self.0 - other.0).abs() <= 3
}
}
pub fn foo(x: Foo) {
const FOUR: Foo = Foo(4);
if let FOUR = x {}
}
Playground, errors:
error: to use a constant of type `Foo` in a pattern, `Foo` must be annotated with `#[derive(PartialEq, Eq)]`
--> src/lib.rs:12:12
|
12 | if let FOUR = x {}
| ^^^^
Also consider that the language team has limited bandwidth for design work. How important is this particular feature compared to ongoing work? servo/servo@11bd81e is the commit that removed all uses of box patterns from Servo. The code becomes slightly less nice, but itβs not too bad IMO. This is not a case of being forced to rework the entire approach to a problem because of language limitations. Personally Iβd be happy to delay box patterns indefinitely if it means delaying slightly less any of the big items like specialization, const generics, or generic associated types.
Thank you for shedding some light on the status of this feature!
@SimonSapin Thanks for the info. I do think box patterns are quite important. I'm still relatively new to the language, but I've been working on a compiler on-and-off for the past 18 months. The most natural way to represent an AST in Rust is as a recursive enum, which means needing to box values. The IR I'm using happens to be a recursive enum as well. Multiple phases of the compiler need to pattern match on specific structures and without box patterns, this grossly complicates the code. Rather than seeing a nice succinct pattern, you see part of a match with a bunch of manual unboxing then the next part of the match and so on.
It's not that the problem isn't solvable without box patterns. It's that I'm not convinced doing it without box patterns is even worthwhile doing in Rust. And that might be fine. Rust doesn't need to be the solution to every problem domain, so maybe something like OCaml is the right answer here. But, I thought I'd share how important this feature would be to someone like me. For me, not having this feature means I really need to convince myself I want to use Rust because it adversely affects the legibility, and thus maintainability, of my code.
Having said that, I don't know enough at this point to try to advance the issue on my own. I'm hoping someone else can take up the mantle. I suspect if I tried to draft an RFC right now I'd end up overlooking a lot of the issues surrounding the feature that you mentioned.
One alternative to box patterns in your case might be to allocate nodes in an arena, enabling you to use references instead.
That gives you support for default binding modes, which are even more succinct than box patterns, and is likely to be a performance improvement as well.
(Leaving this here so others with the same problem can see it.)
Has there been any movement in this space since the last round of discussion, in particular perhaps about auto-deref for pattern matching instead of stabilising the box
keyword for pattern matching?
Been implementing an AST and tearing out my hair. π
I think people are looking at "default-binding mode" now.
Can this be used to do an "owning pointer", like fn A<T: !Sized>(box data: T)
? This could mean, that we recieving a boxed, unsized value, but working with it like it was usized local. Is it a good adition to DST?
As I was reading this, I have to tell that, this is really not the way of doing this.
For exampe, we add box keyword and what about the other types? Will we add rc keyword, and refcell keyword and others, and even custom_cell keyword? Of course not. I'm not proposing any solution, but I personally wouldn't want to see this feature in this keyword form. There must be a better way.
From what I understand, the box keyword works for rc and other types. It's basically the semantics of a deref-move, whatever that would mean.
As I was reading this, I have to tell that, this is really not the way of doing this.
For exampe, we add box keyword and what about the other types? Will we add rc keyword, and refcell keyword and others, and even custom_cell keyword? Of course not. I'm not proposing any solution, but I personally wouldn't want to see this feature in this keyword form. There must be a better way.
I guess it's #42640
From what I understand, the box keyword works for rc and other types. It's basically the semantics of a deref-move, whatever that would mean.
That's not the case currently, but there is a project group that is working to make something like this happen: https://github.com/rust-lang/project-deref-patterns
Marking Box patterns (in the literal sense of the box
keyword) as perma-unstable. If there's forward progress here, it'd be under the auspices of "deref patterns".
@deontologician, @jonhoo, @jcotton42, @ice1000, @Robbepop, @nirvdrum, @bbqsrc and everybody else interested!
I made proc macro library, which implements deref patterns right now, in stable Rust! π
https://crates.io/crates/match_deref
It will work for any type, which implements Deref, for example Box, Rc and String.
My library is not drop-in replacement for box patterns, because I call Deref::deref internally, and Deref::deref takes a reference and returns a reference. I. e. with my lib you can do this: match &Box::new(...) { Deref @ _ => ... }
, but you cannot do this: match Box::new(...) { Deref @ _ => ... }
. I. e. moving a value out of a box is not supported.
Also currently there is no support for DerefMut and RefCell, but they can be trivially added. It is possible that I will add support for them (check periodically my lib on crates.io ). But you can simply download the code and just edit it in few places to get DerefMut and RefCell support
maybe create new gate min_box_pattern
and think about only Box on there? Is there blocker to stabilize?
@KisaragiEffective , there is already nearly-finished work for strings here:
#98914 . My opinion is this: we should create DerefPure
as suggested in #98914 (comment) and then make String and Box both impls of DerefPure
Edit: unfortunately, this will not add ability to move out of Box
For anyone hunting for info - this #87121 is the tracking issue for deref patterns, which eliminate the need for box_patterns
. So, box_patterns
will eventually be deprecated and not stabilized.
Support will probably be added one by one for std types (String, Vec, Box, Rc, Arc) until a trait is decided upon that enables use on custom types like DerefPure
(which could only be those where deref
has no side effects, per zulip). It seems like it will be a while before we're able to use the result though, since the changes look pretty wide reaching - see the preliminary implementation for String #98914 that merged somewhat recently
Edit: couldn't find one anywhere, so here's a working example with the new string deref syntax:
#![feature(string_deref_patterns)]
fn main() {
let s = String::from("this is a string");
if let Some("this is a string") = Some(s) {
println!("matches!");
}
}