简体   繁体   中英

Why does the same const struct as match guard compile but not as match pattern?

I was struggling a bit to handle errors from the postgres crate. Specifically, the SqlState in postgres::error . Since the constants are using Cow , I did not manage to use it in a simple way in match patterns.

For example, UNIQUE_VIOLATION is defined as

/// 23505
pub const UNIQUE_VIOLATION: SqlState = SqlState(Cow::Borrowed("23505"));

I ended up with doing the following, but I am sure there is a more ergonomic/canonical way of matching the errors.

        let maybe_user: postgres::Result<User> = create_user(&conn, email);
        match maybe_user {
            Err(e) => {
                // e.code() returns Option<&postgres::SqlState>
                match e.code() {
                    None => panic!("not a sql error"),
                    Some(sqlstate) if sqlstate == &UNIQUE_VIOLATION => {
                        // do something...
                    },
                    Some(sqlstate) => panic!("unexpected error"),
                }
            }
            Ok(user) => {
                assert_eq!(user.email, email);
            }
        }

If I want to explicitly handle a large quantity of errors, it gets really tedious to repeat lines like

Some(sqlstate) if sqlstate == &UNIQUE_VIOLATION => {},

So, my question is, is there a simpler, more ergonomic way of matching on these errors?

Update: What I tried

The suggestion of @edwardw is what I wanted to achieve but everything I tried did not compile.

  1. Using UNIQUE_VIOLATION in the match pattern gives the error:
expected &postgres_shared::error::sqlstate::SqlState, found struct postgres_shared::error::sqlstate::SqlState
  1. Using &UNIQUE_VIOLATION in the match patterns gives the error:
error: to use a constant of type std::borrow::Cow in a pattern, std::borrow::Cow must be annotated with #[derive(PartialEq, Eq)] 

Update 2: Minimal Example

Heres a minimal example to quickly reproduce the errors:

use postgres::error::UNIQUE_VIOLATION;
use postgres::Result;

fn example() {
    let sql_state = &UNIQUE_VIOLATION;

    match sql_state {
        // this match arm works
        s if s == &UNIQUE_VIOLATION => {
            // do something...
        }
        // this match arm does not compile
        &UNIQUE_VIOLATION => {},
        _ => panic!("unexpected state"),
    }
}

Requires postgres as dependency in Cargo.toml

[dependencies]
postgres = "0.15.2"

No, I'm afraid that you can't do much better than this. One "obvious" improvement seems to be:

match e.code().expect("not a sql error") {
    &UNIQUE_VIOLATION => {
        // do something
    },
    _ => panic!("unexpected error"),
}

But it fails to compile with the error:

error: to use a constant of type std::borrow::Cow in a pattern, std::borrow::Cow must be annotated with #[derive(PartialEq, Eq)]

The reason why it should fail isn't obvious. The Rust reference has thispassing sentence :

(...in a path patterns...) Struct and enum constants must have #[derive(PartialEq, Eq)] (not merely implemented).

Turns out this is deliberate on the part of rustc . RFC 1445 explains why and how. In short,

When expanding constants of struct or enum type into equivalent patterns, require that the struct or enum type is decorated with #[structural_match] .

The feature gate #[structural_match] will be kept unstable. This feature gate is only automatically applied if a struct or enum is marked as #[derive(Eq)] but not if manually implemented. The std::borrow::Cow struct, which is part of SqlState you tried to match against, unfortunately falls into later category.

The == operator, however, is not subject to such restriction. So a constant struct or enum can still be used in match guards as you did in the question.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM