I get little confused when learning about control flow. I don't understand the difference between if-let and match.
fn main() {
let some_u8_value = Some(8u8);
// println!(" {} ", some_u8_value);
if let Some(value) = some_u8_value {
println!(" {} ", value);
} else {
println!("not a num");
}
match some_u8_value {
Some(value) => println!(" {} ", value),
None => println!("not a num"),
}
}
Why do we need if-let?
Why do we need if-let?
We don't need it, it's a convenience feature. Per RFC 160 which introduced it:
[if let] allows for refutable pattern matching without the syntactic and semantic overhead of a full match, and without the corresponding extra rightward drift.
and
The idiomatic solution today for testing and unwrapping an Option looks like
match optVal {
Some(x) => {
doSomethingWith(x);
}
None => {}
}
This is unnecessarily verbose, with the None => {} (or _ => {}) case being required, and introduces unnecessary rightward drift (this introduces two levels of indentation where a normal conditional would introduce one).
[explanation of the issues with using a simple if with is_some and unwrap]
The if let construct solves all of these problems, and looks like this:
if let Some(x) = optVal {
doSomethingWith(x);
}
Related
I have a state machine written in Rust which needs to perform the same action for two states.
Both states are variants of an enum but contain a different number of elements.
match self.state {
RunState::ACCUMULATE(byte, count) | RunState::ESCAPE(count) => todo!(),
_ => todo!()
}
The example is invalid source code because byte is not bound in all patterns.
This could be solved by binding byte to a literal zero in the second pattern but i don't know how to archive that.
I`am currently matching both patterns separately which leads to code duplication which i would like to avoid.
Thank you for your attention.
Patterns are designed to match by structure and bind variables based on the value being matched. It is not designed to introduce arbitrary bindings. You should use separate match arms and perhaps use a helper function if you're concerned about duplication:
match self.state {
RunState::ACCUMULATE(byte, count) => helper(byte, count),
RunState::ESCAPE(count) => helper(0, count),
...
}
Or you can introduce a function for your enum that returns the needed values like:
impl RunState {
fn to_parts(&self) -> (u8, usize) { // or Option<(u8, usize)> if its not applicable to all
match self.state {
RunState::ACCUMULATE(byte, count) => (byte, count),
RunState::ESCAPE(count) => (0, count),
...
}
}
}
You can solve your issue by not binding byte in any branch:
match self.state {
RunState::ACCUMULATE(_, count) | RunState::ESCAPE(count) => todo!(),
_ => todo!()
}
If you need byte in the ACCUMULATE case, then the code isn't strictly speaking the same for both, but this can be accomplished with an extra step to extract byte:
match self.state {
RunState::ACCUMULATE(_, count) | RunState::ESCAPE(count) => {
let byte = if let RunState::ACCUMULATE (byte, _) = self.state { byte } else { 0 };
todo!()
},
_ => todo!()
}
In this below example (here is the Rust playground), I need to filter values in an iterator that match a specific pattern.
One way I found is to use a match returning an Option in filter_map:
#[derive(Copy, Clone, Debug)]
struct Entity(i32);
enum Event {
EntityInserted(Entity),
EntityRemoved(Entity),
}
fn main() {
let [entity1, entity2] = [Entity(1), Entity(2)];
let events = vec![
Event::EntityInserted(entity1),
Event::EntityRemoved(entity1),
Event::EntityInserted(entity2),
];
let inserted_entities: Vec<_> = events
.iter()
.filter_map(|event| match event { // <---
Event::EntityInserted(entity) => Some(entity), // <--- Those lines
_ => None, // <--- here
}) // <---
.collect();
dbg!(inserted_entities);
}
Is there a more idiomatic way to have this behavior ?
As for the filter_map, I think that's the way to go.
Curiously, I wrote a macro yesterday that generates a Some in case the if was "successful", and otherwise evaluates to None:
macro_rules! if_then_some {
($cond: expr, $val: expr) => {
if $cond { Some($val) } else { None }
};
(let $pattern:pat = $expr: expr, $val: expr) => {
if let $pattern = $expr { Some($val) } else { None }
};
}
As it stands, it can be used for checking bool-conditions and if let-conditions. In your case it could be used like this:
let inserted_entities: Vec<_> = events
.iter()
.filter_map(|event|
if_then_some!(let Event::EntityInserted(entity)=event, entity)
)
.collect();
It is disputable if this is "idiomatic", but imho it is terse yet quite readable.
A bit off-topic: On the other hand, whenever I see if let with something else than Option or Result, I am wary that the compiler does not warn me if I add new variants that should be checked in these conditions.
I am confused about the Some(T) keyword.
I want to check for two variables, if the value is defined (not None). If that is the case, the value of this variables is processed.
I know the match pattern which works like this:
match value {
Some(val) => println!("{}", val),
None => return false,
}
If I use this pattern, it will get very messy:
match param {
Some(par) => {
match value {
Some(val) => {
//process
},
None => return false,
}
},
None => return false,
}
This can't be the right solution.
The is a possibility, to ask if the param and value is_some() That would effect code like that:
if param.is_some() && value.is_some() {
//process
}
But if I do it like that, I always have to unwrap param and value to access the values.
I thought about something like this to avoid that. But this code does not work:
if param == Some(par) && value == Some(val) {
//process
}
The idea is that the values are accessible by par and val like they are in the match version.
Is there any solution to do something like this?
If I have several Option values to match, I match on a tuple of the values:
enum Color {
Red,
Blue,
Green,
}
fn foo(a: Option<Color>, b: Option<i32>) {
match (a, b) {
(Some(Color::Blue), Some(n)) if n > 10 => println!("Blue large number"),
(Some(Color::Red), _) => println!("Red number"),
_ => (),
}
}
fn main() {
foo(Some(Color::Blue), None);
foo(Some(Color::Blue), Some(20));
}
This allows me to match the combinations that are interesting, and discard the rest (or return false, if that is what you want to do).
If your function is processing multiple Option values, and would like to discard them if they're not Some, your function could return an Option itself:
fn foo(param: Option<usize>, value: Option<usize>) -> Option<usize> {
let result = param? + value?;
Some(result)
}
This will short-circuit the function in case there's a None value stored in either param or value.
Please read the book for more information on the ? operator.
If your function can't return an Option, you can still get away with destructuring using if let or match:
let x = if let (Some(p), Some(v)) = (param, value) {
p + v
} else {
return 0;
}
let x = match (param, value) {
(Some(p), Some(v)) => p + v,
(Some(p), _) => p,
(_, Some(v) => v,
_ => return 0,
}
Please read What is this question mark operator about? for more information on the ? operator
Please read this chapter in Rust by Example for more information on destructuring multiple things at once
There's a couple more alternatives not yet listed:
If you're willing to use experimental features (and hence the nightly compiler) you can use a try block as an alternative of extracting a function.
#![feature(try_blocks)]
fn main() {
let par: Option<f32> = Some(1.0f32);
let value: Option<f32> = Some(2.0f32);
let x: Option<f32> = try { par? + value? };
println!("{:?}", x);
}
Another alternative is to use map which only applies if the value is not None
let x: Option<f32> = par.map(|p| value.map(|v| p + v));
How do I persuade the Rust compiler that the internal match expression is fine here, as the outer match has already restricted the possible types?
enum Op {
LoadX,
LoadY,
Add,
}
fn test(o: Op) {
match o {
Op::LoadX | Op::LoadY => {
// do something common with them for code reuse:
print!("Loading ");
// do something specific to each case:
match o {
// now I know that `o` can only be LoadX | LoadY,
// but how to persuade the compiler?
Op::LoadX => print!("x"), /* LoadX specific */
Op::LoadY => print!("y"), /* LoadY specific */
_ => panic!("shouldn't happen!"),
}
println!("...");
}
Op::Add => println!("Adding"),
}
}
fn main() {
test(Op::LoadX);
test(Op::LoadY);
test(Op::Add);
}
I tried two approaches, but neither seems to work.
Name the or-pattern and then match using that name:
match o {
load#(Op::LoadX | Op::LoadY) => {
// ...
match load {
// ...
}
}
That's not valid Rust syntax.
Name and bind every constructor:
match o {
load#Op::LoadX | load#Op::LoadY => {
// ...
match load {
//...
}
}
That still doesn't satisfy the exhaustiveness check, hence the same error message:
error[E0004]: non-exhaustive patterns: `Add` not covered
--> src/main.rs:14:19
|
14 | match load {
| ^ pattern `Add` not covered
Is there any idiomatic way of solving this problem or should I just put panic!("shouldn't happen") all over the place or restructure the code?
Rust playground link
I think that you just need to refactor your code, obviously LoadX and LoadY are very close. So I think you should create a second enumeration that regroup them:
enum Op {
Load(State),
Add,
}
enum State {
X,
Y,
}
fn test(o: Op) {
match o {
Op::Load(state) => {
// do something common with them for code reuse
print!("Loading ");
// do something specific to each case:
match state {
State::X => print!("x"),
State::Y => print!("y"),
}
println!("...");
}
Op::Add => println!("Adding"),
}
}
fn main() {
test(Op::Load(State::X));
test(Op::Load(State::Y));
test(Op::Add);
}
This make more sense to me. I think this is a better way to express what you want.
You cannot. Conceptually, nothing prevents you from doing o = Op::Add between the outer match and the inner match. It's totally possible for the variant to change between the two matches.
I'd probably follow Stargateur's code, but if you didn't want to restructure your enum, remember that there are multiple techniques of abstraction in Rust. For example, functions are pretty good for reusing code, and closures (or traits) are good for customization of logic.
enum Op {
LoadX,
LoadY,
Add,
}
fn load<R>(f: impl FnOnce() -> R) {
print!("Loading ");
f();
println!("...");
}
fn test(o: Op) {
match o {
Op::LoadX => load(|| print!("x")),
Op::LoadY => load(|| print!("y")),
Op::Add => println!("Adding"),
}
}
fn main() {
test(Op::LoadX);
test(Op::LoadY);
test(Op::Add);
}
should I just put panic!("shouldn't happen")
You should use unreachable! instead of panic! as it's more semantically correct to the programmer.
I'm writing a toy programming language in Rust. I prototyped the parser logic in Ruby:
def rd_tree(chars)
loop do
case c = chars.next
when /\s/
# whitespace stuff
when "("
# open paren stuff
when ")"
# close paren stuff
else
# default stuff
end
end
end
And now I'm converting it to Rust:
fn rd_tree(chars: std::str::Chars) {
while let Some(c) = chars.next() {
if c.is_whitespace() {
// whitespace stuff
} else if c == '(' {
// open paren stuff
} else if c == ')' {
// close paren stuff
} else {
// default stuff
}
}
}
I resorted to using an if, else-if chain because as far as I can tell, Rust's match feature is limited to destructuring, enums, and type patterns. Is there a way to match on regexes or boolean functions? If not, is there a more idiomatic pattern here than if, else-if? I expect the logic to have more branches in the future and I want it to stay neat.
Not yet. The match patterns must be composed of things that can be statically verified by the compiler.
However, you can use a match guard:
fn rd_tree(chars: std::str::Chars) {
while let Some(c) = chars.next() {
match c {
c if c.is_whitespace() => {}
'(' => {}
')' => {}
_ => {}
}
}
}
A match guard allows you to run a function against whatever the pattern matched.
In the future, constant evaluation may be improved to allow calling functions in place of a pattern:
#[derive(PartialEq, Eq)]
struct Foo {
f: usize,
g: usize,
}
impl Foo {
const fn repeated(x: usize) -> Self {
Foo { f: x, g: x }
}
}
fn main() {
let f = Foo { f: 0, g: 1 };
match f {
const { Foo::repeated(22) } => println!("hi"),
_ => println!("1"),
}
}
This work is tracked in issue #57240. RFC 2920 "const expressions and patterns" (and its tracking issue #76001) are also relevant.
It's not immediately obvious to me how this would work with your exact example or a regex without a substantial amount of effort though.