What happens in Rust using "match" if nothing matches? - rust

I'm learning Rust and it looks very interesting. I'm not yet very familiar with "match", but it looks to be fairly integral. I was using the following code (below) to convert String to i64 which included the commented-out line "None" in place of the next line "_". I wondered what happened in the event of a non-match without underscore or whether "None" may be a catch-all. The calling-code requires a positive i64, so negative results in an invalid input (in this case). I'm not sure if it's possible to indicate an error in a return using this example other than perhaps using a struct.
Without underscore as a match item, will "None" catch all, and can it be used instead of underscore?
Is it possible to return an error in a function like this without using a struct as the return value?
In general, is it possible for a non-match using "match" and if so, what happens?
In the example below, is there any difference between using underscore and using "None"?
Example code :
fn fParseI64(sVal: &str) -> i64 {
match from_str::<i64>(sVal) {
Some(iVal) => iVal,
// None => -1
_ => -1
}
}

Without underscore as a match item, will "None" catch all, and can it be used instead of underscore?
In this case, yes. There are only 2 cases for Option<T>: Some<T> or None.
You are already matching the Some case for all values, None is the only case that's left, so
None will behave exactly the same as _.
2. Is it possible to return an error in a function like this without using a struct as the return value?
I'd suggest you read this detailed guide to understand all the possible ways to handle errors -
http://static.rust-lang.org/doc/master/tutorial-conditions.html
Using Options and Results will require you to return a struct.
3. In general, is it possible for a non-match using "match" and if so, what happens?
Rust won't compile a match if it can't guarantee that all the possible cases are handled. You'll need to have a _ clause if you are not matching all possibilities.
4. In the example below, is there any difference between using underscore and using "None"?
No. See Answer #1.
Here are 3 of the many ways you can write your function:
// Returns -1 on wrong input.
fn alt_1(s: &str) -> i64 {
match from_str::<i64>(s) {
Some(i) => if i >= 0 {
i
} else {
-1
},
None => -1
}
}
// Returns None on invalid input.
fn alt_2(s: &str) -> Option<i64> {
match from_str::<i64>(s) {
Some(i) => if i >= 0 {
Some(i)
} else {
None
},
None => None
}
}
// Returns a nice message describing the problem with the input, if any.
fn alt_3(s: &str) -> Result<i64, ~str> {
match from_str::<i64>(s) {
Some(i) => if i >= 0 {
Ok(i)
} else {
Err(~"It's less than 0!")
},
None => Err(~"It's not even a valid integer!")
}
}
fn main() {
println!("{:?}", alt_1("123"));
println!("{:?}", alt_1("-123"));
println!("{:?}", alt_1("abc"));
println!("{:?}", alt_2("123"));
println!("{:?}", alt_2("-123"));
println!("{:?}", alt_2("abc"));
println!("{:?}", alt_3("123"));
println!("{:?}", alt_3("-123"));
println!("{:?}", alt_3("abc"));
}
Output:
123i64
-1i64
-1i64
Some(123i64)
None
None
Ok(123i64)
Err(~"It's less than 0!")
Err(~"It's not even a valid integer!")

Related

Question mark operator replacement when return type is neither Option nor Result

Rust has a question mark operator than can be used like this:
fn make_foo() -> Option<Foo> { ... }
fn make_bar() -> Option<Bar> {
let foo = make_foo()?;
// ... Many lines of code using `foo` to compute data for `Bar::make` ...
Some(Bar::make(...))
}
But what if I want a function returning Bar rather than Option<Bar> (thus retuning Bar::new instead of None when make_foo() returns None)?
Of course, I could do
fn make_bar() -> Bar {
match make_foo() {
None => { Bar::new() }
Some(foo) => {
// ... Many lines of code using `foo` to compute data for `Bar::make` ...
Bar::make(...)
}
}
}
but this increases nesting level for the entire function, which I do not like. I would like to have a replacement for the ? operator.
So I came up with
fn make_bar() -> Bar {
let foo = match make_foo() {
None => { return Bar::new(); }
Some(v) => v
};
// ... Many lines of code using `foo` to compute data for `Bar::make` ...
Bar::make(...)
}
Is this idiomatic Rust? Or are there better solutions?
Is this idiomatic Rust?
Yes, it looks fine. You can remove the braces:
let foo = match make_foo() {
None => return Bar::new(),
Some(v) => v
};
The best will be let-else (unstable):
fn make_bar() -> Bar {
let Some(foo) = match make_foo() else {
return Bar::new();
};
// ... Many lines of code using `foo` to compute data for `Bar::make` ...
Bar::make(...)
}
For a similar one, see Idiomatic way to handle errors with an early return in Rust.
There's multiple ways to do this and I wouldn't necessarily say one is better than the other. However, let me give you one more option that may be useful if you could be using the ? operator multiple times in this function. Having many early return Bar::new() will likely get old. With this approach you can have your cake and eat it too.
Option has many utility methods. You can combine .and_then() with a final .unwrap_or_else() in this situation:
fn make_bar() -> Bar {
make_foo()
.and_then(|foo| {
// Do stuff with foo
Some(Bar::make(...))
})
.unwrap_or_else(Bar::new)
}
This does increase the nesting level, but only once, and has the advantage that you can use ? inside of the mapping function to cause the outer function to return the default Bar::new() value.
(If you implement Default on Bar then you could even use the less-verbose .unwrap_or_default() at the end.)

