Rust: Why ? is good
What is the question mark (?) operator?
In Rust, the question mark (?) operator is used as an alternate error propagation method for functions that yield Result or Option types. The ? operator is a shortcut that minimizes the amount of code required in a function to quickly return Err or None from the types Result<T, Err>, or Option.
Error propagation is the process of "propagating," spreading up, or returning error information identified in code that is often triggered by a caller function in order for the caller function to resolve the problem correctly.
Let's have a look at how error propagation works in code using the example below:
fn main() -> Result<()> {
let i = match halves_if_even(i) {
Ok(i) => i,
Err(e) => return Err(e),
};
println!("halves of i = {}", i);
match submit_number(i) {
Ok(_) => {
println!("Successfully");
Ok(())
},
Err(e) => return Err(e),
}
}
fn halves_if_even(i: i32) -> Result<i32, Error> {
if i % 2 == 0 {
Ok(i / 2)
} else {
Err(/* something */)
}
}
fn submit_number(i: i32) -> Result<(), Error> {
// ...
}
However, in that it is very verbose. This is where the question mark operator ? comes in.
fn main() -> Result<()> {
let i = 35;
let i = halves_if_even(i)?;
println!("halves of i = {}", i);
submit_number(i)?;
Ok(())
}
fn halves_if_even(i: i32) -> Result<i32, Error> {
// ...
}
fn submit_number(i: i32) -> Result<(), Error> {
// ...
}
What ? does here is equivalent to the match statement above with an addition. This operator desugars into a pattern match.
The nice thing about this code is that one can easily see and audit potential errors: for example, I can see that halves_if_even may result in an error, and a keen-eyed reviewer may see the potential data loss.
Even better, in the event of an error, I can do some type of recovery by opting to match rather than forward the error.
fn main() -> Result<()> {
let i = 35;
let i = halves_if_even(i)?;
match submit_number(i) {
Ok(_) => Ok(()),
Err(err) => recover_from_error(err),
}
}
References
Related Posts
Cargo: Patch Dependencies
There are several scenarios when you will need to override or patch upstream dependencies. Like testing a bugfix of your crates before pushing to crates.io, a non-working upstream crate has a new feature or a bug fix on the master branch of its git repository that you'd want to try, etc. In these cases, the [patch] section of Cargo.toml might be useful.
Cargo: workspace inheritance
Since 1.64.0, Cargo now supports workspace inheritance, so you can avoid duplicating similar field values between crates while working within a workspace. Workspace inheritance can include things like shared version numbers, repository URLs, or rust-version.
Apache OpenDAL in Rust to Access Any Kind of Data Services
OpenDAL is a data access layer that allows users to easily and efficiently retrieve data from various storage services in a unified way such as S3, FTP, FS, Google Drive, HDFS, etc. They has been rewritten in Rust for the Core and have a binding from many various language like Python, Node.js, C, etc..
Fossil Data Platform Rewritten in Rust 🦀
My data engineering team at Fossil recently released some of Rust-based components of our Data Platform after faced performance and maintenance challenges of the old Python codebase. I would like to share the insights and lessons learned during the process of migrating Fossil's Data Platform from Python to Rust.