Change `ref` to `*` in patterns

It seem intuitive for me to use * instead of ref in patterns, since ref in pattern is exact opposite of * in expression.

Example:

match std::os::args().as_slice() {
     [_, *arg] => println!("{}", arg),
     _ => panic!()
}

Pros:

  • This syntax correctly suggests that string inside array can be obtained by dereferencing arg.
  • One keyword less.
  • Discoverable by randomly adding *s and &s (which, what I noticed, is a common practice in languages like C++).
  • Consistently mirrors expressions: match *x { *x => x } works as a no-op, as with &.

Cons:

  • May be considered uglier.
  • Ungooglable (but I don’t think optimizing language for newcommers which haven’t read the guide is a good idea).

I want to ask if it was considered before and hear your opinion on that.

Edit: Changed example, which was just wrong.

6 Likes

This is exactly what I thought! Why does there need to be a ref?

struct Point {
    x: int,
    y: int,
}

fn main() {
    let point = Point{ x: 1, y: 2 };
    let Point { x: *xx, y: yy } = point;

    println!("{}, {}", *xx, yy);
}

This doesn’t work. But:

...
let Point { x: ref xx, y: yy } = point;
...

does, that makes zero sense

This idea is already being discussed in RFC comments here. The biggest problem with it IMHO is what happens when you want to specify a mutable reference (but it may still be the least bad option).

Thanks for pointing to that discussion! I was almost certain that It was proposed before.

I wasn’t thinking about mut before, but now I see that *mut may seem misleading, as it looks like it has to something do with raw pointer. So…

  • We could introduce *mut operator (that would make code less ambigous but it would too much noise to consider this)
  • Change syntax of ref mut x to mut *x. I like it because it’s really *x being mutable, not the reference itself. (I haven’t seen it being proposed in discussion)

We can also say that pattern syntax tries to mirror expression syntax and has nothing to do with type syntax. You don’t use Option<x> to get value inside Some, so why should *x work for raw pointers?

Speaking of raw pointers and compatibility with C, let us remember that compatibility with C led C++ to being ugly and incosistent, so I see no reasons to pursue it. We could rename raw pointer to ^ or @ or anything, so it won’t collide with expression and pattern syntax. It’s also worth noticing that we put * on the other side of type than C, so the original reason behind this syntax makes no sense here.

1 Like

The trouble is that just as you can currently have mut ref mut x in a pattern, which results in an &mut reference to x in a mutable slot (so like let mut x: &mut T), following that logic mut *x would be an & reference in a mutable slot, rather than an &mut reference in an immutable one. I'm not sure if it's possible to support both axes of mutability any other way.

I personally wouldn't actually mind getting rid of the built-in syntax for pointer types, and replacing it with Ptr<T> and PtrMut<T>, or maybe &unsafe and &unsafe mut, or whatever. But I also wouldn't be surprised if others did. (The latter is something that I believe was actually considered at some point a long time ago and decided against, though I can't remember exactly why.)

1 Like

I also came up with the same idea of replacing ref with *. Personally it’s OK to get &mut in an immutable slot with Ok(*mut ptr) => ... and to get & in a mutable slot with Ok(mut *ptr) => .... Granted, these usages of mut have nothing to do with symmetry, but I think they are no worse than the current syntax.

For me the exact opposite makes more sense. You can read mut ptr in Ok(*mut ptr) as “mutable pointer”, and mut *ptr as “mutable value behind pointer”. But I would be happy with either syntax, since compiler will warn/error if you get it wrong.

1 Like

I find ref much more comprehensible than abusing * one more time.

Besides that I find it counter-intuitive. What is [_, *arg] supposed to mean? *arg would mean that you construct something sucht that *arg can be satisfied. It would not work like any other match. Match patterns usually reduce, ref and your proposed * increase complexity.

ref makes it more clear that it is not part of the matching pattern but just a specifier that you want a reference to the matched thing.

Why do you think it is counter-intuitive? The rule of thumb is when you write

match expr {
    [*a] => ...
}

[*a] will produce what is equal to expr under the new rule.

Please recall that when you write

match opt {
    Some(x) => ...
}

Some(x) produces what is equal to opt as of now.

1 Like

C’s declaration-follows-use style for types has a similar rule of thumb, but that doesn’t stop people from getting very confused by it :slight_smile:

Because C’s declarations are written in a spiral pattern. http://c-faq.com/decl/spiral.anderson.html

@iopq That’s not true, see Linus’s answer here: https://plus.google.com/+gregkroahhartman/posts/1ZhdNwbjcYF

It’s not ACTUALLY true, but the complicated rules for precedence and associativity of several different operators is why C declarations are confusing, not because they follow use

This is really a digression, but -

I don’t think so. There is only one relevant precedence rule, which is that *a[b] / *a(b) are *(a[b]) and *(a(b)) respectively, not (*a)[b] or (*a)(b). Everything else about type declarations is unambiguously determined from basic syntax principles common to most programming languages.

E.g., why do you write the outer array’s size first, then the inner (the ‘associativity’ thing Linus mentioned)? Because indexing an array of arrays (outer) gets you one array (inner), and indexing one array gives you the final element. (Some scientific languages have a specific concept of a multidimensional array distinct from an array of arrays, but C joins the norm in not.) Why int *(*x)() for a function pointer returning int *? Because int **x() would call x() and dereference the result (per that rule), while int (**x)() would dereference twice before calling. Et cetera. There is no need to remember some weird “spiral rule” or any other rule specific to type declarations; if you remember that one rule of precedence (which you should really know anyway if you’re doing anything with pointers), you should be able to work out any C type by following use.

But you do have to think backwards - work through what sequence of indexes, derefs, and calls you can do to x to eventually get a char, and then flip that around to get an idea of what the type looks like. I can only pin the confusion on this.

Thing is, this “thinking backwards” does somewhat resemble what’s required to understand why *a would be found in a match expression.

2 Likes

If you are still interested in discussing this proposal, here’s an RFC (please leave comments there from now on).

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.