Result and Option unwrapping - rust

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.

Related

Return empty String if match fails in function

I have a function like this
fn get_html(address: &str) -> String {
let mut response = reqwest::blocking::get(
address,
);
response = response.unwrap_or_else(|_e| {String::from("")});
response = response.text().unwrap_or_else(|_e| {String::from("")});
return response
}
Where I'm checking for html content. I would like to return an empty String if any kind of an error occurs somewhere in this function.
I'm not sure how to deal with this because unwrap_or_else expecting Result not String.
The reqwest::blocking::get() function is returning a Result<Response>.
To obtain the html, you have to unwrap this result and call .text() method.
That will return a Result<String>, that you have to unwrap again.
In your code you assign a String::from("") when you unwrap the Result<Response>, and that is not right, because you have a Response when it is Ok and a String when it is an Err.
Instead you should match the result and return the String from the function:
fn get_html(address: &str) -> String {
let mut response = reqwest::blocking::get(
address,
);
match response {
Ok(response) => response.text().unwrap_or_else(|_e| String::from("")),
Err(_) => String::from(""),
}
}
In this code, you use unwrap_or_else() just on the .text() result.
While if you have an error on the response itself, you return a String from the function.
An idiomatic way to solve your issue would be to refactor slightly your code: because code in get_html could fail, it's natural that its signature reflects this, so instead of returning a String, it could return an Option<String>, leaving the caller decide on what to do in this case:
fn get_html(address: &str) -> Option<String> {
reqwest::blocking::get(address)
.ok()?
.text()
.ok()
}
See the playground.
This makes the code much more straightforward. However, you may really want the signature of the function to be -> String, not -> Option<String>. In this case, there are two solutions.
The first solution would be to use the experimental try block. I mention this solution, not because it's currently the most adequate, but because it will be one day (most likely).
fn get_html(address: &str) -> String {
let result: Option<_> = try {
reqwest::blocking::get(address)
.ok()?
.text()
.ok()?
};
result.unwrap_or_default()
}
See the playground.
Note that, as is, Rust is not able to figure out types on its own for the try block, so we have to help it, which makes this more verbose. This aspect will probably improve over time, when try blocks are stabilized.
Also note that, since the Default::default constructor of String produces an empty string, we can directly use .unwrap_or_default() instead of .unwrap_or(String::new()) or .unwrap_or_else(|| String::new()) (since an empty string is not allocated, the first option is also acceptable).
The second solution would simply be to add an other function with the wanted signature, and make it use the first function
fn get_html_failable(address: &str) -> Option<String> {
reqwest::blocking::get(address)
.ok()?
.text()
.ok()
}
fn get_html(address: &str) -> String {
get_html_failable(address).unwrap_or_default()
}
This may seem unconvenient, but lots of libraries in Rust have internal error propagation using types that represent failure (such as Result or Option), because it's really convenient to write such functions and to compose them (both reqwest::blocking::get and .text() return a Result, and see how easy it is to use them in a function that returns an Option), and have wrapper function that expose the wanted signature (most libraries will still expose fail types, but if you are writing an application it's not necessarily the best choice).
A variable of both preceding workarounds is to "simulate" a try block in stable Rust, without relying on an additional toplevel function.
fn get_html(address: &str) -> String {
(|| reqwest::blocking::get(address).ok()?.text().ok())()
.unwrap_or_default()
}
See the playground.
Note that, in this case, countrary to the try version, Rust is able to figure out the right types on its own.
This is probably the least readable solution, so I wouldn't recommend it (use try blocks instead). But it works.

In Rust, why do I not dereference on a match when it is required for if?

As a C programmer familiar with pointers and dereferencing, I'm working through a Rust tutorial, and have come across something I don't understand.
fn main() {
let vector = vec![10, 20, 30, 40];
for entry in &vector {
if *entry == 30 { // I have to use a star (dereference) here...
println!("thirty");
} else {
println!("{}", entry);
}
match entry { // ...but why do I not need a star here?
30 => println!("thirty"),
_ => println!("{}", entry), // or here?
}
}
}
On the first case, your if statement is comparing against a value, so you must bring the value, aka dereferencing it, in order to compare values properly.
Also, the loop over the vector is implying a .into_iter() call, that, given the context, gives you a reference &T to the current value in entry.
On the second match, the Rust compiler is applying automatic dereferencing, so the compiler already knows that you don't want to match against the pointer, but against the value. As others pointed out, match statements contain some ergonomics to make the code more readable.

rust what does a failed compare exchange on AtomicPtr return?

