owned this note
owned this note
Published
Linked with GitHub
# Deref Patterns, requirements and Syntax Tracking Document
Discussion on irlo: https://internals.rust-lang.org/t/somewhat-random-idea-deref-patterns/13813
Discussion on Zulip: https://rust-lang.zulipchat.com/#narrow/stream/213817-t-lang/topic/Deref.20patterns
Older irlo discussion: https://internals.rust-lang.org/t/pre-pre-rfc-match-ergonomics-for-container-types-restricted-method-calls-in-patterns/13371
Also discussed on the box patterns issue: https://github.com/rust-lang/rust/issues/29641
## Goal
Be able to match through a `Deref` or `DerefMut` smart pointer ergonomically.
```rust
let x: Option<Rc<bool>> = ...;
match x {
Some(deref true) => ...,
Some(x) => ...,
None => ...,
}
```
## Syntax options
- No keyword or sigil at all
- No keyword or sigil for literals (e.g. matching `String` with `"hello"`), keyword or sigil for non-literals (matching `String` with `x` and having `x` work as type `&str`)
- Only allow literals, disallow non-literals
- Sigil `*`: `*<pattern>`
- Sigil `&`: `&<pattern>`
- Overloads the existing `&` pattern
- Keyword `box`: `box <pattern>`
- Reuses and generalizes `box_pattern` syntax
- This would likely be confusing for non-`Box` types
- Keyword `deref`: `deref <pattern>`
- This would require reserving a new keyword
- Keyword would confict with the exact method this would work with.
## Constraints
- this affects match exhaustiveness. If we don't restrict the `Deref` impls, then it is unsound to consider the following as exhaustive:
```rust
let x: Option<SomePointer<bool>> = ...;
match x {
Some(deref true) => ...,
Some(deref false) => ...,
None => ...,
}
```
- we can't generally commit to only `deref`ing once per subpattern, because e.g.:
```rust
match x {
Some(deref 0) => ...,
Some(deref mut 1) => ...,
Some(deref 2) => ...,
}
```
- Note: this case could be implemented as a single `deref_mut`, then reborrowing. However, this would not work in a case like this:
```rust
match &mut x {
Some(deref v@0..3) => ..., // (1)
Some(deref mut v@4..7) => ..., // (2)
v@&None => ..., // (3)
Some(deref v@8..11) => ..., // (4)
}
```
- The above requires at least one deref_mut and one deref. Depending on the `Deref` impl, the shared borrow in pattern 3 may invalidate the mutable reference used in 1 and 2 (at the very least, for wrapper types like `ManuallyDrop`)
- However, when `x` is immutable, it should be valid to only deref once.
- if we allow side-effecting `Deref` impls, then people may start depending on the number and order of the `deref` calls, like they depended on drop order even if it was supposed to be unspecified.
- Lang Team only wants "pure" `Deref` impls. But what about impure `Deref` that are idempotent (like `Lazy`/`SyncLazy`)
- if we want the "no syntax" option, the type of `x` in `Some(x)` and `Some(x @ true)` could be different
- that already happens with `&` and match ergonomics I think
- some types can both be destructured and have a `Deref` impl
- e.g. `Cow` and `AssertUnwindSafe`
- outright ambiguous in the "no syntax" case:
```rust
let cowcow: Cow<Cow<bool>> = ...
match &cowcow {
Cow::Owned(x) => {} // what's the type of `x`?
}
```
- How would exhaustive matching handle mixing deref patterns with destructuring
```rust
let unwind_safe: AssertUnwindSafe<bool> = ...;
match &unwind_safe{
AssertUnwindSafe(true) => {},
deref false => {},
/* Would the above be considered exhasutive */
}
```
- Probably would be impossible for user-defined types; may be difficult for little advantage for standard library types (aside from adding `#[lang]` items to `Cow` and `AssertUnwindSafe`).
- What about Cow when `T::Owned` is not `T` (`str`, `[T]`, custom `ToOwned` impl)
- `DerefMove` still isn't a thing, yet we'd like to accomodate moving out of smart pointers in patterns in the future
- so we need to be careful when things are owned to not default to taking `ref`s where in the future we would want to move out
```rust
let x: Box<Option<bool>> = ...
match x {
Some(ref x) => {} // ok
Some(ref mut x) => {} // ok
Some(x) => {} // should error for now
}
```
- mutability propagates backwards: the choice of `deref` vs `deref_mut` depends on subpatterns
```rust
let x: Box<Option<bool>> = ...
match x {
Some(ref x) => {}
Some(ref mut x) => {}
}
```
- what happens if we mix `ref` and `ref mut` in a same pattern? Probably using `deref_mut` is ok.
## Possible solutions
- Restrict to only a chosen set of Standard Library types
- Current List:
- Language-level References: `&T` and `&mut T`
- Do we need to allow language-level references in deref patterns (absent generic deref patterns)?
- Smart Pointers: `Box`, `Arc`, `Rc`
- Precedent in Special Casing these in the standard library, with reciever types
- Growable slice collections: `Vec`, `String`, `OsString`, `CString`, `PathBuf`
- Vec and String also would be nice, since the deref targets (`[T]` and `str` resp.)
- Mutex/RefCell guards: `Ref`, `RefMut`, `MutexGuard`, `RwLockReadGuard`, `RwLockWriteGuard`
- Wrapper types: `IOSlice`, `IOSliceMut`, `ManuallyDrop`, `AssertUnwindSafe` (note: AssertUnwindSafe can be destructured)
- ManuallyDrop in particular would be nice.
- `Pin<P>` where `P` is a qualifying type
- Likewise to `Box`, `Arc` and `Rc`, this has precedent in being special-cased in the language.
- Could `Lazy` and `SyncLazy` qualify?
- The implementations are not pure (can run arbitrary user-provided code), but are idempotent wrt. structure and address (and side effects)
- Probably
- Could `Cow` qualify?
- `Deref` is idempotent wrt. structure and address
- ~~`DerefMut` is not. The address stability question cannot be resolved. Additionally, it can invoke a user-provided `Clone`, which is a safe trait and may not produce a structurally equivalent value.~~ `Cow` does not implement `DefefMut` so this is not an issue
- ~~Only an issue when mixing `Deref` and `DerefMut` accesses in patterns~~
- `Cow` can qualify for deref patterns, provided a `DerefMut` impl is not added.
- Initial Project will persue this solution, providing the list of requirements for a standard library type to qualify, as well as propose an initial list
- The standard library implementation would provide an unstable trait or attribute or otherwise (at it's option), applied to all qualifying types
- Require a `_` pattern for the match to count as exhaustive
```rust
let x: Option<SomePointer<bool>> = ...;
match x {
Some(true) => ...,
Some(false) => ...,
Some(_) => ...,
None => ...,
}
```
- people might rely on the order and number of `deref` calls, which would limit future changes
- An unsafe `DerefPure` trait that restricts `deref`.
- to make exhaustiveness sound, we need idempotency: successive calls must return "the same thing"
- need at least structural stability between `deref{,_mut}` calls without an intervening access through `&mut` to the pointer or pointee, other than a `deref_mut` call.
- ~~this disallows `DerefMut` for `Cow` since it must `clone` which may not preserve structural stability~~ Moot point, `Cow` is not `DerefMut`
- unless we can restrict to structural clones
- either with a new trait
- or allowing only auto-derived `Clone` impls, akin to how only auto-derived `PartialEq` impls are allowed for constants in patterns
- unless we disallow mixing `deref` and `deref_mut` on a same pointer
- might also want address stability without an intervening move or access to the pointer through `&mut`.
- would be useful for other optimizations too
- could have stronger requirements like outright purity
- would prevent `Lazy`
- unsure what the benefits are
- Some requirements are novel for unsafe traits.
- restrict to `const` `deref` impls
- Probably not enough to make exhaustiveness sound
- could modify a `&mut` ref and thus not be idempotent
- maybe enough if we don't allow `DerefMut`?
- May be overly restrictive (e.g. a `Lazy` that reads a file)
- Currently disqualifies all standard library smart pointers, due to not permitting raw pointer dereference.