Pre-pre-RFC: match ergonomics for container types β€” restricted method calls in patterns

This is an interesting point.

Note that in C++, you can project shared_ptrs, because they allow an aliasing constructor (#8). Arc/Rc just made different design tradeoffs.

So it might be something valuable to keep even if things like Box can't.

Oh -- and pin-project is a thing, which would be an interesting thing to have work through this feature...

That actually sounds quite cool! Of course, then you need more complex syntax, to specify what kind of projection you want - or alternatively, making the "cast" (via Deref, or maybe even AsRef) explicit, so that the projection is simply that of the type which you currently matched on.

I think it would just be leaning in to binding modes. So if you match a Pin<T>, you'd get out Pin<U>s, the same as how if you match a &T you get &Us. (That would need GATs, I assume, to actually make work well.) And if you want to get &Us instead of SharedPtr<U>s, then you'd match on &*p or whatever.

I suppose that wouldn't solve the match-a-Box<T> problem that was the original point of this thread, though :confused:

I just had the idea that a deref syntax/feature for match would also allow something like:

match (expr1(), lazy(|| expr2())) {
    (Foo, _) => foo(),
    (Bar, deref Baz) => baz(),
    (Bar, deref Qux) => qux(),
}

mimicking a common idiom from lazy functional programming languages for avoiding nested matches while also avoiding unnecessary evaluation of expr2.

In case that isn’t clear: lazy would return a wrapper enum for the FnOnce() -> T that implements derefs to T and caches the result after the first call. The code above is supposed to be pretty much equivalent to something like this:

match expr1() {
    Foo => foo(),
    Bar => match expr2() {
        Baz => baz(),
        Qux => qux(),
    }
}
6 Likes

Provided we had new (contextual) keywords, for sure the choice of the syntax does not really matter (at least to me), I just chose & since:

  • it does not involve / require an extra keyword,

  • using & / &mut allows to choose whether Deref or DerefMut is used.

    As I mentioned, if we were to have DerefMove, then my whole "beautiful symmetry" vision would break, so if a deref / deref mut (and future possible deref move) keyword usage is deemed more appropriate, so be it.

    That being said, :thinking:, another argument against & / &mut is that, in the expr world, it's always * that is used to perform a dereference. In that regard, a single deref keyword would actually make more sense :thinking:

The important part is to have a pattern dual of let binding = &*<expr> as let smth(ref binding) = <expr>; whether smth(<pat>) is &<pat> or deref(<pat>) is of no importance to me.


(That would need either the inexistent DerefMove, or to bind by ref / T : Copy, though).

1 Like

The starting point of that paragraph was agreeing to the "structural/pure dereference" idea, and to the fact that it doesn't matter what syntax that uses. I should have made it more clear.

One question that now arises though is, how pure should the implementation be for that to count? For supporting Rc and friends it has to at least allow access to arbitrary fields. But it would be cool if it could also support (slightly) more complex operations, such as checking an enum variant - for supporting Cow, for example.

Regarding the syntax of the match, I liked your distinction that * is used for all derefs. I guess that makes sense, as when borrowing you need to specify what kind of reference you want (&/&mut/Rc/...) but when dereferncing it's decided what implementation to use depending on what you need. So here the distinction between Deref, DerefMut and DerefMove (when it comes, or for Box right now) could be done by having

let deref (ref binding) = <expr>;
let deref (ref mut binding) = <expr>;
let deref (binding) = <expr>;

as the duals of

let binding = &*<expr>;
let binding = &mut *<expr>;
let binding = *<expr>;

I assume this is what you meant?

4 Likes

Yes :+1: