Somewhat Random Idea: Deref patterns

We already have that problem now though:

let foo = &2;
if let 2 = foo { // ok
    assert_eq!(foo, 2); // type error

Although I think a sigil would be a good idea in this case, because it allows you to express the difference between the patterns Some(ref x_string @ *"foo") and Some(*(ref x_str @ "foo")), I don't find the "patterns should exactly match constructors" argument convincing because we already lack that property.

Fair enough, although that one does not bind a new variable, so it's not that bad (cost to pay to get match ergonomics, since ref and ref mut are more noisy than "single-char" sigils).

  • The following example, which indeeds fails with a type error on the assertion, is, however, much more saddening:

    match outputs::<&i32>() {
        two @ 2 => assert_eq!(two, 2),

But the fact we already have some inconsistencies shouldn't be an argument to get more, imho.

Note that without the whole ergonomic discussion, for & (or &mut) stuff, we can indeed disambiguate between the two bindings by doing:

match outputs::<&i32>() {
    &(two @ 2) => assert_eq!(two, 2), // ✅ (except for unused_parens firing 😩)
    two @ &2 => assert_eq!(two, &2), // ✅

So I think that Deref{,Mut} generalizations should be no exception to this, as you mentioned, so there must be the option to use a sigil / an annotation. And, as I mentioned in my previous post, I think that such sigil elision should be left to a follow-up RFC; it shouldn't be mixed with discussions about the desired SpecialDeref semantics.

The more I think about it, the more I like repurposing the box pattern syntax. One advantage, anyone who is still using box_patterns unstably won't have to change their code with this (since Box<T> would be a primary candidate for Deref patterns).

Are there any reasons that the syntax should not be used?

& for the sigil?

let v : String = ...;
match v {
    // I have always been struggling with
    ref s => assert_eq!(s, &"abc".to_string()),

    // because intuitively I've been expecting this instead
    *s => assert_eq!(s, &"abc".to_string())
    // indeed, s is a pointer (reference) to a String
    // so what it points to (*s) gets matched against a String

By similar token in case of a deref I'd expect

let v : String = ...;
match v {
    // v, a String, is used as an "address" of &str
    // indeed *v yields s
    &s => assert_eq!(s, "abc")

Both suggestions are based on the notion of * and & being an inseparable complimentary pair. Said notion is indeed ingrained into the brain of any C programmer worth his salt.

&<pattern> is already valid, for reference patterns. IE.

    let i = Some(32);
    match &i{
        Some(&v) => println!("{}",v),
        None => println!("Empty");

I am hesistent to overload it for an arbitrary deref.

1 Like

Actually that doesn't work, although I wish it did. You have to use &Some(v) instead of Some(&v), because you can't use & on inner pattern matches unless they are structurally references.

Regarding "overloading", we already overload * to mean pointer / reference dereferencing as well as Deref impls, so I don't see an issue with overloading & to do the same thing in patterns.

1 Like

Given a concern about patterns calling arbitrary user code. Would limiting const impl Deref for patterns (if and when that RFC is accepted) be a good idea?

I know that it doesn't limit everything but it should help with some cases.

const means compile-time executable. AFAIK const doesn’t mean side-effect free and it will only get worse in the future. E.g. a const DerefMut could eventually be able to modify self. (I know that you were talking about Deref and not DerefMut.) Rust doesn’t have a mechanism of limiting side-effects other than having it be a condition for some unsafe.

Could this possibly be more restrictive then necessary? Either way, going that route would, as you mentioned, block on const trait impls.

What may be a good idea until the exact restrictions that would be reasonable to impose are known, is that we limit deref patterns to the standard library types I listed previously for now (noting that std::borrow::Cow also has to be excluded).

The (hopefully) exhaustive list of types that would be accepted in deref patterns, if this route was chosen, would be:

  • Language-level references &T and &mut T
  • The smart pointer types: Box, Rc, Arc
  • The growable slice types: Vec, String, CString, OsString, and PathBuf
  • The wrapper types: ManuallyDrop, AssertUnwindSafe, IOSlice, and IOSliceMut, and PeekMut
  • Guard Types: Ref, RefMut, MutexGuard, RwLockReadGuard, RwLockWriteGuard
  • And Pin<P>, where P is one of the above types.

Note that Lazy and SyncLazy cannot be candidates, as derefing them can call a user-provided function that may panic. Likewise, Cow (which I neglected the first time) cannot be either, as the DerefMut implementation can call a user-provided clone function.

That is true. And, since references could reasonably not be excluded from deref patterns, it would be consistent to use &.

I know that const means only "executable in a const context" but I doubt that a deref impl should do I/O at all.

I really doubt (and hope) that this will be merely a fixed list. One of Rust's main advantages (that I see at least) is one where it tries to give the user as much control to build their own implementations. I find it very gratifying that we seem to be moving to a world where even Box<T> is fully implementable without any special casing.

On the zulip thread, one of the main issues brought for the const impl is that you (currently) can't dereference raw pointers in const, which rules out half of the types I listed.

At the start, it would be that fixed list. However, the feature should definately be extended eventually to user-defined types.

I do agree, and I don't particularily like magic boxes (and in my WIP implementation of rust, Box isn't even a lang item). However, in my opinion, having the feature for the "main" cases, quicker, is more important then eliminating the magic

Dreaming bold:

  • pure fn akin to const fn
  • pure traits akin to const traits :slight_smile:
  • similar to but not idential to const
    • no assignments
    • only call fn-s that are pure
    • no asm
    • ...

wouldn't that help here and elsewhere?

Alternative syntax:

const(pure) fn foo(..) { .. } // pure
const fn bar(..) {..} // const
const(pure) const fn baz(..) {..} // both pure and const

P.S. or maybe

fn foo(&const mut ref) {..}

This function takes unique reference but is not allowed to modify anything through it

I think that would do best as it's own orthogonal discussion (saying nothing about how well it would do). It could help with this in theory, but I certainly would not want to block deref patterns on such a major proposal.

Rust used to have a pure modifier, but it was removed pre-1.0. I don't think we're going to double back again.