Why is a useless match on an unintialized variable allowed?

fn main() {
    let x: i32;
    match x {
        _ => println!("we just matched on x even though it was uninitialized"),
    }
}

I have two questions about the above code:

  1. Why is that code accepted, when most other code that does things with uninitialized variables isn't, even when it doesn't actually read from them? E.g., match x { y => // something that doesn't use y ... and match Some(x) { Some(_) => // ... are both rejected.
  2. Is it ever useful to write code like that? I ask because I saw `unreachable_patterns` emitted on `_` that matches a local of uninhabited type (that is uninitialized) · Issue #134733 · rust-lang/rust · GitHub about getting warnings with such code for uninhabited types, which I wouldn't expect anyone to have discovered or care about if it were useless, but I can't think of any use for it.

The _ pattern is special and doesn't bind anything or "examine" the place it matches against. It's a no-op as far as initialization and borrow checking is concerned (and other things, like it doesn't try to move values either).

2 Likes

It’s not useful to refer to an uninitialized variable alone, but it’s useful to be able to refer to parts of a partially-deinitialized (moved out of) value:

fn do_thing(input: (String, Option<i32>, bool)) {
    drop(input.0); // pretend this is some meaningful usage of it
    
    match input {
        (_, Some(0), false) => println!("conditions met"),
        (_, None, _) => println!("other conditions met"),
        _ => {}
    }
}

If _ didn't have the property of not looking at the value at all, then this wouldn’t compile because input.0 has been moved out of. You could write this code in other ways, but they would require more steps.

You could still decide to ban accessing a whole uninitialized variable, but then that would be a special case that makes life harder for macros and other code generators. It’d be reasonable to have a warning, but in context of the more general allowance, the warning is more like “superfluous code”. In fact, Clippy produces such a warning:

warning: this match could be replaced by its body itself
 --> src/main.rs:3:5
  |
3 | /     match x {
4 | |         _ => println!("we just matched on x even though it was uninitialized"),
5 | |     }
  | |_____^ help: consider using the match body instead: `println!("we just matched on x even though it was uninitialized")`

This warning wouldn’t appear if the match had more than one arm, but there’s no way for this match to have more than one executable arm! (Well, except for if guards.)

5 Likes

Looks all kinds of irrefutable pattern work that don't introduce bindings

fn main() {
    let x;
    match x {
        true | false => println!("to be or not to be"),
    }
}

(playground)

2 Likes