Lived Experiences: Strange match ergonomics

This has been "broken" since before 1.0. let x = y; already "radically differs" in owning vs borrowing based on whether y is a reference. All match ergonomics does is allow this sort of thing to pass through patterns, by eliding part of the structure of types (the part built out of references).

Your jab about "local reasoning" in compilers is also incredibly disingenuous. None of the local reasoning a compiler does is at all thwarted by match ergonomics.

1 Like

No, it was an analogy. Match "ergonomics" breaks humans' local reasoning (obviously if it broke the compiler's, then it would have more immediately-noticeable and lower-level effects such as missed optimizations, failing to compile, or even unsoundness).

1 Like

That’s irrelevant to my point- your repeated scare quotes around “ergonomics,” and your insistence that the compiler team did something impossible to understand, are not helpful.

Match ergonomics happened, they’re helpful to some. We can discuss drawbacks and mitigations without your antagonism.

3 Likes

To be clear, I think ergonomics in match and if let is fantastic.

It has saved me a lot of hassle, tons of ugly &Enum syntax, and de-facto eliminated ref, which I think is a wart in the language (every novice had to ask about difference between ref and &).

I’m here with intention of improving just a certain case where I don’t find it valuable .

5 Likes

It isn't, since you were putting words in my mouth. Again, I never asserted that the default binding modes broke the compiler, and so your claim that I did is false.

The fact that they happened doesn't in itself imply that they are universally helpful. Yes, they are helpful to some. Yes, I think they are also harmful. That's not an oxymoron – there are many features in many languages that seemed to be a good idea because they made some things easier, but turned out to have unexpected consequences or be dangerous in other situations. Array-to-pointer decay in C, automatic semicolon insertion in JavaScript, all these features were intended as helpful shortcuts, but in the end they happen to have more negative effects than the marginal helpfulness would be worth it. My claim is that pattern match ergonomics is one such feature too. Not because of the compiler, but because of human users of the language.

Your personal insults aren't helpful either, and I'm not willing to continue/litigate this further.

On this and other issues you have behaved with considerable antagonism, @rpjohnst telling you how it is impacting the conversation is not an insult. Every time you have been informed that your antagonism is disruptive, you have responded in this exact manner. I believe you have genuine intentions to have a positive impact on Rust: the tenor you have adopted on this forum is extremely counterproductive to that end.

9 Likes

We definitely didn’t consider deeply the possibility that there was a difference in experience and desire between refutable and irrefutable patterns. I think when you are sufficiently intimate with how Rust works, the answer seems obvious: “this is totally orthogonal to refutability, it would be inconsistent and confusing if it didn’t apply across the board.” But I believe that for many users, destructuring with let and with match felt significantly different from one another, and whereas this change might’ve removed noise in one case, it might be surprising in the other.

On the other hand, like I think several people have commented, this is probably impacted a lot by having learned Rust without the feature: its possible that people learning Rust now will not find its application in let surprising at all. If newer users continue to have trouble with this, though, I think it would be worth revisiting whether or not applying match ergonomics to irrefutable patterns was a mistake in the next edition after 2018.

7 Likes

17 posts were split to a new topic: Linting and match ergonomics

I think this is an interesting argument for something like Random Musings: types in patterns : the way this is dealt with in that case is by adding a type (x in let x: u32 = y; is definitely not a borrow), so the same would work inside a pattern (x in let Struct { x: i32 } = y; is definitely not a borrow).

3 Likes

A lint specifically against “match ergonomics” does feel like dissensus to me, but there’s an implication in phaylon’s last few posts that this is an arbitrary distinction, and I think he’s on to something there.

I believe there are two concrete reasons why I personally get that impression from a match ergonomics lint but not from e.g. an unsafe code lint:

  1. Timing and context. As @withoutboats put it: “The negative reaction I’ve seen on this and other issues in clippy has been to … just disable whole features through clippy right after they’re stabilized.” Phaylon’s last few posts here are the first time I’ve seen anyone suggest they only want to disable match ergonomics in some of their code, rather than completely disable the feature.

  2. “match ergonomics” is not really an independent feature. It’s a change to type inference. Like “non-lexical lifetimes”, once it’s stabilised we’ll probably stop talking as if it’s a separate feature in need of a separate name. After all, that’s why we’re content to continue using a name that describes the motivation for the feature rather than the feature itself; it can’t mislead future novices if it’s going to disappear anyway.

#2 in particular makes me want to ask: @phaylon how would you feel about linting against all type inference in your most sensitive code?

3 Likes

I am quite happy about the match ergonomics. You can often debug the matches by adding an extra type constraint

struct Z;
fn foo(w: Z) { }

pub fn main() {
    let v = &Some(Some(Some(Some(Z))));
    let Some(w) = v;
    let Some(x): Option<_> = w;
    let Some(y) = x;
    let Some(z) = y;
    foo(z);
}

Error message

9 |     let Some(x): Option<_> = w;
  |                              ^
  |                              |
  |                              expected enum `std::option::Option`, found reference
  |                              help: try using a variant of the expected type: `Some(w)`
  |
  = note: expected type `std::option::Option<_>`
             found type `&std::option::Option<std::option::Option<std::option::Option<Z>>>`

But this approach becomes clumsy if w has the type MySurprisinglyLongEnumName. Is there a shorter way to require that w is not a reference?

Ideally rustc should follow the trail of matches and warn the user that x is a reference because v is a reference, but that may not be easy.

Hey, everyone, the conversation has gone way off base, and in the process turned from heated to accusing. Let’s all agree to take a break and cool off.

This is a thread about discussing strange effects related to match ergonomics. If you’d like to continue listing those, please continue. If you have other topics, please start a new thread.

(ProTip: You can branch off of any post by clicking the age indicator in the top-right and selecting “+ new topic”.)

1 Like

I have moved most of the sidebar about linting (and much of the resulting meta-discussion) to a separate thread.

I’d like to reinforce @illustrious-you’s request that the discussion cool off and stay on topic. There are some genuine disagreements here that I think are worth debating, but please focus on clearly explaining your point of view and listening respectfully to others’, without making claims about their motives or emotions.

If you feel someone else is behaving inappropriately, in most cases you should flag it and/or talk to the moderators rather than arguing back and forth. Otherwise it becomes impossible to clean up the thread without hiding both the initial message and your replies. (And as usual, responses to this moderation note should go to private mail or a new thread, and not here.)

2 Likes

@phaylon mentions a few more ergonomic troubles in the Linting and match ergonomics thread (link goes directly to the examples).

Minor stumble today:

https://play.rust-lang.org/?gist=359f3b3704ab15c5936b1dee88ffd9cc&version=stable&mode=debug&edition=2015

use std::ffi::OsString;

struct S {
    //e: E,
    e: &'static E,
}

impl S {
    fn foo(&self) -> String {
        match self.e {
            E::A(ref os) => os.clone().into_string().unwrap(),
            E::B(s) => s.to_owned(),
        }
    }
}

enum E {
    A(OsString),
    B(&'static str),
}

fn main() {}

Field e on S changed from owned to being borrowed. Match ergonomics pushes the error past the match statement and patterns, and surfaces it by having a double-reference (&&str), so to_owned now gives the wrong type.

Attempts to solve:

  • E::B(s) => s.to_owned().to_owned(), - works, a bit ugly
  • E::B(&s) => s.to_owned(), - fails to compile, as it assigns s to the unsized type str (presumably because match ergonomics is disabled)
  • E::B(s) => s.to_string(), - works, but I personally prefer to_owned if possible (I find it more type-restrictive)
  • E::B(&ref s) => s.to_owned(), - works, but cryptic
  • match *self.e { - works (by disabling match ergonomics I assume)

Is it ever useful to borrow a reference again to end up with a double reference? I guess we can’t change this now though.

Just a small piece of ergonomics feedback: On the unofficial Rust Discord server, a beginner posted this snippet today:

enum TokenType {
    Symbol(usize, usize),
    // ...
}

for token in tokens.iter() {
    let out = match token {
        TokenType::Name(start, end) => {
            println!("Name: {}", &contentstr[start..end]);
        },
        // ...
        _ => println!("Unknown token.")
    };
}

This fails with the following error message:

the type `str` cannot be indexed by `std::ops::Range<&usize>`

The solution here is to write match *token, but this was rather unintuitive to them and the error message didn’t point them at the right location. (I got somewhat confused by the message as well and initially suggested the sub-par solution &contentstr[*start..*end].)

3 Likes
println!("Name: {}", &contentstr[*start..*end]);

For beginners I think this is good enough though. Although you can show them match *token as well, but the above is not that bad anyways.

Beginners tend to use grammars in a less elegant way and this is fine because they don’t have much knowledge of better solutions. But they need time to learn.

My mental model for matching eventually became that I always want to match against non-reference types, such that match arms never begin with & (implicit or explicit) and I add a ref whenever I want/need a reference rather than a value. That makes the process:

  1. write the outer type of the match arms
  2. get the input value to match the type in 1.
  3. get the bindings in each arm to be the right type, using ref or ref mut.

The above examples made me realize that match ergonomics will make that process significantly more difficult.

2 Likes

This has been my experience as well, but I find that instead of writing ref or ref mut, I now just add & or &mut appropriately to the match.

1 Like

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