Matching an i32 value with character constants

I'm writing a parser in Rust, which needs at various points to match the current token against candidate values. Some of the candidate values are characters, others are integer constants, so the token is declared as i32, which would be plenty to accommodate both. (All the characters to be matched against are ASCII.)
The problem is that when I supply a character constant like '(' to be matched against, the compiler complains that it expected i32 and is getting char.
I tried writing e.g. '(' as i32 but an as expression is not allowed as a match candidate.
Obviously I could look up the ASCII values and provide them as numbers, but it seems there should be a more readable solution. Declaring the token as char doesn't really seem correct, as it sometimes needs to hold integers that are not actually characters.
What's the recommended way to solve this problem?
It’s a bit verbose, but your match arms could be of the form c if c == i32::from(b'(').
Another alternative would be to match on u8::try_from(some_i32) (branch arms Some(b'(') and then either None if some_i32 == … or None => { match some_i32 { … } }).
Yet another would be to change the type from i32 to your own enum, which is probably the cleanest option but might require some convincing of the Rust compiler to get an i32-like representation if you need that for some reason.
Finally, you could define const PAREN_OPEN: i32 = b'(' as i32; and use PAREN_OPEN as the pattern.
Since as expressions are allowed in constants, and matching is allowed against constants, you can use a constant:
const LPAREN: i32 = '(' as i32;
match v {
LPAREN => { ... }
// ...
}
If you can use nightly, you can use the inline_const_pat feature to reduce the boilerplate:
#![feature(inline_const_pat)]
match v {
const { '(' as i32 } => { ... }
// ...
}
Another way: here's a small proc macro that will replace the characters with their numerical value (it does not work with nested char patterns):
use proc_macro::TokenStream;
use quote::ToTokens;
#[proc_macro]
pub fn i32_match(input: TokenStream) -> TokenStream {
let mut input = syn::parse_macro_input!(input as syn::ExprMatch);
for arm in &mut input.arms {
if let syn::Pat::Lit(lit) = &mut arm.pat {
if let syn::Expr::Lit(syn::ExprLit { lit, .. }) = &mut *lit.expr {
if let syn::Lit::Char(ch) = lit {
*lit = syn::Lit::Int(syn::LitInt::new(
&(ch.value() as i32).to_string(),
ch.span(),
));
}
}
}
}
input.into_token_stream().into()
}
i32_match! {
match v {
'(' => { ... }
// ...
}
}

Result and Option unwrapping

I am trying to learn Rust and I would like to understand the conceptual fundamentals. In Rust, we often use Result<T, E> as a return type. Basically, a type, which consists of - Ok() and Err(), and it is up to the caller to handle these.
But what I am kind a surprised is fact, that both Ok() and Err(), have again their "options" -> Namely Some and None.
Example code:
fn integer_divide(a: u32, b: u32) -> Result<u32, String> {
if b != 0 {
Ok(a / b)
} else {
Err("Division by zero!".into())
}
}
let result = integer_divide(5, 0);
if(result.is_err()){
if(result.err().is_some()){
// some logic
}
}
Question
So basically, we need to double check before we get to resulting value of every function (First for Err or Ok, then for Some or None)? If yes, I find this very clunky. Especially in case my integer_divide function, where reliably I am able to say, it can never have a result with Err() of None value.
It would make more sense to me, if we would just unwrap Err(), then check if it's value is None or some other type... Especially in case, where I am 100% sure, it cannot hold None value. Any thoughts appreciated.
Note
I am aware of existence of ? operator. I just want to understand concept.
Considering result.err() alone, nothing ensures that result actually contains an error, thus this method cannot return an error every time we invoke it.
The usual way in Rust to provide something which is optional is to return an Option, hence the is_some() method.
This does not mean that the Result contains such an Option; this Option is created by the err() method just in case result did not contain an error.
You know that there is actually an error inside result because you tested just before with result.is_err(); but the designer of the err() method did not know, a long time ago, that you would test is_err() just before calling err().
The usual way to handle result without double check would be
match result {
Ok(v) => {
println!("result is {}", v);
}
Err(e) => {
println!("error: {}", e);
}
}
is_err() serves to answer the question "does this result contain an error?". If that's all you're interested in, then you're good to go:
if result.is_err() {
// logic here
}
Result is just an enum with two well-defined variants, so if you also need to access the value of the error, you can use if let to match the Err variant:
if let Err(e) = result {
// logic here, `e` contains the error value
}
Finally, if you need to handle both Ok and Err in the same go, use match, as shown in the answer by #prog-fh.

