Stream: project-deref-patterns

Topic: Deref Patterns (but now a project group)


Connor Horman (Mar 09 2021 at 18:24, on Zulip):

Given that this is now a project group, and work can officially start, I'm going to use this opportunity to bring back up the major questions of the feature, and the updates from the MCP process.
A previous consolidation of the discussions for this can be found on this hackmd.
Syntax:

Soundness and Exhaustiveness:

@Nadrieril, I believe you previously expressed interest in participating in the MCP and Project Group. Are you still interested in working on this?

Josh Triplett (Mar 09 2021 at 18:31, on Zulip):

Note that we didn't rule out all future possibility of allowing this for user types; we're just suggesting that the initial version should focus on standard types.

Josh Triplett (Mar 09 2021 at 18:34, on Zulip):

Also, FWIW, I think "no sigil for literals, sigil for non-literals" should be a contender as well. I'd expect literals to be a very common case of this, and they're also the case where a sigil provides the least value.

bstrie (Mar 09 2021 at 19:07, on Zulip):

deref as the keyword has some nice symmetry with the ref keyword. the overlap with .deref() is unfortunate, but there's no technical reason why a keyword couldn't be contextual rather than reserved.

bstrie (Mar 09 2021 at 19:10, on Zulip):

more generally I'm in favor of a keyword rather than a sigil, for reasons that are probably out of scope for this proposal. to elaborate, I like the idea of being able to use the chosen keyword in non-pattern contexts as a replacement for *, whose prefix nature gets awkward with method chaining; in the same way that .await is postfix, we could have a .deref postfix keyword for this use case. of course, this isn't exactly urgent, since you can technically do .deref() today already in method chains :P

Connor Horman (Mar 09 2021 at 19:11, on Zulip):

Isn't there a potential ambiguity? I don't know off the top of my head.

Nadrieril (Mar 09 2021 at 19:14, on Zulip):

I still want to help out if I can, but I can't commit to anything sadly :/

bstrie (Mar 09 2021 at 19:20, on Zulip):

ambiguity with regard to having the keyword be contextual rather than reserved? I don't think it's an insurmountable challenge, but it would take some care. the good news is that most method and function calls are illegal in pattern context, so the actual Deref::deref would remain unambiguous to parse. the only problem I would foresee is if someone wrote something like enum Foo { deref(i32, i32) }, in which case a pattern like deref (4, 4) would be syntactically ambiguous. the same thing applies to tuple structs like struct deref(i32, i32). you would want a rule where users are forbidden from defining enum variants or tuple structs named deref. other than that, you would disambiguate on whether you're in pattern or expression context. that said, if there's a better keyword, that would be easier to implement :)

Connor Horman (Mar 09 2021 at 19:26, on Zulip):

In any case, it would most likely require an edition boundary.

bstrie (Mar 09 2021 at 19:27, on Zulip):

true, but that's the case for any new keyword

Connor Horman (Mar 09 2021 at 19:30, on Zulip):

The problem is that it would delay deref patterns to 2024 edition, unless the proposal for the reservation can be drafted, submitted as an RFC, and fcp merged before whatever deadline exists for edition 2021. I don't know that doing such would be reasonable.

bstrie (Mar 09 2021 at 19:33, on Zulip):

well, keywords can be provisionally reserved long before their RFCs are accepted; the 2018 edition reserved async and await, which didn't have accepted RFCs at the time of the edition. given that this RFC is a successor to box patterns, I think it's clear that this is something that's generally desirable and doesn't really need to do much work to justify itself. so if a keyword for this can be decided upon relatively soon, I don't think it would be too much of a stretch to ask that it be reserved long before your proposal is finalized

Josh Triplett (Mar 09 2021 at 19:36, on Zulip):

@bstrie We have a policy now (developed largely because of the problems from those keywords) against reserving keywords before we have a concrete feature proposal on the table.

bstrie (Mar 09 2021 at 19:37, on Zulip):

how about chase? dereferencing is often called "pointer chasing", so it avoids having to invent new terminology. it's also short, unabbreviated, relatively unlikely to be used as an identifier in existing code

Joshua Nelson (Mar 09 2021 at 19:38, on Zulip):

Making up new keywords doesn't seem very helpful - it won't fix the ambiguity and it means people are less likely to recognize it

Joshua Nelson (Mar 09 2021 at 19:39, on Zulip):

I think deref is better than chase for that reason

bstrie (Mar 09 2021 at 19:39, on Zulip):

it does fix the ambiguity in a sense: we can make the keyword reserved rather than contextual, which we can't do for deref because that would break std

bstrie (Mar 09 2021 at 19:45, on Zulip):

(technically you could make deref a reserved keyword if you deprecated-and-renamed Deref::deref to something else, and then introduced the deref keyword only on edition X, and then made it a compiler error to access Deref::deref on edition X (using mechanisms akin to what this RFC proposes), but this would be both highly disruptive and take years to achieve.)

scottmcm (Mar 09 2021 at 19:49, on Zulip):

