return ... is in and on itself an expression, which evaluates to the diverging / never type !, as most unreachable expressions do. It thus typechecks with the input of foo (! can be coerced to any type); that's why the compilation goes "without a warning" except for the unreachable_code that follows.
The main offender here, imho, are the non-lazy or methods. Many people get them wrong, because their name seems to suggest the opposite (i.e., that they are lazy). That's why or_else is to be preferred, and tools like clippy will lint against usage* of .or() so I guess / hope it would catch your initial error.
*with a non trivial expression in or; things like .or(0) are accepted, but they are still not better than .or_else(|| 0), since such cases will be inlined anyways.
Note that or_else(|| return 42) is the same as .or_else(|| 42), since a return refers to the innermost function / closure. That's why in some cases you have no other choice but to perform a match or an if let explicitely.