Option types and early returns. return an Error when is_none()

Using match (like in bar) seems to be a common approach..
#[derive(Debug)]
pub enum MyErrors {
SomeError,
}
fn foo(x: Option<u64>) -> Result<u64, MyErrors> {
if x.is_none() {
return Err(MyErrors::SomeError);
}
// .. some long code where more options
// are checked and matched
// The ok here is just so the code is simple and compiles
Ok(x.unwrap() * 2)
}
fn bar(x: Option<u64>) -> Result<u64, MyErrors> {
match x {
None => {
return Err(MyErrors::SomeError)?;
}
Some(v) => {
// .. some long code where more options
// are checked and matched
// The ok here is just so the code is simple and compiles
Ok(x.unwrap() * 2)
}
}
}
fn main() {
foo(Some(1));
bar(Some(2));
}
However, early returns (such as in foo) significantly reduce how nested the code looks like. If there are multiple times when an option has to be unwrapped or an error returned, code like bar gets very nested...
What is the recommended practice for early returning an error in the case of empty options?
If a longer method chain is undesirable due to complex logic inside, there are still a few readable, low-indent options.
ok_or and ?
We can convert an Option to a Result with a desired error, and immediately unwrap it with the ? operator. This solution probably provides the least indent possible, and can be easily used to "unwrap" multiple Options.
fn bar1(x: Option<u64>) -> Result<u64, MyErrors> {
let x = x.ok_or(MyErrors::SomeError)?;
// A lot of stuff going on.
Ok(x * 2)
}
This will evaluate the error inside ok_or regardless of whether or not it will actually be used. If this computation is expensive, ok_or_else, which produces the error lazily, will be more efficient (related question).
if let
This solution can still lead to a staircase of code if nested, but may be more appropriate if the else branch logic is more involved.
fn bar2(x: Option<u64>) -> Result<u64, MyErrors> {
if let Some(x) = x {
// Lot of stuff here as well.
Ok(x * 2)
} else {
Err(MyErrors::SomeError)
}
}

What is unwrap in Rust, and what is it used for?

I have this code that uses .unwrap():
fn main() {
let paths = std::fs::read_dir("/home/user").unwrap();
for path in paths {
println!("Name: {}", path.unwrap().path().display());
}
}
After looking at the definition of unwrap,
pub fn unwrap(self) -> T {
match self {
Ok(t) => t,
Err(e) => unwrap_failed("called `Result::unwrap()` on an `Err` value", e),
}
}
And the signature of read_dir
pub fn read_dir<P: AsRef<Path>>(path: P) -> io::Result<ReadDir>
Am I correct in understanding that unwrap returns the T type that is passed in Result?
In Rust, when you have an operation that may either return a T or fail, you will have a value of type Result<T,E> or Option<T> (E will be the error condition in case of an interesting error).
The function unwrap(self) -> T will give you the embedded T if there is one. If instead there is not a T but an E or None then it will panic.
It is best used when you are positively sure that you don't have an error. If that is not the case usually it is better either pattern-match the error or use the try! macro ? operator to forward the error.
In your example, the call to read_dir() returns a io::Result<ReadDir> because opening the directory might fail. And iterating the opened directory returns multiple values of type io::Result<DirEntry> because reading the directory might also fail.
With try! ? it would be something like this:
fn try_main() -> std::io::Result<()> {
let entries = std::fs::read_dir("/home/user")?;
for entry in entries {
println!("Name: {}", entry?.path().display());
}
Ok(())
}
fn main() {
let res = try_main();
if let Err(e) = res {
println!("Error: {}", e);
}
}
Look how every error case is checked.
(Updated to use ? instead of try!(). The macro still works, but the ? is preferred for new code).
The problem is that reading a line from a file produces a potential error type. The type is
Result<String,std::io::Error>
Result is an enum. There are two potential values in the Result, they are used for error handling and management. The first value is Err. If Err is populated, there was an error in the function that was called. The other potential selection is Ok. Ok contains a value.
enum Result<T, E> {
Ok(T),
Err(E),
}
Enum is a complex type, rather than a single value. To get the value we are looking for, we use unwrap() to unwrap the value.
unwrap() is used here to handle the errors quickly. It can be used on any function that returns Result or Option (Option is also enum). If the function returns an Ok(value), you will get the value. If the function returns an Err(error), the program will panic.

Resources