I’m not sure which one is the best approach, but here’s a few…
Using unstable try_blocks
#![feature(try_blocks)]
pub fn compare_paths_for_size(path_a: &str, path_b: &str) -> Ordering {
Option::unwrap_or(
try {
file_info_size(path_a)
.ok()?
.partial_cmp(&file_info_size(path_b).ok()?)?
},
Ordering::Equal,
)
}
and similarly the stable workaround for lack of try
blocks, by defining and immediately calling a closure to make a scope for ?
syntax:
pub fn compare_paths_for_size(path_a: &str, path_b: &str) -> Ordering {
(|| {
file_info_size(path_a)
.ok()?
.partial_cmp(&file_info_size(path_b).ok()?)
})()
.unwrap_or(Ordering::Equal)
}
Using and_then
a few times:
pub fn compare_paths_for_size(path_a: &str, path_b: &str) -> Ordering {
file_info_size(path_a)
.ok()
.and_then(|size_a| {
file_info_size(path_b)
.ok()
.and_then(|size_b| size_a.partial_cmp(&size_b))
})
.unwrap_or(Ordering::Equal)
}
Using unstable let_chains
#![feature(let_chains)]
pub fn compare_paths_for_size(path_a: &str, path_b: &str) -> Ordering {
if let Ok(size_a) = file_info_size(path_a)
&& let Ok(size_b) = file_info_size(path_b)
&& let Some(val) = size_a.partial_cmp(&size_b) {
val
} else {
Ordering::Equal
}
}
or the stable, but slightly more repetitive (w.r.t. the else
case) let … else
syntax:
pub fn compare_paths_for_size(path_a: &str, path_b: &str) -> Ordering {
let Ok(size_a) = file_info_size(path_a) else { return Ordering::Equal };
let Ok(size_b) = file_info_size(path_b) else { return Ordering::Equal };
let Some(val) = size_a.partial_cmp(&size_b) else { return Ordering::Equal };
val
}
After writing this, I kind-of find the last two the most readable; slightly preferring let_chain
s, but they’re unstable, so then: let … else
it is?
Edit: Here’s another fun approach I came up with
pub fn compare_paths_for_size(path_a: &str, path_b: &str) -> Ordering {
'block: {
let Ok(size_a) = file_info_size(path_a) else { break 'block None };
let Ok(size_b) = file_info_size(path_b) else { break 'block None };
size_a.partial_cmp(&size_b)
}
.unwrap_or(Ordering::Equal)
}
Also, the closure approach becomes more nicely readable if the intermediate values are named:
pub fn compare_paths_for_size(path_a: &str, path_b: &str) -> Ordering {
(|| {
let size_a = file_info_size(path_a).ok()?;
let size_b = file_info_size(path_b).ok()?;
size_a.partial_cmp(&size_b)
})()
.unwrap_or(Ordering::Equal)
}
as does the try
blocks approach…
#![feature(try_blocks)]
pub fn compare_paths_for_size(path_a: &str, path_b: &str) -> Ordering {
Option::unwrap_or(
try {
let size_a = file_info_size(path_a).ok()?;
let size_b = file_info_size(path_b).ok()?;
size_a.partial_cmp(&size_b)?
},
Ordering::Equal,
)
}
The original versions of those two also suffer from rustfmt formatting and can probably be made slightly more readable by re-formatting alone; e.g. in each case, the first .ok()?
should IMO definitely not go in its own line. And the and_then
, too, is severely hindered by rustfmt… using a more Haskell-manual-monad-style-esque formatting seems nicer to me
pub fn compare_paths_for_size(path_a: &str, path_b: &str) -> Ordering {
file_info_size(path_a).ok().and_then( |size_a|
file_info_size(path_b).ok().and_then( |size_b|
size_a.partial_cmp(&size_b)
)).unwrap_or(Ordering::Equal)
}
though I do find rustfmt useful in general, so syntax that plays more nicely with it has some merits.