Compiler not smart enough dealing with match and masked integer?


#1

It seems the compiler isn’t smart enough to figure this code out:

fn main() {
    let a = 99;
    
    match a & 3 {
        0 => println!("0"),
        1 => println!("1"),
        2 => println!("2"),
        3 => println!("3"),
    }
}

Playground:
https://play.rust-lang.org/?gist=e78fa64e91dbfefeec940dfc0c63b0ad&version=stable&mode=debug

It complains that the pattern isn’t exhaustive. So is the compiler just not smart enough, or is there some other way to write the code to make it realize that a & 3 can only be 0,1,2,3?


#2

Context:

I have a CPU emulator which grabs some bits out of the opcode and matches on them to perform different operations (say on an ALU opcode it’d perform add, or sub, or mul, etc). This mask and then match is endemic in the codebase. I end up having to shush the compiler with _ => unreachable!(), everywhere. It’s both silly and dangerous (what if the mask changes at some point in the future, and now the compiler won’t warn me about non-exhaustive pattern since I silenced it with _).


#3

This type of thing has been brought up a few times and there are several github issues on this topic. It boils down to the compiler not being smart enough - it doesn’t do arbitrary expression evaluation, to start.

Your best bet might be to convert your integer opcodes to an enum, and then work off the enum for the balance of your code.


#4

Okay, thank you for confirming. Would you happen to have a link to those issues so I can follow? I’ve tried searching but I’m not sure I’m using the right keywords.

Converting to an enum would … also involve a match, no?


#5

#6

Awesome, thank you!


#7

This should go under the 2018 efforts to improve Rust for low-level coding, but handling integers well in match is a basic feature that for me feels like having a hole.


#8

It would. The idea, however, is to confine this conversion (and match) to a single place (or as few as possible). All subsequent uses of the enum can rely on exhaustive matches. This may or may not help, depending on how much code keeps reexamining the value.


#9

It might not fit at all with what you expect or want, but perhaps consider something like the nom crate to pull apart opcodes and let your code work with more symbolic representations generally, especially for debugging.


#10

Trying to resolve the possible outcomes of boolean/arithmetic expressions in general could easily slow down the compiler to a speed, where it is unusable


#11

A similar analysis is done by the D compiler, and it’s (much) faster than the rustc. This not a proof, but it hits that it can be done efficiently enough for practical uses.


#12

I’m not sure that what is suggested here is feasible. The compiler cannot do this in general, and whether your code compiles should not depend on whether the compiler is clever enough for your special case. Having a _ => unreachable!() that gets optimized away seems like a better solution to me.


#13

I guess that’s the reason currently Rustc has this problem. If you can define in a simple way a natural subset of the problem the compiler is able to face this problem, then it’s a good idea to introduce it into the compiler.

The “if” clauses of match arms can’t be understood by a simple compiler, then if they are present the compiler could bail out. If all the cases are integral numbers then the compiler can reason on the value, and perform the analysis reliably. This covers a significant percentage of low-level use cases (like the embedded focus of the current year), and it’s easy teach to Rust programmers “integral expressions without if”.

It’s a too much cheap and bug-prone solution, that’s OK for a language like C but not good enough for a modern reliable language as Rust.