Is there an idiomatic way of chaining Option in Rust? - rust

When deserializing deeply nested structures (e.g. from JSON), it's not uncommon to have to traverse multiple Option types.
For example:
let foo = Foo {
x: Some(Bar {
y: Some(Baz {
z: Some(42),
})
})
};
Is there an idiomatic way of chaining Option to access deeply nested values?
So far I have the following, but neither are as concise as foo.x?.y?.z in other languages that support optional-chaining:
let z = foo.x.as_ref().and_then(|x| x.y.as_ref()).and_then(|y| y.z);
let z = foo.x.as_ref().and_then(|x| x.y.as_ref()?.z);
let z = (|| foo.x.as_ref()?.y.as_ref()?.z)();
It looks like the try_block feature might be a good fit, but it's currently unstable.
let z = try { foo.x.as_ref()?.y.as_ref()?.z };

As you say, the try block would be perfect for this.
In the meantime you can take advantage of the fact that ? works in functions, and wrap your expression in a closure and call it:
let z = (|| foo.x.as_ref()?.y.as_ref()?.z )();
You can write a simple macro to make it a bit nicer:
macro_rules! tryit {
($($e: tt)+) => {
(|| { $($e)+ })()
}
}
Which works basically the same as the try block:
let z = tryit! { foo.x.as_ref()?.y.as_ref()?.z };

Another option if you don't like the immediately called closure is to use a crate like map_for or mdo. Example with map_for:
use map_for::option::FlatMap;
let z = map_for!{
x <- foo.x.as_ref();
y <- x.y.as_ref();
=> y.z }:
Disclaimer: I wrote the map_for crate.

Related

How to define a macro vecvec to initialize a vector of vectors?

Just like vec![2,3,4], can we define a similar macro vecvec to initialize vector of vector. Eg.
let vv0 = vecvec![[2,3,4],[5,6,7]]; // vec of 2 vecs
let vv1 = vecvec![[1,2,3]];
let vv2 = vecvec![[1,2,3], []];
let vv3 = vecvec![[1,3,2]; 2];
You just need to think through the problem. You really only have 2 main cases. The first case being if elements are listed (Ex: a, b, c) and the second where a single value and length are given (Ex: a; b). We can even check our work by reading the documentation for vec!. In the documentation we can see vec! is defined as follows:
macro_rules! vec {
() => { ... };
($elem:expr; $n:expr) => { ... };
($($x:expr),+ $(,)?) => { ... };
}
As you can see, they have 3 cases. We didn't specify the the case were no items are included, but that does not really matter since your macro can call vec! and have it handle that case for you.
We can just copy the cases in their macro and add the functionality inside. The only other issue that might stop you is that [a, b, c] is an expression in of itself. Luckily we can just skip that by specifying items as requiring brackets and pick out the items ourselves before passing them off to vec!.
macro_rules! vecvec {
([$($elem:expr),*]; $n:expr) => {{
let mut vec = Vec::new();
vec.resize_with($n, || vec![$($elem),*]);
vec
}};
($([$($x:expr),*]),* $(,)?) => {
vec![$(vec![$($x),*]),*]
};
}
Instead of defining a new macro. You can initialize the vector of the vector.
In the example below, I'm explicitly setting type. It's not necessary but a good practice.
let vv0:Vec<Vec<u32>> = vec![vec![2,3,4],vec![5,6,7]];
let vv1:Vec<Vec<u32>> = vec![vec![2,3,4],vec![5]];
let vv2:Vec<Vec<u32>> = vec![vec![],vec![5,6,7]];
let vv3:Vec<Vec<u32>> = vec![vec![2,3,4],vec![]];

Using while let with two variables simultaneously

I'm learning Rust and have been going through leetcode problems. One of them includes merging two linked lists, whose nodes are optional. I want to write a while loop that would go on until at least 1 node becomes None, and I was trying to use the while let loop for that.
However, it looks like the while let syntax supports only one optional, e.g.:
while let Some(n) = node {
// do stuff
}
but I can't write
while let Some(n1) = node1 && Some(n2) = node2 {
}
Am I misunderstanding the syntax? I know I can rewrite it with a while true loop, but is there a more elegant way of doing it?
Also, can one do multiple checks with if let? Like if let None=node1 && None=node2 {return}
You can pattern match with Option::zip:
while let Some((n1, n2)) = node1.zip(node2) {
...
}
In addition to what #Netwave said, on nightly you can use the unstable let_chains feature:
#![feature(let_chains)]
while let Some(n1) = node1 && let Some(n2) = node2 {
// ...
}

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.

How do I run a function on a Result or default to a value?

We wish to run a function on a Result or if the result is actually an error have a default value. Something like:
let is_dir = entry.file_type().is_dir() || false
// ^--- file_type() returns a Result
// ^--- default to false if Result is an IOError
At the moment, we're doing it with:
let is_dir = entry.file_type().map(|t| t.is_dir()).unwrap_or(false);
But that seems horribly confusing, to run a map on a single item result. Is there a better way of doing it?
You may be more familiar and comfortable with the map function from its common use in Iterators but using map to work with Results and Options is also considered idiomatic in Rust. If you'd like to make your code more concise you can use map_or like so:
let is_dir = entry.file_type().map_or(false, |t| t.is_dir());
Alternatively, if you find the map unclear, you could use an if or match to be more explicit (and verbose):
let is_dir = if let Ok(file_type) = entry.file_type() {
file_type.is_dir()
} else {
false
};
or
let is_dir = match entry.file_type() {
Ok(file_type) => file_type.is_dir(),
_ => false,
};
Not necessarily better or worse, but an option available to you :)

Resources