So in rust, I am trying to see what result is given when a AtomicPtr.compare_exchange() fails, where the expected value is not what is actually contained. I have the following code:
use std::sync::atomic::{AtomicPtr, Ordering};
pub fn foo() {
let ptr = &mut 5;
let ptr2 = &mut 6;
let atomic_ptr = AtomicPtr::new(ptr);
unsafe {
match atomic_ptr.compare_exchange(ptr, ptr2, Ordering::SeqCst, Ordering::SeqCst) {
Ok(got) => println!("Worked got {}", *got),
Err(nogot) => println!("Failed got {}", *nogot),
}
}
}
pub fn bar() {
let ptr = &mut 5;
let ptr2 = &mut 6;
let atomic_ptr = AtomicPtr::new(ptr);
unsafe {
match atomic_ptr.compare_exchange(ptr2, ptr2, Ordering::SeqCst, Ordering::SeqCst) {
Ok(got) => println!("Worked got {}", *got),
Err(nogot) => println!("Failed got {}", *nogot),
}
}
}
fn main() {
foo();
bar();
}
But this prints
Worked got 5
Failed got 5
It seems both success and fail for the compare exchange give the currently held pointer. Is this truly the case? Or am I missing something here? The docs don't document what the Result gives on failure either https://doc.rust-lang.org/std/sync/atomic/struct.AtomicPtr.html#method.compare_exchange
It seems both success and fail for the compare exchange give the currently held pointer. Is this truly the case?
Yes.
The docs don't document what the Result gives on failure either
They do, though the wording obscures it:
The return value is a result indicating whether the new value was written and containing the previous value. On success this value is guaranteed to be equal to current.
This is actually 3 completely separate clauses:
the return value is a result indicating whether the new value was written (Ok) -- implies that it's an Err on failure
the return value contains the previous value -- regardless of success
on success the the return value is guaranteed equal to current -- obviously on failure the current you provided was wrong, so you get something else (the actual current value)
Either opening a documentation issue or a documentation PR to clarify this would probably be welcome, though beware that this issue seems to be duplicated on all three implementations of compare_exchange so you'll probably have to fix them all if you go the PR route: all integer types are implemented via a common macro but AtomicPtr and AtomicBool have their own copies of the entire interface.

What are the performance implications of consuming self and returning it?

I've been reading questions like Why does a function that accepts a Box<MyType> complain of a value being moved when a function that accepts self works?, Preferable pattern for getting around the "moving out of borrowed self" checker, and How to capture self consuming variable in a struct?, and now I'm curious about the performance characteristics of consuming self but possibly returning it to the caller.
To make a simpler example, imagine I want to make a collection type that's guaranteed to be non-empty. To achieve this, the "remove" operation needs to consume the collection and optionally return itself.
struct NonEmptyCollection { ... }
impl NonEmptyCollection {
fn pop(mut self) -> Option<Self> {
if self.len() == 1 {
None
} else {
// really remove the element here
Some(self)
}
}
}
(I suppose it should return the value it removed from the list too, but it's just an example.) Now let's say I call this function:
let mut c = NonEmptyCollection::new(...);
if let Some(new_c) = c.pop() {
c = new_c
} else {
// never use c again
}
What actually happens to the memory of the object? What if I have some code like:
let mut opt: Option<NonEmptyCollection> = Some(NonEmptyCollection::new(...));
opt = opt.take().pop();
The function's signature can't guarantee that the returned object is actually the same one, so what optimizations are possible? Does something like the C++ return value optimization apply, allowing the returned object to be "constructed" in the same memory it was in before? If I have the choice between an interface like the above, and an interface where the caller has to deal with the lifetime:
enum PopResult {
StillValid,
Dead
};
impl NonEmptyCollection {
fn pop(&mut self) -> PopResult {
// really remove the element
if self.len() == 0 { PopResult::Dead } else { PopResult::StillValid }
}
}
is there ever a reason to choose this dirtier interface for performance reasons? In the answer to the second example I linked, trentcl recommends storing Options in a data structure to allow the caller to do a change in-place instead of doing remove followed by insert every time. Would this dirty interface be a faster alternative?
YMMV
Depending on the optimizer's whim, you may end up with:
close to a no-op,
a few register moves,
a number of bit-copies.
This will depend whether:
the call is inlined, or not,
the caller re-assigns to the original variable or creates a fresh variable (and how well LLVM handles reusing dead space),
the size_of::<Self>().
The only guarantees you get is that no deep-copy will occur, as there is no .clone() call.
For anything else, you need to check the LLVM IR or assembly.

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