(Being able to implement things as k#deref in nightly to be able to avoid these conversations for a while is one of advantages I put in the draft https://github.com/scottmcm/rfcs/blob/never-preemptively-reserve-keywords/text/0000-raw-keywords.md#motivation )

rpjohnst (Mar 09 2021 at 21:38, on Zulip):

Keyword or sigil, it would be great to preserve the pattern/expression duality in some way- box patterns+expressions do this, & patterns+expressions do this, but ref is a binding mode (i.e. only applies to identifiers) with no expression analog.

The tricky thing here, as I see it, is there is not one single expression counterpart- Box::new, Rc::new, &, etc. are all individual cases that "add" a deref-able thing that a deref pattern would match. (TBH, if ref didn't already exist, this would make it a good candidate here!)

rpjohnst (Mar 09 2021 at 21:42, on Zulip):

Another form of consistency that would be good to preserve is interaction with default binding modes. That is, in the main "by-move" "2015-compatible" mode, indirections should be considered part of the scrutinee's structure and represented in the pattern (i.e. using the keyword/sigil), and they should disappear from patterns in precisely the same way- by switching default binding modes.

(IMO "no sigil for literals, sigil for non-literals" breaks this, because literal-vs-not is a subtly different distinction than the one currently used for switching binding modes.)

bstrie (Mar 09 2021 at 22:43, on Zulip):

with regard to preserving the pattern/expression duality, is having a counterpart in expression position for this still desirable? indeed one of the original goals of the box keyword would be that you could construct Box, Rc, etc. with box foo, but it feels like the idea of having a generic "smart pointer constructor" keyword has been long since abandoned.

rpjohnst (Mar 09 2021 at 22:56, on Zulip):

I'm less interested in actually having a generic smart pointer constructor, and more interested in making the deref pattern fit the duality. So e.g. "deref" feels "backwards" here because a "deref" isn't part of the structure of anything, while "box" or "&" or "ref" (in an alternative timeline at least) feel like they follow the same rules as other patterns.

bstrie (Mar 09 2021 at 23:31, on Zulip):

sure, if we're just thinking about keywords here then a verb like "deref" would be a bad choice; you'd want to represent this concept with a noun like ptr or something

rpjohnst (Mar 09 2021 at 23:34, on Zulip):

A thought: it's already idiomatic to invoke autoderef by adding an extra & (like foo(&some_box) where fn foo(x: Box<T>)), and further & is already the sigil that you can elide with default binding modes, so maybe & itself would be a good general-purpose deref pattern?

bstrie (Mar 09 2021 at 23:34, on Zulip):

that's mentioned as one of the options in the proposal, I'm still unsure how I feel about it

bstrie (Mar 09 2021 at 23:37, on Zulip):

it still feels kinda... backwards. which is to say, if you actually had box <pattern>, you might prefer to make it work with references (what are they if not smart pointers?) and use that to replace &<pattern>

bstrie (Mar 09 2021 at 23:38, on Zulip):

but overall it's not the worst idea

Josh Triplett (Mar 10 2021 at 00:47, on Zulip):

To me, something like match some_string { &SOME_STR => ... } seems backwards; if you were going to apply & to something, it'd be some_string not SOME_STR.

Joshua Nelson (Mar 10 2021 at 01:33, on Zulip):

Why? some_string is a struct, not a reference.

Daniel Henry-Mantilla (Mar 10 2021 at 01:57, on Zulip):

The case of strings is a bit weirder in that we never have true strs constants nor patterns (we only have &strs).
Should we have: const SOME_STR: str = *"…";, then we could write:

if matches!(*"hi", SOME_STR | *"foo") {
// and thus:
if matches!("hi", &SOME_STR | &*"foo") {

At that point matching a &str scrutinee leads to &*<pattern of type &str> being the most explicit syntax, and needing to write that for String would, at the cost of some sigil noise, lead to the most consistent logic / mental model:

matches!(some_string, &*"foo")

The sigil noise can then be solved with our good old match ergonomics: deref patterns shouldn't be incompatible with those, it's just that having a sigil / keyword becomes especially necessary when binding values, while remaining optional for those who would want extra explicit indirection syntax.

This whole thing may look weird but, again, it's the most consistent model:

match get_string() { &*(s @ ("foo" | "bar")) =>
// move the `&` from rhs pattern to lhs scrutinee (`*`):
match *get_string() { *(s @ ("foo" | "bar")) =>
// move the `*` from rhs pattern to lhs scrutinee (`&` or `&mut` based on further usage):
match &*get_string() { s @ ("foo" | "bar") =>

@Josh Triplett in the case of String we may have been used to just putting a single & on scrutinee position and let auto-Deref take care of the actual magic

rpjohnst (Mar 10 2021 at 03:31, on Zulip):

Josh Triplett said:

To me, something like match some_string { &SOME_STR => ... } seems backwards; if you were going to apply & to something, it'd be some_string not SOME_STR.

This immediately brings to mind the IntoIterator impls for things like &Vec<T> vs Vec<T>, but i'm not sure i really understand why this makes sense. &some_string having type &String means the pattern would be doing two derefs, one old-style and one new-style, wouldn't it? what's the purpose of the extra old-style deref?

Last update: Jul 24 2021 at 20:15UTC