What is the difference between the switch and match syntax? - switch-statement

Some languages have a switch expression/statement and some have a match statement. Is there a difference in this semantically, or is it just a different syntax for something that is fundamentally the same.
For example:
Rust has match:
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => 25,
}
(Taken from https://doc.rust-lang.org/book/ch06-02-match.html#the-match-control-flow-operator.)
Java has switch:
switch coin {
case Penny:
return 1;
break;
case Nickel:
return 5;
break;
case Dime:
return 10;
break;
case Quarter:
return 25;
break;
}
(A piece of equivalent code.)

Caveat: this varies wildly depending on the language, of course.
Here I'll work with the Java switch statement, since it's a commonly-used language and its switch semantics seem roughly representative.
A few key differences are:
match is exhaustive (i.e. you have to be able to prove to the compiler that exactly one branch is matched)
match is an expression (although arguably this is more a Rust feature than a match feature), i.e.:
let x = 123;
let s = match x {
0 => "zero",
1 => "one",
_ => "something else",
};
println!("{}", s); // prints "something else"
match performs destructuring:
let x = Some(123);
let s = match x {
None => "nothing".to_string(),
Some(x) => format!("the number: {}", x),
};
println!("{}", s); // prints "the number: 123"
However, you really should take a look at the docs for match and compare them to the docs for the relevant switch feature in another language. These are just the obvious differences when comparing against C-like switches.

Related

How to use values from an array in matching ranges of values with ..= in rust?

I'm learning rust and I found something I can't just find in google.
I was experimenting with match and I wanted to use values from an array with the ..= syntax.
I know I'm doing something wrong, but I only know Js and Python and I feel I'm missing something basic that it's just known but not explained.
pub fn match_statement() {
println!("Match statement----------------------------------");
let mut country_code=0;
let country_codes_range: [i64; 4] = [1,999,50,66];
let country = match country_code {
34 => "Spain",
46 => "Sweden",
country_codes_range[0]..=country_codes_range[1] => "unknown",
_ => "invalid",
};
country_code=invalid_country;
println!(
"The {} country code is {} because is out of the range [{},{}]",
country, invalid_country, country_codes_range[0], country_codes_range[1]
);
}
the error I get is:
expected one of =>, #, if, or |, found [
on the line
country_codes_range[0]..=country_codes_range[1] => "unknown"
I don't know if the issue lies in my calling of items of the array, an incorrect use of ..= or another thing
Also, I guess I would get a similar error if I used a tuple instead of an array?
Thanks for your help
Rust needs to know the "values" of each match arm at compile time, so what you're describing isn't possible, instead you'll get an error saying runtime values cannot be references in patterns.
If you know what country_codes_range will be at compile time, you can make it available at compile time using const:
fn match_statement() {
let country_code = 123;
const COUNTRY_CODES_RANGE: [i64; 4] = [1, 999, 50, 66];
const FIRST: i64 = COUNTRY_CODES_RANGE[0];
const SECOND: i64 = COUNTRY_CODES_RANGE[1];
let country = match country_code {
34 => "spain",
46 => "sweden",
FIRST..=SECOND => "unknown",
_ => "invalid",
};
// ...
}
Note, the intermediate consts FIRST and SECOND are needed because currently Rust's parser doesn't support the a[i] syntax in patterns, though that is a separate problem to having a match use runtime values

How to avoid "_" case in matches on modular numbers

This smells bad:
let millis = time % 1000;
match millis {
0..=199 => do_something(),
200..=599 => do_something_else(),
600..=999 => do_something_altogether_different(),
_ => panic!("There are only 1,000 ms in a second."),
}
The "_" case will never be called, as millis is time % 1000.
How can I rewrite this so that I only have three branches?
Ideally, I'd also like to remove the duplication that is 199/200, 599/600 and 999/1000/0.
If the best solution is not a match I'm happy to use some other control structure.
Rust uses the type for matching, so it doesn't know the bounds as per your logic. But you do.
For being explicit you can use unreachable!:
Indicates unreachable code.
This is useful any time that the compiler can’t determine that some
code is unreachable. For example:
Match arms with guard conditions. Loops that dynamically terminate.
Iterators that dynamically terminate. If the determination that the
code is unreachable proves incorrect, the program immediately
terminates with a panic!.
let millis = time % 1000;
match millis {
0..=199 => do_something(),
200..=599 => do_something_else(),
600..=999 => do_something_altogether_different(),
_ => unreachable!("There are only 1,000 ms in a second."),
}
Otherwise you can consider that the last branch is the default one:
let millis = time % 1000;
match millis {
0..=199 => do_something(),
200..=599 => do_something_else(),
_ => do_something_altogether_different(),
}
I would use a simple if because you have one condition per case and only a small number of cases:
if millis < 200 {
do_something()
} else if millis < 600 {
do_something_else()
} else {
do_something_altogether_different()
}
If you want to use match you could also use if-guards in it:
match millis {
x if x < 200 => do_something(),
x if x < 600 => do_something_else(),
_ => do_something_altogether_different(),
}
Both remove the duplication of 199/200...
You can modify one of the arms so that the compiler understands it to include the case that cannot happen, by making one of the ranges unbounded. This could be considered the match version of the if chain option:
fn foo(time: u32) {
let millis = time % 1000;
match millis {
0..=199 => todo!(),
200..=599 => todo!(),
600.. => todo!(),
}
}
However, I would choose this option only in code that's as obviously-correct as this example; in more complex cases I'd prefer the version with an _ => unreachable!() since that will catch any bugs resulting in actually out-of-range values. (In fact, there's such a possible hidden situation in code very similar to this! If the input is a signed integer, time % 1000 might be negative, which is out of range. Though that'd make the above not compile for not covering those patterns.)
This is just an improvement on Kevin Reid's answer, which removes the duplication by reversing the order:
let millis: u64 = time % 1000;
match millis {
600.. => todo!(),
200.. => todo!(),
0.. => todo!(),
}
We could also leave out one more number, but I'm undecided as to whether this is an improvement.
let millis: u64 = time % 1000;
match millis {
600.. => todo!(),
200.. => todo!(),
_ => todo!(),
}

What is the idiomatic way to do something when an Option is either None, or the inner value meets some condition?

Is there a more idiomatic way to express something like the following?
fn main() {
let mut foo: Option<u8> = None;
match foo {
Some(foo_val) if ! (foo_val < 5) /* i.e. the negation of my acceptance condition */ => {}
_ => { foo.replace(5); }
}
}
It seems like most of the time there's an alternative to having an arm that doesn't do anything, but I've been unable to find one for this particular case.
What I'd like to say is the more direct if foo.is_none() || /* some way to extract and test the inner value */ { ... }, or perhaps some chaining trick that's eluding me.
// in None case
// │ in Some(_) case
// ┌┴─┐ ┌───────────────────┐
if foo.map_or(true, |foo_val| foo_val < 5) {
// ...
}
For more information see Option::map_or.
There are many ways to do it. One of the simplest (and arguably most readable) is something like this:
if foo.unwrap_or(0) < 5 {
...
}
The above will be true in both cases:
when foo is Some with a value smaller than 5;
when foo is None.
In some more complex scenarios, where the "default" value needs to be calculated and performance is critical, you might want to consider unwrap_or_else.
As Lukas suggested, the map_or method can also be used. Note that arguments passed to map_or are eagerly evaluated, so if performance is critical, you might want to consider map_or_else as an alternative.
You can do it with filter (using the negation of your condition) and is_none:
if foo.filter(|&x| !(x < 5)).is_none() {
// Here either foo was None or it contained a value less than 5
}
I'm not sure I completely understand your question but you can try something like that:
fn main() {
let foo: Option<u8> = None;
let result = foo.filter(|foo_val| !(*foo_val < 5) ).unwrap_or(5);
println!("Result: {result}");
}
More example on Playground
The matches! macro seems like a good fit:
if !matches!(foo, Some(a) if a>=5) { foo.replace(5) }
Rust Playground
I'll throw in another solution just for fun....
foo = foo.
or(Some(5)). // if None return Some(5)
map(|x| if x<5 { 5 } else { x });
or (for this specific example)
foo = foo.
or(Some(5)). // if None return Some(5)
map(|x| u8::max(x, 5));
With filter and or,
foo = foo.filter(|a| *a >= 5)
.or(Some(5));
There is the unstable method Option::is_some_and that has been built for exactly this purpose:
if foo.is_some_and(|foo_val| foo_val < 5) {
// ...
}
As it's unstable, it's currently only usable on nightly. See the tracking issue for up to date information.

Does Rust 2018 support "if let" chaining?

I'm parsing a vector of tokens, each of enum type. This means I get a lot of code like:
if v.len() >= 3 {
if let Token::Type1(value1) = &v[0] {
if let Token::Type2(value2) = &v[1] {
if let Token::Type(value3) = &v[2] {
return Parsed123(value1, value2, value3);
}
}
}
}
This is pretty ugly - and I've worked out that I can do this to make it a little nicer:
if v.len() >= 3 {
if let (Token::Type1(value1), Token::Type2(value2), Token::Type3(value3)) =
(&v[0], &v[1], &v[2])
{
return Parsed123(value1, value2, value3);
}
}
But honestly, its not much better.
However, there's some closed issues / RFCs for chaining these conditions and "if let" bits in what feels a lot more ergonomic way -- Tracking issue for eRFC 2497 "if- and while-let-chains take 2" and Support && in if let expressions -- this would let me write something like:
if v.len() >= 3 &&
let Token::Type1(value1) = &v[0] &&
let Token::Type2(value2) = &v[1] &&
let Token::Type3(value3) = &v[2]
{
return Parsed123(value1, value2, value3);
}
However, I can't seem to get this to compile in my copy of nightly Rust with edition="2018" (exact version is 1.32.0-nightly (653da4fd0 2018-11-08)). So either I've got the syntax wrong or I've misinterpreted the RFCs / issues and this feature hasn't landed yet. Either way, I'd love some info on how this feature stands.
RFC #2497 has not been implemented yet. The GitHub issue you linked is only for describing how to deal with the ambiguity.
To enable the second interpretation in the previous section a warning must be emitted in Rust 2015 informing the user that [...] will both become hard errors, in the first version of Rust where the 2018 edition is stable, without the let_chains features having been stabilized.
So no, you cannot use the syntax yet, but instead use tuples as a workaround, as you already did.
if v.len() >= 3 {
if let (Token::Type1(value1), Token::Type2(value2), Token::Type3(value3)) =
(&v[0], &v[1], &v[2])
{
return Parsed123(value1, value2, value3);
}
}
While hellow is correct that RFC #2497 is not yet supported in 2018 (and 2015), I felt the if_chain library mentioned by Michail was worthy of an answer.
The if_chain library provides a macro that transforms some code that is almost in the form of RFC #2497 into valid Rust.
You can write:
if_chain! {
if v.len() >= 3;
if let Token::Type1(value1) = &v[0];
if let Token::Type2(value2) = &v[1];
if let Token::Type3(value3) = &v[2];
then {
return Parsed123(value1, value2, value3);
}
}
which the compiler treats as:
if v.len() >= 3 {
if let Token::Type1(value1) = &v[0] {
if let Token::Type2(value2) = &v[1] {
if let Token::Type(value3) = &v[2] {
return Parsed123(value1, value2, value3);
}
}
}
}
As mentioned in the comments by L.F., in 2020 there is now another alternative. It still doesn't give us chained if let, but does allow us to match on slices - which is enough to make this example quite neat. The code could now be written as
if let [Token::Type1(value1), Token::Type2(value2), Token::Type3(value3), ..] = v {
return Parsed123(value1, value2, value);
}

Destructing a string in Rust

How can I do a complex pattern matching on a string? Something like this:
let res = match data.as_ref() {
"aaa" => "this is aaa",
"bbb" => "this is bbb ",
//...........
//aaa some_data_here
"bbb {data2}" => &format!("this is 'bbb' + some data: {}", data2)
x => &format!("this is not a pattern I know of, it is {}", x),
};
There's no such thing built-in in Rust, so you have to roll your own.
In simple cases you can use slices and helper methods like starts_with(), but if the patterns are more complex, try using regex or parser crates.
match supports "guards" that allow you to run extra code to refine a match:
match string {
s if s.starts_with("bbb ") => format!("this is 'bbb' + some data: {}", &s[4..])
}

Resources