Why this compile?

Hi, I am reading a Book and found this code

enum Location {
  Point(i32),
  Range(i32, i32)
}
fn main() {
  let l: Location = Location::Range(0, 5);
  let n = match l {
    Location::Point(_) => -1,
    Location::Range(_, n) => n,
    Location::Range(0, _) => 0,
    _ => -2
  };
  println!("{n}");
}

Rust say it will not compile code with bugs, but this code is a very big bug, but it compiles. Rust only give warning about it.

warning: unreachable pattern

If code
Location::Range(0, _) => 0,
can't be reached, but programmer expects it to be reached it is a big bug as it will give wrong result.

Let me ask you a question that will guide you in the right direction. What do you expect Location::Range(_, n) to do?

1 Like

Well, it does emit a warning. So don't ignore warnings.

You can turn it (or all warnings) into an error using #![deny(...)] anyway.

Rust doesn't claim to catch all bugs at compile-time – that's impossible.

6 Likes

Safe Rust guarantees are primarily around memory safety and race conditions, not logic errors in general. So sure, Rust will compile code with logic errors -- and the same is true of every other programming language. The language can't omnipotently know what the program is supposed to do.

Not necessarily, maybe they just wrote more code that necessary because they didn't realize a certain case was impossible, or got some privacy annotations wrong and didn't expose some function that was supposed to be public.

Anyway, if you want warnings to be errors, you can do that.

1 Like

Because enum Location variant Range have two numbers I assume this numbers means min and max range.
So, line Location::Range(_, n) => n, will return max number of Range. But at time min is 0 it should return 0, but this line is unreachable.

_ matches anything, as so does an identifier, so (_, n) matches both fields, unconditionally.

Great. You are only missing one key piece: Patterns are evaluated in order.

In the same way, values go through each pattern in a match, and at the first pattern the value β€œfits,” the value falls into the associated code block to be used during execution.

https://doc.rust-lang.org/book/ch06-02-match.html#the-match-control-flow-construct

So you need swap the second and third pattern, as the 0 is a kind of special case and need to go first

1 Like

I think you misunderstood me. I know that locations after Location::Range(_, n) => n, is unreachable because this line fits all possible Range variants, so it should be at the end of arms.

I am only asking why Rust treats unreachable code as warning and not as error.

As quinedot wrote it is logical error, but this type of logical error is caught and most of time means a bug.
I think this type of bug should be treated the same as not exhausting all arms.
If you use not all arms (this is error in Rust) or too many arms (because of unreachable warning) it should be error and not a warning.

Because it is not necessarily an error. If you declare a variable and never use it, that's also dead code. It's not an error. It is treated as such in Go, and that annoys the hell out if people trying to debug their code by commenting out parts of it.

If you were to write the following function, should Rust (or whatever programming language you are used to) reject it just because you were expecting it to return 1 instead of 0?

fn what(var: usize) -> usize {
  if var >= 3 {
    return 0;
  }
  
  return 1; 
}

let result = what(3);

Wrong; non-exhaustive arms would cause memory unsafety by falling through to who knows what arbitrary code path.

As mentioned by several people here, and has repeatedly been ignored by you, all warnings can be turned into errors.

Here is the reasoning why it's a warning and not an error:

Dead code: this is a lint. While the user probably doesn't want dead code in their crate, making this a hard error would make refactoring and development very painful.

5 Likes