I'm wading through a codebase full of code like this:
if let Some(i) = func1() {
if let Some(j) = func2(i) {
if let Some(k) = func3(j) {
if let Some(result) = func4(k) {
// Do something with result
} else {
println!("func 4 returned None");
}
} else {
println!("func 3 returned None");
}
} else {
println!("func 2 returned None");
}
} else {
println!("func 1 returned None");
}
That's a stupid, simplified example, but the general pattern is that:
There are a bunch of different functions which return an Option.
All the functions must be called in sequence, with the return value of the previous function (if it's not None) passed to the next function.
If all functions return a Some, then the final returned is used for something.
If any returns None, then execution stops and some kind of error is logged - usually an error message that informs the user exactly which function returned None.
The problem, of course, is that the above code is an ugly and unreadable. It gets even uglier when you substitute i, func1 etc. with variable/function names that actually mean something in the real code, and many examples in my real codebase have far more than four nested if lets. It's an example of the arrow anti-pattern, it completely fails the squint test, and it's confusing how the error messages appear in reverse order to the functions which can cause them.
Is there really not a better way to do this? I want to refactor the above into something that has a cleaner, flatter structure where everything appears in a sensible order. if let chaining might help but it doesn't look like that feature is available in Rust yet. I thought maybe I could clean things up by using ? and/or extracting some helper functions, but I couldn't get it to work and I'd rather not extract a ton of new functions all over the place if I can avoid it.
Here's the best I could come up with:
let i : u64;
let j : u64;
let k : u64;
let result : u64;
if let Some(_i) = func1() {
i = _i;
} else {
println!("func 1 returned None");
return;
}
if let Some(_j) = func2(i) {
j = _j;
} else {
println!("func 2 returned None");
return;
}
if let Some(_k) = func3(j) {
k = _k;
} else {
println!("func 3 returned None");
return;
}
if let Some(_result) = func3(k) {
result = _result;
} else {
println!("func 4 returned None");
return;
}
// Do something with result
But this still feels very long and verbose, and I don't like how I'm introducing these extra variables _i, _j etc.
Is there something I'm not seeing here? What's the simplest and cleanest way to write what I want to write?
There's an unstable feature that will introduce let-else statements.
RFC 3137
Introduce a new let PATTERN: TYPE = EXPRESSION else DIVERGING_BLOCK; construct (informally called a let-else statement), the counterpart of if-let expressions.
If the pattern match from the assigned expression succeeds, its bindings are introduced into the surrounding scope. If it does not succeed, it must diverge (return !, e.g. return or break).
With this feature you'll be able to write:
let Some(i) = func1() else {
println!("func 1 returned None");
return;
};
let Some(j) = func2(i) else {
println!("func 2 returned None");
return;
};
let Some(k) = func3(j) else {
println!("func 3 returned None");
return;
};
let Some(result) = func3(k) else {
println!("func 4 returned None");
return;
};
If you want to try it out now, use:
#![feature(let_else)]
If-let chaining will make this a lot nicer, but for now (assuming you don't want to use nightly), it's possible a slight refactor could help. For example, pulling all but the last call of the chain into its own function allows you to use the ? operator:
fn get_result() -> Option<u64> {
let i = func1()?;
let j = func2(i)?;
let k = func3(j)?;
func3(k)
}
fn main() {
if let Some(result) = get_result() {
// do something
}
}
If you need more fine-grained control over the error cases, you could return a Result instead:
enum Error {
Func1,
Func2,
Func3,
Func4,
}
fn get_result() -> Result<i64, Error> {
let i = func1().ok_or(Error::Func1)?;
let j = func2(i).ok_or(Error::Func2)?;
let k = func3(j).ok_or(Error::Func3)?;
func4(k).ok_or(Error::Func4)
}
fn main() {
use Error::*;
match get_result() {
Ok(result) => {},
Err(Func1) => {},
// ...
}
}
I'd like to put two more things on your list of "eventually look into" things: The simple Option::and_then:
let result = func1().and_then(func2).and_then(func3).and_then(func4);
match result {
Some(result) => …,
None => …,
}
And the slightly more tricky, but incredibly convenient anyhow::Context:
use anyhow::Context;
let j = func2(i).context("Func2 failed")?;
A slightly better version of the if let ... else { return } can be used:
let i = if let Some(i) = func1() { i } else {
println!("func 1 returned None");
return;
};
let j = if let Some(j) = func2(i) { j } else {
println!("func 2 returned None");
return;
};
let k = if let Some(k) = func3(j) { k } else {
println!("func 3 returned None");
return;
};
let result = if let Some(result) = func3(k) { result } else {
println!("func 4 returned None");
return;
};
Related
I finished converting the Rust books guessing game example to an Iced GUI application and wanted to handle error handling for the input from the user.
I have a String trying to convert to an i32 and am not sure how to handle the error if the user puts a String in the text_input or just hits return. I figured out a simple solution of:
self.hidden_compare = self.user_guess_text.trim().parse::<i32>().unwrap_or(0);
Rather than having self.hidden_compare default to 0. I would rather have self.user_guess_text default to a String I have set to earlier in the application and am unsure of how to accomplish this still being fairly new.
Edit: Full function added for clarification.
fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::BtnGuessNow => {
self.hidden_compare = self.user_guess_text.trim().parse::<i32>().unwrap_or(0);
if self.hidden_value == self.hidden_compare {
self.label_compare = String::from("A WINNER!");
self.number_ofguesses.push(self.hidden_compare.to_string() + ", A WINNER!");
}
else if self.hidden_value > self.hidden_compare {
self.label_compare = String::from("Too Low");
self.number_ofguesses.push(self.hidden_compare.to_string() + ", Too Low!");
}
else if self.hidden_value < self.hidden_compare {
self.label_compare = String::from("Too Big");
self.number_ofguesses.push(self.hidden_compare.to_string()+ ", Too Big!");
}
self.user_guess_text = "".to_string();
Command::none()
}
Message::UserInputValueUpdate(x) => {
self.user_guess_text = x;
Command::none()
}
}
and a relevant function that handles the Vec output:
fn guess_output_calc(&self) -> String {
let mut tempoutput = String::new();
for (i, x) in self.number_ofguesses.iter().enumerate().skip(1) {
let guessfmt = String::from(format!("Guess# {} Was: {}\n", i, x));
tempoutput.push_str(&guessfmt);
};
return
I would rather have self.user_guess_text default to a String
I'm not entirely sure what you mean with that, but I interpret this as "I want to set the self.user_guess_text variable to a specific value if it can't be converted to an integer". If this is wrong, then please update your question.
This is how I would approach this (simplified):
fn main() {
let mut user_guess_text = " a ";
match user_guess_text.trim().parse::<i32>() {
Ok(value) => {
println!("Parsed to value: {}", value);
}
Err(_) => {
println!("Unable to parse. Resetting variable.");
user_guess_text = "fallback text!";
}
}
println!("user_guess_text: {}", user_guess_text);
}
Unable to parse. Resetting variable.
user_guess_text: default text!
I'd like to know the name of the function that called my function in Rust.
In C# there's CallerMemberName attribute which tells the compiler to replace the value of a string argument to which it's applied with the name of the caller.
Does Rust have anything like that?
I don't know of a compile time solution, but you can use the backtrace functionality to resolve it at runtime.
use backtrace::Backtrace;
fn caller_name_slow() -> Option<String> {
let backtrace = Backtrace::new();
let symbolname = backtrace.frames().get(2)?.symbols().first()?.name();
symbolname.map(|s| format!("{:#?}", s))
}
fn caller_name_fast() -> Option<String> {
let mut count = 0;
let mut result = None;
backtrace::trace({
|frame| {
count += 1;
if count == 5 {
// Resolve this instruction pointer to a symbol name
backtrace::resolve_frame(frame, |symbol| {
if let Some(name) = symbol.name() {
result = Some(format!("{:#?}", name));
}
});
false
} else {
true // keep going to the next frame
}
}
});
result
}
fn my_function() {
println!("I got called by '{}'.", caller_name_slow().unwrap());
println!("I got called by '{}'.", caller_name_fast().unwrap());
}
fn main() {
my_function();
}
I got called by 'rust_tmp::main'.
I got called by 'rust_tmp::main'.
Note, however, that his is unreliable. The amount of stack frames we have to go up differs between targets and release/debug (due to inlining). For example, on my machine, in release I had to modify count == 5 to count == 2.
I find myself doing something like the following a lot:
fn foo() -> Result<i32, String> {
let cur = match something_that_returns_an_option() {
Some(cur) => cur,
None => return Err("Some error"),
};
// use `cur`
1
}
If I need several variables, I'm left with this pattern over and over again, or nested if lets/matches.
I there a more ergonomic way to handle repeatedly extracting values from Options?
You are looking for Option::ok_or. It lets you map an Option into a Result with the provided error. Combined with the ? operator you clean things up nicely:
fn foo() -> Result<i32, String> {
let cur = something_that_returns_an_option().ok_or("some error")?;
Ok(cur + 1)
}
Playground
Option::ok_or_else might also be helpful, as it evaluates the error branch lazily.
In your example you want to not just continue, break or return a regular value, but return an error. For that particular case, the Aiden4's answer is the way to go. But I've been in situations where I want to unwrap or (in the case of None) directly continue, break or return a non-error value. Rust (still) doesn't provide a short and concise way to do exactly that.
Here is a "one-liner" which kinda does the trick, but is still a bit verbose:
let v = if let Some(d) = some_option_value { d } else { continue; };
If you want a shorter solution, here are two options...
A macro
You can write a macro like this:
macro_rules! unwrap_or {
($e:expr, $or_do_what:expr) => {
if let Some(d) = $e { d } else { $or_do_what }
};
}
That will allow you to write code like this:
let options = vec![Some(74), None, Some(9)];
for o in options {
let v = unwrap_or!(o, continue);
// ...
}
That's a trivial example, but I think the biggest benefit can come if you need to perform multiple checks, so that instead of writing something "idiomatic" like this:
for thing in things {
if let Some(a) = thing {
// ...
if let Some(b) = a.another_opt {
// ...
if let Some(c) = a.yet_another_opt {
// ...
}
}
}
}
, you can simplify the code by avoiding the nesting of multiple blocks like this:
for thing in things {
let a = unwrap_or!(thing, continue);
// ...
let b = unwrap_or!(a.another_opt, continue);
// ...
let c = unwrap_or!(a.yet_another_opt, continue);
// ...
}
Whether that's a good practice is subjective, of course.
let...else (unstable)
There is an unstable let...else feature, which is intended to solve the problem. Example:
#![feature(let_else)]
...
let options = vec![Some(74), None, Some(9)];
for o in options {
let Some(v) = o else { continue };
println!("v = {v}");
}
I've been tinkering with Rust and I'm a little confused with function return types. As an experiment I'm writing an IRC log parser. I'm familiar with the primitive types, and having functions return those. What about more complex types when returning multiple pieces of data?
/* Log line example from log.txt */
/* [17:35] <#botname> name1 [460/702] has challenged name2 [224/739] and taken them in combat! */
#[derive(Show)]
struct Challenger {
challenger: String,
defender: String
}
fn main() {
let path = Path::new("log.txt");
let mut file = BufferedReader::new(File::open(&path));
for line in file.lines() {
let mut unwrapped_line = line.unwrap();
let mut chal = challenges3(unwrapped_line);
println!("Challenger: {}", chal.challenger);
println!("Defender: {}", chal.defender);
}
}
fn challenges3(text: String)-> Challenger {
let s: String = text;
let split: Vec<&str> = s.as_slice().split(' ').collect();
if(split[4] == "has" && split[5] == "challenged") {
let mychallenger = Challenger { challenger: split[2].to_string(), defender: split[6].to_string()};
return mychallenger;
}
}
I realize this code isn't very idiomatic, I'm getting familiar with the language.
I get an error with this code:
"mismatched types: expected `Challenger`, found `()` (expected struct Challenger, found ())"
How can I return a Struct or a HashMap? Is there a better way to return multiple fields of data?
The if in challenges3 has no else block, so if the condition isn't met, execution continues after the if block. There's nothing there, so the function implicitly returns () at this point. You must also return a Challenger after the if block, or panic! to abort the program.
Alternatively, you could change the return type of your function to Option<Challenger>. Return Some(mychallenger) in the if block, and None after the if block:
fn challenges3(text: String) -> Option<Challenger> {
let s: String = text;
let split: Vec<&str> = s.as_slice().split(' ').collect();
if split[4] == "has" && split[5] == "challenged" {
let mychallenger = Challenger { challenger: split[2].to_string(), defender: split[6].to_string()};
return Some(mychallenger);
}
None
}
You can also use Result instead of Option if you want to return some information about the error.
I want to print every item in a vector separated by commas. You could use numeric indexing:
for i in 0..vec.len() {
print!("{}", vec[i]);
if i < vec.len() - 1 {
print!(", ");
}
}
But what if you just have an Iterator? You either need to treat the first or last value specially, or create a custom iterator, which seems like a lot of work.
Is there a cleaner idiomatic way of expressing this in Rust?
If you want to avoid using a variable to check if element is first, you can make use of .take() and .skip() methods of iterators:
for e in vec.iter().take(1) {
print!("{}", e);
}
for e in vec.iter().skip(1) {
print!(", {}", e);
}
or compact all in a fold :
vec.iter().fold(true, |first, elem| {
if !first { print(", "); }
print(elem);
false
});
You can do something special for the first element, then treat all subsequent ones the same:
let mut iter = vec.iter();
if let Some(item) = iter.next() {
print!("{}", item);
for item in iter {
print!("<separator>{}", item);
}
}
If you use Itertools::format, it's even easier:
println!("{}", vec.iter().format("<separator>"));
let first = true;
for item in iterator {
if !first {
print(", ");
}
print(item);
first = false;
}
Another method:
fn main() {
let a = vec!["May", "June"];
for (n, s) in a.iter().enumerate() {
if n > 0 {
print!(",");
}
print!("{}", s);
}
}
https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.enumerate