Does Rust 2018 support "if let" chaining? - rust

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);
}

Related

Rust lifetimes in if statement

I have an if statement in a for loop, and I want it to create a variable with the lifetime of that iteration of the for loop.
for condition_raw in conditions_arr {
println!("{}", condition_raw);
let matching = !condition_raw.contains('!');
if matching {
let index = condition_raw.find('=').unwrap_or_default();
} else {
let index = condition_raw.find('!').unwrap_or_default();
}
let key = &condition_raw[..index];
}
let key = &condition_raw[..index]; currently throws cannot find value index in this scope
not found in this scope rustc E0425
I'll ignore the condition variable which does not seem to be used at all in your example.
A let statement creates a binding that holds at most for the current scope. For this reason, when you create the index variable inside the if, you are not making it accessible anywhere else. There are two ways to solve this issue.
The first way is to explicitly declare index as being part of the outer scope, and only define it inside the if statement.
for condition_raw in conditions_arr {
let matching = !condition_raw.contains('!');
let index;
if matching {
index = condition_raw.find('=').unwrap_or_default();
} else {
index = condition_raw.find('!').unwrap_or_default();
}
let key = &condition_arr[..index];
}
There is no risk of accidentally not defining index, since Rust will make sure that index is defined (exactly once) in all possible branching of your code before it is used. Yet, it's not a pretty solution because it violates a "locality" principle, that is that pieces of code should have effects on or pieces of code that are sufficiently close. In this case, the let index; is not too far from its definition, but it could be arbitrarily far, which makes it painful for someone who reads your code to remember that there is a declared but not yet defined.
Alternatively, you could use the fact that most things in Rust are expressions:
for condition_raw in conditions_arr {
let matching = !condition_raw.contains('!');
let index = if matching {
condition_raw.find('=').unwrap_or_default();
} else {
condition_raw.find('!').unwrap_or_default();
}
let key = &condition_arr[..index];
}
But, in fact, you could factorize your code even more, which is usually better:
for condition_raw in conditions_arr {
let matching = !condition_raw.contains('!');
let index = condition_raw.find(if matching {
'='
} else {
'!'
}).unwrap_or_default();
let key = &condition_arr[..index];
Or, even more
for condition_raw in conditions_arr {
let index = condition_raw
.find('!')
.or_else(|| condition_raw.find('='))
.unwrap_or_default();
let key = &condition_arr[..index];
}
An idiomatic way to assign variables from an if else statement is as follows:
let index: usize = if matching {
condition_raw.find('=').unwrap_or_default()
} else {
condition_raw.find('!').unwrap_or_default()
};
Idiomatic way of assigning a value from an if else condition in Rust
In Rust, an if/else block is an expression. That is to say, the block itself has a value, equivalent to the last expression in whatever section was executed. With that in mind, I would structure your code like this:

error handling when unwrapping several try_into calls

I have a case where I need to parse some different values out from a vector.
I made a function for it, that returns a option, which either should give a option or a None, depending on whether the unwrapping succeeds.
Currently it looks like this:
fn extract_edhoc_message(msg : Vec<u8>)-> Option<EdhocMessage>{
let mtype = msg[0];
let fcnt = msg[1..3].try_into().unwrap();
let devaddr = msg[3..7].try_into().unwrap();
let msg = msg[7..].try_into().unwrap();
Some(EdhocMessage {
m_type: mtype,
fcntup: fcnt,
devaddr: devaddr,
edhoc_msg: msg,
})
}
But, I would like to be able to return a None, if any of the unwrap calls fail.
I can do that by pattern matching on each of them, and then explicitly return a None, if anything fails, but that would a lot of repeated code.
Is there any way to say something like:
"if any of these unwraps fail, return a None?"
This is exactly what ? does. It's even shorter than the .unwrap() version:
fn extract_error_message(msg: Vec<u8>) -> Option<EdhocMessage> {
let m_type = msg[0];
let fcntup = msg[1..3].try_into().ok()?;
let devaddr = msg[3..7].try_into().ok()?;
let edhoc_msg = msg[7..].try_into().ok()?;
Some(EdhocMessage {
m_type,
fcntup,
devaddr,
edhoc_msg
})
}
See this relevant part of the Rust Book.

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.

What is the difference between the switch and match syntax?

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.

How do I assert that a Rust futures-preview future has not yet resolved

I'm trying to bring my dependencies up-to-date. I have some code that uses futures-preview and futures-timer. Earlier versions of futures-timer included the ability to put a timeout on a future, and I have a test that uses this feature, but it was removed from recent releases.
Is there a straightforward way of just checking to see if the future has resolved and then fail the test if it has? (Also, is "resolved" the correct word?)
I tried playing around with f1.poll() but couldn't quite figure out the return type and how to work with it.
I also tried using the timeout from async_std but I got a bunch of errors along these lines:
error[E0008]: cannot bind by-move into a pattern guard
--> /Users/nathan/.cargo/registry/src/github.com-1ecc6299db9ec823/async-std-0.99.9/src/net/driver/mod.rs:207:17
|
207 | Err(err) if err.kind() == io::ErrorKind::WouldBlock => {}
| ^^^ moves value into pattern guard
|
= help: add `#![feature(bind_by_move_pattern_guards)]` to the crate attributes to enable
Unless I'm mistaken, that's asking me to modify async-std, right? Is there a way around that?
Here's my test:
#[cfg(test)]
mod tests {
use super::*;
use crate::threadpool::ThreadPool;
use futures_timer::TryFutureExt;
// ....
#[test]
fn test_trigger_future_untriggered() {
let tf = TriggerFuture::default();
let f1 = tf.future();
ThreadPool::default()
.spawn_wait::<_, _, ()>(async {
f1.map(|_| Result::<(), std::io::Error>::Ok(()))
.timeout(std::time::Duration::from_millis(50))
.await
.expect_err("expected timeout");
Ok(())
})
.expect("expected!");
}
}
Update: Here's one more thing I tried. With futures-timer v1.0.1, it fails as expected if I uncomment the tf.trigger(), but it runs forever when I don't. What's wrong here?
#[test]
fn test_trigger_future_untriggered() {
let tf = TriggerFuture::default();
let f1 = tf.future();
// tf.trigger();
ThreadPool::default()
.spawn_wait::<_, _, _>(async {
use futures_timer::Delay;
let delay = Delay::new(std::time::Duration::from_millis(50));
future::select(f1, delay).then(|either| {
match either {
Either::Left((_, b)) => b.map(move |y| (Err("this future shoudn't resolve"), y)).left_future(),
Either::Right((_, a)) => a.map(move |x| (Ok("expected timeout"), x)).right_future(),
}
}).await.0
})
.expect("expected!");
}
Here is my Cargo.toml as requested:
[package]
name = "shared_futures"
version = "0.1.0"
publish = false
edition = "2018"
[dependencies]
futures-preview = { version = "0.3.0-alpha.19", features = [ "async-await", "compat" ] }
crossbeam-channel = "0.3.9"
num_cpus = "1.10.1"
# Note: the timeout feature was removed in futures-timer v0.6
futures-timer = "1.0.1"
[features]
sanitizer_safe = []
[lib]
name = "shared_futures"
crate-type = ["rlib"]
Could you kindly post your Cargo.toml and any use statements that belong to the example code? I can't run your code but after looking over your test example and error message I believe there are some related questions (and here) that may help you.
Can you try updating to the latest version of nightly and see if it fixes the issue? It appears the feature was recently stabilized.
rustup update nightly
This is assuming you are using the nightly version of the rust compiler.
Best of luck.
Figured it out (with a little help). My final attempt was close but was doing too much. This is a simplified version that actually works:
#[test]
fn test_trigger_future_untriggered() {
let tf = TriggerFuture::default();
let f1 = tf.future();
tf.trigger();
ThreadPool::default()
.spawn_wait::<_, _, &str>(async {
use futures_timer::Delay;
let delay = Delay::new(std::time::Duration::from_millis(50));
let res = future::select(f1, delay).await;
match res {
Either::Left(..) => Err("The TriggerFuture resolved first"),
Either::Right(..) => Ok(()),
}
})
.expect("The Delay should have resolved first");
}

Resources