Why can't this be done with if? - rust

I'm trying to handle errors received from an async call:
let res: Result<TcpStream, Box<dyn std::error::Error>> = session.runtime().borrow_mut().block_on(async {
let fut = TcpStream::connect(session.configuration().socket()).await?;
Ok(fut)
});
I tried to do it the old school way with an if but the compiler didn't like it:
if res.is_err() {
return Err(res);
}
After some googling I came across this:
let mut stream = match res {
Ok(res) => res,
Err(res) => return Err(res),
};
which feels very much the same but with Rusts' equivalent of a switch statement. Why can't I use the if?

if res.is_err() { return res } should work. Result is an enum with two variants: Ok which by convention holds a "successful" result, and Err which holds error information. As John pointed out, wrapping the existing Result (which happens to hold an Err) in another Err result doesn't make sense - or, more precisely, doesn't match the return type of the function.
When you use match, you unpack the result into its constituent values, and then in the error case re-pack it into a new result. Note that instead of the match statement use can use the ? operator, which would compress the declaration to just:
let mut stream = res?;

Related

Trying to get rid of unwraps

I have this line in my program:
let date = file.metadata().unwrap().modified().unwrap();
Can it be changed into form of if let Ok(date) = file.metadata().something.... and still be one liner?
Forgot to add: can't use ? operator, bc this is in a closure in for_each().
Using Result::and_then:
if let Ok(date) = file.metadata().and_then(|md| md.modified()) {
// stuff
}
Using the "try" operator (?):
// containing function returns `Result<T, E>` where `E: From<io::Error>`
let date = file.metadata()?.modified()?;
If you're inside a closure which must return (), and you want to ignore the error, I'd actually recommend using let else as such:
let Ok(metadata) = file.metadata() else { return };
let Ok(date) = metadata.modified() else { return };
// ...
This has the advantage that it doesn't increase the indentation level.

Why does a value created inside a function borrow and how can I avoid this pattern?

I'm new to rust but an engineer of over 6 years in various other languages from Javascript to Go.
I'm wondering why here the value is borrowed when I convert the response body to an "object".
I understand that the function owns the value and then the value is destroyed when the function returns BUT functions exist to create and return values. So there's clearly something fairly big I'm missing here. Can someone set me straight?
let response = match self
.client
.index(IndexParts::IndexId(index, id))
.body(json!({
"index": index,
"body": doc,
}))
.send()
.await
{
Ok(response) => response,
Err(err) => {
return Err(Box::new(err));
}
};
let response_body = match response.json::<Value>().await {
Ok(response_body) => response_body,
Err(err) => {
return Err(Box::new(err));
}
};
let response_map = response_body.as_object();
Ok(response_map)
I understand that the function owns the value and then the value is destroyed when the function returns BUT functions exist to create and return values. So there's clearly something fairly big I'm missing here.
You need to return an owned value, not a reference into a local. I assume what you're doing now boils down to:
fn foo() -> &Map<String, Value> {
let x = serde_json::json!({}); // except you get it by http
x.as_object().unwrap() // except you do proper error handling
}
This doesn't compile because you're returning the reference to a local value. Instead, you need to return the value itself:
fn foo() -> Map<String, Value> {
let x = serde_json::json!({}); // except you get it by http
match x {
Value::Object(o) => o,
_ => unreachable!(), // you'd return Err(...)
}
}
But even this is more complicated than you need. Since you already deserialize the value yourself, and handle the errors, you can simply ask serde to deliver a Map<String, Value> to begin with:
let response_body = match response.json::<Map<String, Value>>().await {
Ok(response_body) => response_body,
Err(err) => ...
};
Of course, you'll also need to adjust the return type to return the actual value instead of a reference.

Reduce nested match statements with unwrap_or_else in Rust

