Undefined constant in match

I'm trying to find out why this code even compiles. If you use an undefined constant in a match (or: a typo), it will compile and catch-all. Can anyone explain if this is expected behavior and why?

const BEAR: usize = 1;
const LION: usize = 2;
const DUCK: usize = 3;

// This works as expected
fn foo(x: usize) {
	match x {
		BEAR => println!("[Foo] A Bear!"),
		LION => println!("[Foo] A Lion!"),
		DUCK => println!("[Foo] A Duck!"),
		_ => println!("[Foo] What is this? {}!", x),
	}
}

// This is strange
fn bar(x: usize) {
	match x {
		DODO => println!("[Bar] A Dodo?"), // DODO is not defined anywhere...
		BEAR => println!("[Bar] A Bear!"),
		LION => println!("[Bar] A Lion!"),
		DUCK => println!("[Bar] A Duck!"),
		_ => println!("What is this? {}!", x),
	}
}


fn main() {
    println!("Hello, world!");
	foo(BEAR);
	foo(LION);
	foo(DUCK);
	foo(42);
	bar(BEAR);
	bar(LION);
	bar(DUCK);
	bar(42);
}

This will print:

Hello, world!
[Foo] A Bear!
[Foo] A Lion!
[Foo] A Duck!
[Foo] What is this? 42!
[Bar] A Dodo?
[Bar] A Dodo?
[Bar] A Dodo?
[Bar] A Dodo?

Are you taking a look at the warnings the compiler gives you?

Ooops, I see. But still: Why?

I do assume, that DODO is bound by the match to whatever value x has… Have you tried inspecting it?

You could also have written that catch all case in the first match as
n => println!("What is this? {}", n)
Where n would have bound to whatever you got that didn't match a previous case.
The same case now applies to DODO, since it isn't a constant or some specific pattern to match some specific case, the compiler treats it as a general catch-all case where you get whatever was put in there, as long as no previous case matched it. Since DODO happens to be the first case, it matches everything, therefore shadowing the other cases (and that's likely what the compiler warned you about). Try moving the DODO case 1, 2.. line down to see how it behaves.
I hope that clarified this a bit

Because from the compiler's perspective, DODO is no different to dodo or x. It doesn't know you think it's supposed to be a constant. There's no way of fixing this without either requiring "constant = all caps", or making variable binding harder, neither of which seems very likely to ever happen.

So, yeah. Warnings are there for a reason.

2 Likes

That's why I prefer the following syntax for matching against const bindings:

match expr {
  x if x == CONSTANT => { /* ... */ },
  // etc.
}

Rewriting your example, we get:

error[E0425]: cannot find value `DODO` in this scope
  --> src/lib.rs:18:13
   |
18 |         x if x == DODO => println!("[Bar] A Dodo?"), // DODO is not defined anywhere...
   |                   ^^^^ not found in this scope

error: aborting due to 2 previous errors

I think this has slightly different semantics. AFAIK, match uses structural equality, while your code uses the Eq trait.

These should boil down to the same behaviour for most basic types, but one should still note the difference.

1 Like

Thanks everyone, I think I understand what's going on here. And yes, I need to pay more attention to warnings...

1 Like

You could also match on paths, like self::LION, so it's clear that you're not introducing a new binding. Then your undefined constant gives:

error[E0531]: cannot find unit struct/variant or constant `DODO` in module `self`
6 Likes

Indeed! I was about to write about that but I gave up for the sake of simplicity :sweat_smile: ;

But now that we have this very neat "trick":

this should provide the best solution; great idea @cuviper !

2 Likes