or
or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up
Syntax | Example | Reference | |
---|---|---|---|
# Header | Header | 基本排版 | |
- Unordered List |
|
||
1. Ordered List |
|
||
- [ ] Todo List |
|
||
> Blockquote | Blockquote |
||
**Bold font** | Bold font | ||
*Italics font* | Italics font | ||
~~Strikethrough~~ | |||
19^th^ | 19th | ||
H~2~O | H2O | ||
++Inserted text++ | Inserted text | ||
==Marked text== | Marked text | ||
[link text](https:// "title") | Link | ||
 | Image | ||
`Code` | Code |
在筆記中貼入程式碼 | |
```javascript var i = 0; ``` |
|
||
:smile: | ![]() |
Emoji list | |
{%youtube youtube_id %} | Externals | ||
$L^aT_eX$ | LaTeX | ||
:::info This is a alert area. ::: |
This is a alert area. |
On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?
Please give us some advice and help us improve HackMD.
Do you want to remove this version name and description?
Syncing
# Deref Patterns Design Proposal(s)
Deref Patterns Design Proposal(s)
Goal: Be able to match through a
Deref
orDerefMut
smart pointer ergonomically.Prior discussions include:
From these and my own considerations, I have come up with two related proposals.
We have a working implementation (without exhaustiveness checking) of one of the proposals available under the
deref_patterns
experimental feature gate.Design principles (WIP)
&T
and a smart pointer should be made as small as possible;Rc
toArc
should require minimal code changes;Q: should
&
vs&mut
indicate the mutability of the type, or of the borrow?A: today is mostly type, ergo2024 makes it sometimes borrow. E.g.
let &mut (ref x) = &mut place;
sharesplace
.Proposal(s)
The two proposals differ in syntax but share how they work: a
&<pat>
/*<pat>
/deref <pat>
/etc pattern would be allowed for stdlib smart pointers likeBox
orRc
, where<pat>
would match on the pointed-to value.Everything else about patterns works the same: we can nest them, we can get immutable or mutable access, they are subject to exhaustiveness checking and the dead arm lint.
Before we discuss the two syntactic options, let's start with some common design details.
Enabled for all std smart pointers
The feature would be enabled for the following std types:
Box
,Rc
,Arc
,Vec
,String
,Cow
,Pin
,ManuallyDrop
,Ref
,RefMut
,LazyCell
,LazyLock
. This is sound because all those impls are sufficiently idempotent.Extending the feature to user-defined
Deref
impls is outside the scope of this proposal.Exhaustiveness
Patterns are treated exhaustively as expected:
This is sound because we only enable the feature for a trusted set of types whose
Deref
impls behave well enough.Mixing deref and normal patterns
Some
Deref
std types likeCow
can be matched normally. For simplicity we forbid mixing deref and normal patterns for now.Explicit vs implicit syntax
The precedent with match ergonomics and the general way rust tends to work suggests that implicit deref patterns (if we want them) should desugar into an explicit form.
Moreover, we need explicit syntax to disambiguate cases like:
As such, both proposals focus on the explicit syntax. Implicit patterns are an optional extension.
Types
We follow how the rest of rust works for matches and
Deref
: we work on places.This means that
&<pat>
/deref <pat>
operates on a placex: P
whereP: Deref<Target=T>
, and matches<pat>
against the place*x
of typeT
.It does not operate on a place of type
&P
/&mut P
(except insofar as match ergonomics make it seem like it does). It also does not return a place of type&T
/&mut T
(again modulo match ergonomics). As we will see in Unresolved Questions, this poses an issue for string literals. Yet this is the consistent choice wrt the rest of rust.The two syntax options
Option 1:
&<pat>
No match ergonomics in the explicit form
Because of how
&
works today, we don't really have much choice:As you can see, this proposal isn't very convenient without implicit deref patterns.
Mutability
As could be expected,
&<pat>
callsderef
and&mut <pat>
callsderef_mut
.Interestingly, because
&mut T: Deref
, this allows matching on&mut T
with a&<pat>
pattern:Future-compatibility with moving out/
DerefMove
Because we distinguish
&
and&mut
the way we do, to move out we'd probably want some other syntax. Maybemove <pat>
, maybe*<pat>
, idk.Interaction with match ergonomics 2024
The match ergonomics 2024 proposal includes some possible changes to
&<pat>
patterns. Depending on the exact choices made, this could conflict with deref patterns.For example:
x: u32
.&<pat>
everywhere" only,x: Box<u32>
(which gives a move error).x: u32
.x: Box<u32>
.Hence combining the features could accidentally create a breaking change.
The safest choice is to disallow
&<pat>
deref patterns in the presence of match-ergonomics-inherited references, and to disallow "&<pat>
everywhere" onDeref
types (should be fine becauseDeref
types are rarelyCopy
). That gives us freedom to land either feature in any order. We can relax restrictions once both are stable.For custom
Deref
, this may mean that implementingDeref
on aCopy
type can break downstream crates:Option 2: some other syntax, e.g.
deref <pat>
,*<pat>
, or<Pointer>(<pat>)
For the sake of presentation, I'll use
deref
. Any syntax different from&
can work the same.Works with match ergonomics
This does not require implicit deref patterns to be practical.
Mutability
Mutability is inferred from the kinds of bindings we do inside the pattern.
Future-compatibility with moving out/
DerefMove
Compatible
. This could in fact replace the
box_patterns
feature entirely.Specific syntax choice
Here are the proposals I've seen:
deref <pat>
;*<pat>
;box <pat>
(already reserved keyword);Box(<pat>)
/Rc(<pat>)
/String(<pat>)
… type-specific pattern.Note that
deref <pat>
would require reserving a keyword, sincederef(x, y)
could be a tuple struct pattern andderef ::Type
could be a path.Note that
Pointer(<pat>)
would require some rule to not conflict with tuple struct syntax. Maybe we disallow it on tuple structs, maybe some visibility-based hack. Also it doesn't work in generic contexts, unless we allowT(<pat>)
for genericT
.Summary
Option 1:
&<pat>
;DerefMove
;&
pattern on something that isn't a reference could feel weird (e.g.matches!(Rc::new(true), &true)
).Option 2:
deref <pat>
,*<pat>
orPointer(<pat>)
.deref
would require a keyword i.e. new edition;*<pat>
goes the wrong way around, e.g.&*<pat>
looks like a reborrow but is actually two dereferences;Pointer(<pat>)
doesn't fit well with the implicit syntax;Pointer(<pat>)
conflicts with tuple structs.Compatibility matrix:
Deref
&<pat>
deref <pat>
Pointer(<pat>)
*<pat>
Unresolved questions
The string literal issue
A fun consequence of how we deal with places.
A similar issue exists today when matching with constants of type
&T
(playground):According to
@compiler-errors
this should be easy to solve for the specific case of string literals so let's ignore for now.Future Possibilities
Implicit deref patterns
As discussed above, we could extend match ergonomics to add implicit deref patterns as needed.
Desugaring algorithm
Today, when a concrete pattern
p
which isn't of the form&<pat>
is used to match on a placex: &T
, we adjust the binding mode and keep matchingp
on the place*x
.We would extend this behavior to places
x
of typeP
whereP: Deref
is one of the supported std types. This would insert implicit deref patterns. E.g.User-defined
Deref
I (Nadri) am reasonably confident that we can make this sound (cannot cause UB) for arbitrary user-defined
Deref
s as long as we disable exhaustiveness (i.e. require a_
arm).That said, there remain many design questions:
For that reason they're not included in the current proposal. That said, I personally think they're too cool to forbid, even if they stay perma-unstable or behind an opt-in unsafe trait.
Note that this is in tension with implicit deref patterns, as this means patterns could implicitly run arbitrary user code. We could e.g. force explicit patterns when the
Deref
impl is untrusted.Moving out of deref patterns
We could implement this today for
Box
, to match the existing deref magic (and replace box patterns). For other types, this would require a solution to the trickyDerefMove
design issue.In either case, this requires a compatible syntax for the explicit form as discussed above.
Mixed exhaustiveness
We could allow mixing normal and deref patterns:
(note: the hypothetical
impl Cow: DerefMut
that clones implicitly would stopCow
from being eligible for exhaustiveness)The two types of patterns are treated independently. In particular, exhaustiveness won't try to figure out that this is exhaustive: