There is context that can convert an optional value to anyhow::Error which is very convenient.
Simplest way to unwrap an option and return Error if None (Anyhow)
However, how do we test that in unit-tests?
Let's say we have a foo like this:
fn foo(input: i32) -> Result<i32> {
// this only keep odd numbers
let filtered = if input % 2 == 0 { Some(input) } else { None };
filtered.context("Not a valid number")
}
It is easy to test that it is valid output, or that the output is an error. But how do we test the error message from the context?
mod test {
use super::*;
#[test]
fn test_valid_number() -> Result<()> {
let result = foo(4)?;
assert_eq!(result, 4);
Ok(())
}
#[test]
fn test_invalid_number() -> Result<()> {
let result = foo(3);
assert!(result.is_err());
Ok(())
}
// error[E0599]: no method named `message` found for struct `anyhow::Error` in the current scope
// --> src/main.rs:33:40
// |
// 33 | assert_eq!(result.unwrap_err().message(), "Not a valid number");
// | ^^^^^^^ method not found in `anyhow::Error`
#[test]
fn test_invalid_number_error_message() -> Result<()> {
let result = foo(3);
assert_eq!(result.unwrap_err().message(), "Not a valid number");
Ok(())
}
}
You can use .chain() and .root_cause() to deal with levels of context and use .downcast_ref() or format! to handle the specific error. For example, lets say you had 2 levels of context.
use anyhow::*;
fn bar(input: i32) -> Result<i32> {
// this only keep odd numbers
let filtered = if input % 2 == 0 { Some(input) } else { None };
filtered.context("Not a valid number")
}
fn foo(input: i32) -> Result<i32> {
return bar(input).context("Handled by bar")
}
In this example the chain would be of the errors "Handled by bar" -> "Not a valid number".
#[test]
fn check_top_error() -> Result<()> {
let result = foo(3);
let error = result.unwrap_err();
// Check top error or context
assert_eq!(format!("{}", error), "Handled by bar");
// Go down the error chain and inspect each error
let mut chain = error.chain();
assert_eq!(chain.next().map(|x| format!("{x}")), Some("Handled by bar".to_owned()));
assert_eq!(chain.next().map(|x| format!("{x}")), Some("Not a valid number".to_owned()));
assert_eq!(chain.next().map(|x| format!("{x}")), None);
Ok(())
}
#[test]
fn check_root_cause() -> Result<()> {
let result = foo(3);
let error = result.unwrap_err();
// Equivalent to .chain().next_back().unwrap()
let root_cause = error.root_cause();
assert_eq!(format!("{}", root_cause), "Not a valid number");
Ok(())
}
Now, you may have been wondering about my use of format!. It turns out a better solution exists involving downcast_ref, but it requires that your context implement std::error::Error and str does not. Here is an example of this taken directly from the anyhow documentation.
use anyhow::{Context, Result};
fn do_it() -> Result<()> {
helper().context(HelperFailed)?;
...
}
fn main() {
let err = do_it().unwrap_err();
if let Some(e) = err.downcast_ref::<HelperFailed>() {
// If helper failed, this downcast will succeed because
// HelperFailed is the context that has been attached to
// that error.
}
}
As a side note, you may find it easier to use .then() or .then_some() for cases like if input % 2 == 0 { Some(input) } else { None } where you create Some based on a boolean. Simply put, if abc { Some(xyz) } else { None } is equivalent to abc.then(|| xyz). .then_some() passes by value instead of using a closure so I don't usually use it.
Related
in order to learn Rust, I try to create small snippets to apply what we learn in the Rust book and implement good practices.
Have a small function to list content of a repository :
use std::{io, fs, path::PathBuf, path::Path};
pub fn get_directory_content(path: &str) -> Result<Vec<PathBuf>, io::Error> {
let _path: bool = Path::new(path).is_dir();
match _path {
true => {
let mut result = vec![];
for file in fs::read_dir(path).unwrap() {
result.push(file.unwrap().path());
}
Ok(result)
},
false => Err(io::Error::new(io::ErrorKind::Other, " is not a directory")),
}
}
my goal is to be able to catch the error if the folder does not exist without triggering a panic.
in main.rs :
mod utils;
fn main() {
let directory = "./qsdsqd";
let test = utils::get_directory_content(directory).unwrap();
println!("{:?}", a);
}
if directory exist : ok, unwrap is happy. But does anyone know a "trick" for get the content of the error in var test ? Also, can we put the name of a variable in io::ErrorKind::Other to get more precision (here : &path) ?
Next try
fn main() {
let directory = "./qsdqsd";
let a = match utils::get_directory_content(directory){
Err(e) => println!("an error: {:?}", e),
Ok(c) => println!("{:?}", c),
};
println!("{:?}", a);
}
When error, ok, we have message, but here, if we put a correct folder : a "just" print result but content is empty, and we can't say Ok(c) => c for just return Ok content from function :/
Have a small function to list content of a repository :
That's already a pretty bad start, because it combines a TOCTOU with unnecessary extra work: if you're checking is_dir then trying to read the directory, it's possible for the entry to get deleted or swapped from under you.
This is a shame, since read_dir already does exactly what you want:
pub fn get_directory_content(path: &str) -> Result<Vec<PathBuf>, io::Error> {
let mut result = vec![];
for file in fs::read_dir(path)? {
result.push(file.unwrap().path());
}
Ok(result)
}
And you can apply this to the individual entries as well:
pub fn get_directory_content(path: &str) -> Result<Vec<PathBuf>, io::Error> {
let mut result = vec![];
for file in fs::read_dir(path)? {
result.push(file?.path());
}
Ok(result)
}
When error, ok, we have message, but here, if we put a correct folder : a "just" print result but content is empty, and we can't say Ok(c) => c for just return Ok content from function :/
Sure you can, however you still have to do something for the Err case: as most things in Rust, match is an expression, so all the branches need to return values of the same type... or not return at all:
let a = match get_directory_content(directory) {
Err(e) => {
println!("an error: {:?}", e);
return;
}
Ok(c) => c,
};
return has type !, which is Rust's "bottom" type: it's compatible with everything, because return does not "terminate", and thus there's npo reason for it to be incompatible with anything.
Alternatively, you could update main to return a Result as well, though that also requires updating it to return a value:
fn main() -> Result<(), io::Error> {
let directory = "./.config";
let a = get_directory_content(directory)?;
println!("{:?}", a);
Ok(())
}
You need to return c from your match statement.
Further, you need to do something in the Err case other than just print. What should a be in the error case?
I assume that you simply want to end the program, so I inserted a return.
mod utils {
use std::{fs, io, path::Path, path::PathBuf};
pub fn get_directory_content(path: &str) -> Result<Vec<PathBuf>, io::Error> {
let _path: bool = Path::new(path).is_dir();
match _path {
true => {
let mut result = vec![];
for file in fs::read_dir(path).unwrap() {
result.push(file.unwrap().path());
}
Ok(result)
}
false => Err(io::Error::new(io::ErrorKind::Other, " is not a directory")),
}
}
}
fn main() {
let directory = "./qsdqsd";
let a = match utils::get_directory_content(directory) {
Err(e) => {
println!("an error: {:?}", e);
return;
}
Ok(c) => {
println!("{:?}", c);
c
}
};
println!("{:?}", a);
}
["./qsdqsd/a.txt"]
["./qsdqsd/a.txt"]
DISCLAIMER: My answer is very much superficial. #Masklinn goes into much more detail about the "cleanest way" and other issues with the given code.
Because this is the accepted answer (at the time of writing), here is how a "cleanest way" version of the code could look like:
use std::{fs, io, path::PathBuf};
pub fn get_directory_content(path: &str) -> Result<Vec<PathBuf>, io::Error> {
let mut result = vec![];
for file in fs::read_dir(path)? {
result.push(file?.path());
}
Ok(result)
}
fn main() {
let directory = "./qsdqsd2";
let a = match get_directory_content(directory) {
Err(e) => {
println!("an error: {:?}", e);
return;
}
Ok(c) => c,
};
println!("{:?}", a);
}
["./qsdqsd/a.txt"]
Alternatively, you could have main() return a Result, which makes this even cleaner:
use std::{fs, io, path::PathBuf};
pub fn get_directory_content(path: &str) -> Result<Vec<PathBuf>, io::Error> {
let mut result = vec![];
for file in fs::read_dir(path)? {
result.push(file?.path());
}
Ok(result)
}
fn main() -> Result<(), io::Error> {
let directory = "./qsdqsd";
let a = get_directory_content(directory)?;
println!("{:?}", a);
Ok(())
}
I know the issue is that I have two Result types from different libraries but can't find how to fix it.
[dependencies]
crossterm = "0.23"
time = "0.3.9"
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.11", features = ["blocking", "json"] }
use time::Instant;
use std::collections::HashMap;
use crossterm::{
event::{self, Event, KeyCode, KeyEvent},
Result,
};
pub fn read_char() -> Result<char> {
loop {
if let Event::Key(KeyEvent {
code: KeyCode::Char(c),
..
}) = event::read()?
{
return Ok(c);
}
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let instant = Instant::now();
let response = reqwest::blocking::get("https://httpbin.org/ip")?
.json::<HashMap<String, String>>()?;
let duration = instant.elapsed();
println!("ns = {:?}, response: {:#?}, ", duration.whole_nanoseconds(), response);
// Any key to continue
println!("Press any key to continue:");
println!("{:?}", read_char());
Ok(())
}
Gives the error:
error[E0107]: this type alias takes 1 generic argument but 2 generic arguments were supplied
--> src\main.rs:20:14
|
20 | fn main() -> Result<(), Box<dyn std::error::Error>> {
| ^^^^^^ -------------------------- help: remove this generic argument
| |
| expected 1 generic argument
How do I fix this? I have searched but am likely looking for incorrect terms e.g. namespace alias and core::Result error[E0107] is not really helping.
I have tried this without success:
fn main() -> core::Result<(), Box<dyn std::error::Error>> {
You have crossterm ::Result in scope, so you would have to disambiguate the result you want to return, otherwise it just thinks you want to return the crossterm type:
fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
...
Ok(())
}
has experience with high level programming languages. I read the Rust book and now trying to survive and understand how the "things" in Rust works. I would love that someone explain what the heck is - Ok(()) and how to deal with it? My goal is to return result from function in to the variable where the output:
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
Running `target/debug/rcp ./file/ aba`
Ok(
"/home/tomand/rcp",
)
Here is the full code:
use std::fs;
use std::env;
use serde_json;
use regex::Regex;
use std::path::Path;
fn determinate_file_size(file: &str) -> u64 {
fs::metadata(file).unwrap().len()
}
fn determinate_is_it_file_or_dirctory(arg: &str) -> &str {
let file = "File";
let dir = "Directory";
let re = Regex::new(r"/").unwrap();
if re.is_match(arg) {
return dir;
}
return file;
}
fn collect_user_arguments() -> Vec<String> {
env::args().collect()
}
fn check_if_arguments_count_valid(args: &Vec<String>) -> bool {
if args.len() == 3 {
return true
}
help();
return false
}
fn get_current_working_dir() -> Result<T> {
env::current_dir()
}
fn help() {
println!("Examples:");
println!("rcp [srcfile] [destfile]");
println!("rcp [srcdir]/[srcfile] [destdir]/[destfile]");
}
fn main() {
let WORKING_DIR = get_current_working_dir();
let args: Vec<String> = collect_user_arguments();
if check_if_arguments_count_valid(&args) {
let arg1 = &args[1];
let arg2 = &args[2];
println!("{:#?}", determinate_is_it_file_or_dirctory(&arg1));
}
}
Seems the compiler tried to give me some inspiration but eventually we miscommunicate in the end:
error[E0107]: this enum takes 2 generic arguments but 1 generic argument was supplied
--> src/main.rs:42:33
|
42 | fn get_current_working_dir() -> Result<T> {
| ^^^^^^ - supplied 1 generic argument
| |
| expected 2 generic arguments
EDIT:
I went with this approach:
fn get_current_working_dir() -> String {
let res = env::current_dir();
match res {
Ok(path) => path.into_os_string().into_string().unwrap(),
Err(_) => "FAILED".to_string()
}
}
It seems more practice is required to understand the Result type and how to manage it.
std::env::current_dir returns a std::io::Result<Pathbuf>, so you need to use that type in your wrapper method:
fn get_current_working_dir() -> std::io::Result<PathBuf> {
env::current_dir()
}
Playground
Other nitpick:
const is not a type so let WORKING_DIR: const = get_current_working_dir(); is wrong, just let WORKING_DIR = get_current_working_dir(); is enough.
I do check function results on assignment using match statement. In some cases I want to exit the program with an error message like panic!() does. But how can I create a function or macro that can be used everywhere?
Example:
let args = match args::Args::parse() {
Ok(args) => args,
Err(e) => someerror("bla")
};
let mut statedoc = match state_loader.load() {
Ok(states) => states,
Err(e) => someerror("blub")
};
What does someerror() need to return to work everywhere?
One way is to use diverging function. Use the following syntax:
fn someerror(msg: &str) -> ! { // Note the `-> !` here
eprintln!("Error: {}", msg);
panic!();
}
fn main() {
let r: Result<i32, &str> = Err("hello");
let x = match r {
Ok(x) => x,
Err(e) => someerror(e),
};
println!("x = {}", x);
}
Remember that main can return a Result and you can use the ?-operator everywhere:
fn foo() -> Result<i32, &'static str> {
Err("Nope!")
}
fn main() -> Result<(), &'static str> {
let x = 5 * foo()?;
println!("{}", x);
Ok(())
}
When executed, the above program will just print "Error: Nope!" and have an exit status not equal to zero. To support more kinds of errors, you can have a a custom enum to wrap those and appropriate implementations of Into, so you can just do let args = args::Args::parse()?;. Any errors will bubble up to main() and cause the error to be printed out.
I have a function that calls another function which returns a Result. I need to check if the Result is Ok or Err and if it is an Err, I need to return early from my function. This is what I'm doing now:
match callable(&mut param) {
Ok(_v) => (),
Err(_e) => return,
};
Is there a more idiomatic Rust way to do this?
You can create a macro:
macro_rules! unwrap_or_return {
( $e:expr ) => {
match $e {
Ok(x) => x,
Err(_) => return,
}
}
}
fn callable(param: &mut i32) -> Result<i32, ()> {
Ok(*param)
}
fn main() {
let mut param = 0;
let res = unwrap_or_return!(callable(&mut param));
println!("{:?}", res);
}
Note that I wouldn't recommend discarding the errors. Rust's error handling is pretty ergonomic, so I would return the error, even if it is only to log it:
fn callable(param: &mut i32) -> Result<i32, ()> {
Ok(*param)
}
fn run() -> Result<(), ()> {
let mut param = 0;
let res = callable(&mut param)?;
println!("{:?}", res);
Ok(())
}
fn main() {
if let Err(()) = run() {
println!("Oops, something went wrong!");
}
}
If both functions return Result<doesn't matter, same T> you can just put a ? at the end of line of call.
fn caller() -> Result<Str, i32> {
let number = job()?; // <-- if job return error this function return/end here
// otherwise the value of Ok will assign to number
Ok(format!("the number is {}", number))
}
fn job() -> Result<i32, i32> {
// do something
Err(3)
}
You can use same pattern for Option<T> too.
You can use my unwrap_or crate to accomplish this.
You can do:
unwrap_or_ok!(callable(&mut param), _, return);
And if you want the result and to return the error, you can do:
let v = unwrap_or_ok!(callable(&mut param), error, return error);
Rust 1.65.0 has stabilized let-else statements, which enables you to write:
let Ok(_v) = callable(&mut param) else { return };