I have a rust program that has multiple nested match statements as shown below.
match client.get(url).send() {
Ok(mut res) => {
match res.read_to_string(&mut s) {
Ok(m) => {
match get_auth(m) {
Ok(k) => k,
Err(_) => return Err(“a”);
}
},
Err(_) => {
return Err(“b”);
}
}
},
Err(_) => {
return Err(“c”);
},
};
All the variables k and m are of type String.I am looking for a way to make the code more readable by removing excessive nested match statements keeping the error handling intact since both the output and the error types are important for the problem.Is it possible to achieve this by unwrap_or_else?
The .map_err() utility converts a Result to have a new error type, leaving the success type alone. It accepts a closure that consumes the existing error value and returns the new one.
The ? operator will early-return the error in the Err case, and unwrap in the Ok case.
Combining these two allows you to express this same flow succinctly:
get_auth(
client.get(url).send().map_err(|_| "c")?
.read_to_string(&mut s).map_err(|_| "b")?
).map_err(|_| "a")?
(I suspect that you actually want to pass s to get_auth() but that's not what the code in your question does, so I'm choosing to represent the code you posted instead of imaginary code that I'm guessing about.)

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.

Idiomatic rust way to properly parse Clap ArgMatches

I'm learning rust and trying to make a find like utility (yes another one), im using clap and trying to support command line and config file for the program's parameters(this has nothing to do with the clap yml file).
Im trying to parse the commands and if no commands were passed to the app, i will try to load them from a config file.
Now I don't know how to do this in an idiomatic way.
fn main() {
let matches = App::new("findx")
.version(crate_version!())
.author(crate_authors!())
.about("find + directory operations utility")
.arg(
Arg::with_name("paths")
...
)
.arg(
Arg::with_name("patterns")
...
)
.arg(
Arg::with_name("operation")
...
)
.get_matches();
let paths;
let patterns;
let operation;
if matches.is_present("patterns") && matches.is_present("operation") {
patterns = matches.values_of("patterns").unwrap().collect();
paths = matches.values_of("paths").unwrap_or(clap::Values<&str>{"./"}).collect(); // this doesn't work
operation = match matches.value_of("operation").unwrap() { // I dont like this
"Append" => Operation::Append,
"Prepend" => Operation::Prepend,
"Rename" => Operation::Rename,
_ => {
print!("Operation unsupported");
process::exit(1);
}
};
}else if Path::new("findx.yml").is_file(){
//TODO: try load from config file
}else{
eprintln!("Command line parameters or findx.yml file must be provided");
process::exit(1);
}
if let Err(e) = findx::run(Config {
paths: paths,
patterns: patterns,
operation: operation,
}) {
eprintln!("Application error: {}", e);
process::exit(1);
}
}
There is an idiomatic way to extract Option and Result types values to the same scope, i mean all examples that i have read, uses match or if let Some(x) to consume the x value inside the scope of the pattern matching, but I need to assign the value to a variable.
Can someone help me with this, or point me to the right direction?
Best Regards
Personally I see nothing wrong with using the match statements and folding it or placing it in another function. But if you want to remove it there are many options.
There is the ability to use the .default_value_if() method which is impl for clap::Arg and have a different default value depending on which match arm is matched.
From the clap documentation
//sets value of arg "other" to "default" if value of "--opt" is "special"
let m = App::new("prog")
.arg(Arg::with_name("opt")
.takes_value(true)
.long("opt"))
.arg(Arg::with_name("other")
.long("other")
.default_value_if("opt", Some("special"), "default"))
.get_matches_from(vec![
"prog", "--opt", "special"
]);
assert_eq!(m.value_of("other"), Some("default"));
In addition you can add a validator to your operation OR convert your valid operation values into flags.
Here's an example converting your match arm values into individual flags (smaller example for clarity).
extern crate clap;
use clap::{Arg,App};
fn command_line_interface<'a>() -> clap::ArgMatches<'a> {
//Sets the command line interface of the program.
App::new("something")
.version("0.1")
.arg(Arg::with_name("rename")
.help("renames something")
.short("r")
.long("rename"))
.arg(Arg::with_name("prepend")
.help("prepends something")
.short("p")
.long("prepend"))
.arg(Arg::with_name("append")
.help("appends something")
.short("a")
.long("append"))
.get_matches()
}
#[derive(Debug)]
enum Operation {
Rename,
Append,
Prepend,
}
fn main() {
let matches = command_line_interface();
let operation = if matches.is_present("rename") {
Operation::Rename
} else if matches.is_present("prepend"){
Operation::Prepend
} else {
//DEFAULT
Operation::Append
};
println!("Value of operation is {:?}",operation);
}
I hope this helps!
EDIT:
You can also use Subcommands with your specific operations. It all depends on what you want to interface to be like.
let app_m = App::new("git")
.subcommand(SubCommand::with_name("clone"))
.subcommand(SubCommand::with_name("push"))
.subcommand(SubCommand::with_name("commit"))
.get_matches();
match app_m.subcommand() {
("clone", Some(sub_m)) => {}, // clone was used
("push", Some(sub_m)) => {}, // push was used
("commit", Some(sub_m)) => {}, // commit was used
_ => {}, // Either no subcommand or one not tested for...
}

Resources