Are there any nice cases where we should use `unwrap`? [closed] - rust

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 4 years ago.
Improve this question
Since using unwrap may be problematic because it crashes in the error scenario, it may be considered as dangerous usage.
What if I am hundred percent sure that it will not crash, like in the following scenarios:
if option.is_some() {
let value = option.unwrap();
}
if result.is_ok() {
let result_value = result.unwrap();
}
Since we already checked the Result and Option there will be no crash with the unwrap() usage. However, we could have used match or if let. In my opinion, either match or if let usage is more elegant.

Let's focus on Result; I'll go back to Option at the end.
The purpose of Result is to signal a result which may succeed or fail with an error. As such, any use of it should fall into this category. Let's ignore the cases where a crate returns Result for impossible-to-fail operations.
By doing what you are doing (checking if result.is_ok() then extracting the value), you're effectively doing the same thing twice. The first time, you're inspecting the content of the Result, and the second, you're checking and extracting unsafely.
This could indeed have been done with a match or map, and both would have been more idiomatic than an if. Consider this case for a moment:
You have an object implementing the following trait:
use std::io::{Error, ErrorKind};
trait Worker {
fn hours_left(&self) -> Result<u8, Error>;
fn allocate_hours(&mut self, hours: u8) -> Result<u8, Error>;
}
We're going to assume hours_left() does exactly what it says on the tin. We'll also assume we have a mutable borrow of Worker. Let's implement allocate_hours().
In order to do so, we'll obviously need to check if our worker has extra hours left over to allocate. You could write it similar to yours:
fn allocate_hours(&mut self, hours: u8) {
let hours_left = self.hours_left();
if (hours_left.is_ok()) {
let remaining_hours = hours_left.unwrap();
if (remaining_hours < hours) {
return Err(Error::new(ErrorKind::NotFound, "Not enough hours left"));
}
// Do the actual operation and return
} else {
return hours_left;
}
}
However, this implementation is both clunky and inefficient. We can simplify this by avoiding unwrap and if statements altogether.
fn allocate_hours(&mut self, hours: u8) -> Result<u8, Error> {
self.hours_left()
.and_then(|hours_left| {
// We are certain that our worker is actually there to receive hours
// but we are not sure if he has enough hours. Check.
match hours_left {
x if x >= hours => Ok(x),
_ => Err(Error::new(ErrorKind::NotFound, "Not enough hours")),
}
})
.map(|hours_left| {
// At this point we are sure the worker has enough hours.
// Do the operations
})
}
We've killed multiple birds with one stone here. We've made our code more readable, easier to follow and we've removed a whole bunch of repeated operations. This is also beginning to look like Rust and less like PHP ;-)
Option is similar and supports the same operations. If you want to process the content of either Option or Result and branch accordingly, and you're using unwrap, there are so many pitfalls you'll inevitably fall into when you forget you unwrapped something.
There are genuine cases where your program should barf out. For those, consider expect(&str) as opposed to unwrap()

In many, many cases you can avoid unwrap and others by more elegant means. However, I think there are situations where it is the correct solution to unwrap.
For example, many methods in Iterator return an Option. Let us assume that you have a nonempty slice (known to be nonempty by invariants) and you want to obtain the maximum, you could do the following:
assert!(!slice.empty()); // known to be nonempty by invariants
do_stuff_with_maximum(slice.iter().max().unwrap());
There are probably several opinions regarding this, but I would argue that using unwrap in the above scenario is perfectly fine - in the presence of the preceeding assert!.
My guideline is: If the parameters I am dealing with are all coming from my own code, not interfacing with 3rd party code, possibly assert!ing invariants, I am fine with unwrap. As soon as I am the slightest bit unsure, I resort to if, match, map and others.
Note that there is also expect which is basically an "unwrap with a comment printed in the error case". However, I have found this to be not-really-ergonomic. Moreover, I found the backtraces a bit hard to read if unwrap fails. Thus, I currently use a macro verify! whose sole argument is an Option or Result and that checks that the value is unwrapable. It is implemented like this:
pub trait TVerifiableByVerifyMacro {
fn is_verify_true(&self) -> bool;
}
impl<T> TVerifiableByVerifyMacro for Option<T> {
fn is_verify_true(&self) -> bool {
self.is_some()
}
}
impl<TOk, TErr> TVerifiableByVerifyMacro for Result<TOk, TErr> {
fn is_verify_true(&self) -> bool {
self.is_ok()
}
}
macro_rules! verify {($e: expr) => {{
let e = $e;
assert!(e.is_verify_true(), "verify!({}): {:?}", stringify!($e), e)
e
}}}
Using this macro, the aforementioned example could be written as:
assert!(!slice.empty()); // known to be nonempty by invariants
do_stuff_with_maximum(verify!(slice.iter().max()).unwrap());
If I can't unwrap the value, I get an error message mentioning slice.iter().max(), so that I can search my codebase quickly for the place where the error occurs. (Which is - in my experience - faster than looking through the backtrace for the origin of the error.)

Related

Is it ok to use asserts in production code (not in test suite) to ensure expected behaviour

I notice when reading the docs that they often use assert when explaining expected behaviour of simple code blocks.
In production level code, would it be considered an anti-pattern to do the same? While reading rust by example I only saw assert's being used in tests, but in the instances where you do expect vars or values to be a specific thing, is assert the correct approach?
The example I came across in my own code is a scenario similar to the following...
fn foo(values: Vec<String>, my_num: usize) {
assert_eq!(values.len(), my_num);
// run this code after
}
I expect the vector passed to have a length equal to another value in the function, and the code wouldn't work if that wasn't the case. Would asserting these two values as being equal be the correct practice?
What are some other best practices or ways of handling other error behaviour?
Assertions are OK in unsafe code. For example, if you need to ensure that a pointer is non-null. Otherwise, it’s better to use Results, Options and usual conditions.
struct FooError;
fn foo(values: Vec<String>, my_num: usize) -> Result<(), FooError> {
if values.len() != my_num {
return Err(FooError);
}
…
Ok(())
}

How can I swap out the value of a mutable reference, temporarily taking ownership? [duplicate]

This question already has answers here:
Temporarily move out of borrowed content
(3 answers)
Closed 1 year ago.
I have a function that takes ownership of some data, modifies it destructively, and returns it.
fn transform(s: MyData) -> MyData {
todo!()
}
In some situations, I have a &mut MyData reference. I would like to apply transform to &mut MyData.
fn transform_mut(data_ref: &mut MyData) {
*data_ref = transform(*data_ref);
}
Rust Playground
However, this causes a compiler error.
error[E0507]: cannot move out of `*data_ref` which is behind a mutable reference
--> src/lib.rs:10:27
|
10 | *data_ref = transform(*data_ref);
| ^^^^^^^^^ move occurs because `*data_ref` has type `MyData`, which does not implement the `Copy` trait
I considered using mem::swap and mem::replace, but they require that you already have some valid value to put into the reference before taking another one out.
Is there any way to accomplish this? MyData doesn't have a sensible default or dummy value to temporarily stash in the reference. It feels like because I have exclusive access the owner shouldn't care about the transformation, but my intuition might be wrong here.
It feels like because I have exclusive access the owner shouldn't care about the transformation, but my intuition might be wrong here.
The problem with this idea is that if this were allowed, and the function transform panicked, there is no longer a valid value in *data_ref, which is visible if the unwind is caught or inside of Drop handling for the memory data_ref points into.
For example, let's implement this in the obvious fashion, by just copying the data out of the referent and back in:
use std::ptr;
fn naive_modify_in_place<T>(place: &mut T, f: fn(T) -> T) {
let mut value = unsafe { ptr::read(place) };
value = f(value);
unsafe { ptr::write(place, value) };
}
fn main() {
let mut x = Box::new(1234);
naive_modify_in_place(&mut x, |x| panic!("oops"));
}
If you run this program (Rust Playground link), it will crash with a “double free” error. This is because unwinding from the panicking function dropped its argument, and then unwinding from main dropped x — which is the same box, already deallocated.
There are a couple of crates designed specifically to solve this problem:
take_mut, and replace_with intended to improve on it. (I haven't used either, particularly.) Both of these offer two options for dealing with panics:
Force an abort (program immediately exits with no ability to handle the panic or clean up anything else).
Replace the referent of the reference with a different freshly computed value, since the previous one was lost when the panic started.
Of course, if you have no valid alternative value then you can't take option 2. In that case, you might want to consider bypassing this situation entirely by adding a placeholder: if you can store an Option<MyData> and pass an &mut Option<MyData>, then your code can use Option::take to temporarily remove the value and leave None in its place. The None will only ever be visible if there was a panic, and if your code isn't catching panics then it will never matter. But it does mean that every access to the data requires retrieving it from the Option (e.g. using .as_ref().unwrap()).
You could derive (or impl) Clone for MyData, then pass the clone into your destructive transform.
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=e3a4d737a700ef6baa35160545f0e514
Cloning removes the problem of the data being behind a reference.

Getting query string from Window object in WebAssembly in Rust

Context: I am learning Rust & WebAssembly and as a practice exercise I have a project that paints stuff in HTML Canvas from Rust code. I want to get the query string from the web request and from there the code can decide which drawing function to call.
I wrote this function to just return the query string with the leading ? removed:
fn decode_request(window: web_sys::Window) -> std::string::String {
let document = window.document().expect("no global window exist");
let location = document.location().expect("no location exists");
let raw_search = location.search().expect("no search exists");
let search_str = raw_search.trim_start_matches("?");
format!("{}", search_str)
}
It does work, but it seems amazingly verbose given how much simpler it would be in some of the other languages I have used.
Is there an easier way to do this? Or is the verbosity just the price you pay for safety in Rust and I should just get used to it?
Edit per answer from #IInspectable:
I tried the chaining approach and I get an error of:
temporary value dropped while borrowed
creates a temporary which is freed while still in use
note: consider using a `let` binding to create a longer lived value rustc(E0716)
It would be nice to understand that better; I am still getting the niceties of ownership through my head. Is now:
fn decode_request(window: Window) -> std::string::String {
let location = window.location();
let search_str = location.search().expect("no search exists");
let search_str = search_str.trim_start_matches('?');
search_str.to_owned()
}
which is certainly an improvement.
This question is really about API design rather than its effects on the implementation. The implementation turned out to be fairly verbose mostly due to the contract chosen: Either produce a value, or die. There's nothing inherently wrong with this contract. A client calling into this function will never observe invalid data, so this is perfectly safe.
This may not be the best option for library code, though. Library code usually lacks context, and cannot make a good call on whether any given error condition is fatal or not. That's a question client code is in a far better position to answer.
Before moving on to explore alternatives, let's rewrite the original code in a more compact fashion, by chaining the calls together, without explicitly assigning each result to a variable:
fn decode_request(window: web_sys::Window) -> std::string::String {
window
.location()
.search().expect("no search exists")
.trim_start_matches('?')
.to_owned()
}
I'm not familiar with the web_sys crate, so there is a bit of guesswork involved. Namely, the assumption, that window.location() returns the same value as the document()'s location(). Apart from chaining calls, the code presented employs two more changes:
trim_start_matches() is passed a character literal in place of a string literal. This produces optimal code without relying on the compiler's optimizer to figure out, that a string of length 1 is attempting to search for a single character.
The return value is constructed by calling to_owned(). The format! macro adds overhead, and eventually calls to_string(). While that would exhibit the same behavior in this case, using the semantically more accurate to_owned() function helps you catch errors at compile time (e.g. if you accidentally returned 42.to_string()).
Alternatives
A more natural way to implement this function is to have it return either a value representing the query string, or no value at all. Rust provides the Option type to conveniently model this:
fn decode_request(window: web_sys::Window) -> Option<String> {
match window
.location()
.search() {
Ok(s) => Some(s.trim_start_matches('?').to_owned()),
_ => None,
}
}
This allows a client of the function to make decisions, depending on whether the function returns Some(s) or None. This maps all error conditions into a None value.
If it is desirable to convey the reason for failure back to the caller, the decode_request function can choose to return a Result value instead, e.g. Result<String, wasm_bindgen::JsValue>. In doing so, an implementation can take advantage of the ? operator, to propagate errors to the caller in a compact way:
fn decode_request(window: web_sys::Window) -> Result<String, wasm_bindgen::JsValue> {
Ok(window
.location()
.search()?
.trim_start_matches('?')
.to_owned())
}

Is passing an iterator to a function as an argument an anti-pattern? [duplicate]

This question already has answers here:
How to write a Rust function that takes an iterator?
(3 answers)
Closed 5 years ago.
Say I have a string, from which I make an iterator (cycle-able, peek-able) over its chars:
let hello = "hello";
let mut iterator = hello.chars().cycle().peekable;
I wanted to figure out what the type of iterator is, so I purposefully introduced an error:
let mut iterator: usize = hello.chars().cycle().peekable;
The compiler then informed me that the type of the right hand side is:
std::iter::Peekable<std::iter::Cycle<std::str::Chars<'_>>>
Wow, that's a mouthful. If I define a function like so:
fn foobar(x: std::iter::Peekable<std::iter::Cycle<std::str::Chars<'_>>>){
// snip
}
I get an error like this:
error: underscore lifetimes are unstable (see issue #44524)
So, if I want to pass an iterator to a function, how should I do so? Or, is this something I should avoid?
Ok so there are multiple aspects to this questions:
First of, you can avoid the compiler error by giving it an explicit lifetime:
fn foobar<'a>(mut x: std::iter::Peekable<std::iter::Cycle<std::str::Chars<'a>>>){
To the he second question, whether this is idiomatic or not, i'd say no, avoid this specific approach.
You can only pass in this specific Iterator chain - something else is not possible. But most of the times, your algorithm isn't interested in the specific combination, rather than the functionality to "produce" chars. Use generics orimpl Trait instead (if you have access to nightly rust).
Impl Trait is a feature, which allows hiding the specific type used. This specific feature, accepting impl traits in argument position landed as of a few days ago, at the time of writing. I made this quick sketch for demonstration purposes, playground link
#![feature(universal_impl_trait)]
fn main() {
foo("hello".chars());
foo("hello".chars().rev());
}
fn foo(x: impl Iterator<Item=char>) {
let text: String = x.collect();
println!("{}", &text)
}
Edit: You can use generics as well, see comments from nullqube and stefan

Nice way to map with potential failure [duplicate]

This question already has answers here:
How do I stop iteration and return an error when Iterator::map returns a Result::Err?
(4 answers)
Closed 6 years ago.
I'm trying to parse a series of Json objects with potential failures that cancel the whole function.
Ideally, I'd do something like:
fn .... -> Result<Vec<Video>, YoutubeParseError> {
...
let videos = try!(doc.find("items").
and_then(Json::as_array).
ok_or(YoutubeParseError));
Ok(videos.into_iter().
map(|item| try!(json_to_video(item))).
collect())
}
But of course try doesn't escape the map() on error and instead of Result<Vec<Video>,_>, I get Vec<Result<Video,_>>. I could rewrite this as manual iteration adding elements into a new vec, but I feel like I'm missing some simpler way of handling this.
Is there some existing function that would get me from Iter<Result<T>> to Result<Vec<T>,_> easily?
In functional programming languages you can treat options and results as containers and Rust is similar, so you can map / flat_map over them. You could do this with flat_map. If videos is already a vector, you can just test for expected number of Ok's against a flat_mapped length to decide whether to return Ok.
However, you should try to keep things lazy and not continue parsing after the first failure. take_while would be an option here. Either way, you will need to track if you saw a parse_failure along the way. Something like below works - it demonstrates how flat_map drops Errors, but it parses more than necessary. You could also use a .filter and then .map to get the parse result
fn get_videos(test: &Vec<&str>) -> Result<Vec<u32>, &'static str> {
let videos = ...
let expected = videos.len();
let extracted = v.into_iter().flat_map(|x| json_to_video(x)).collect();
if extracted.len() == expected {
Ok(extracted)
} else {
Err("not_ok")
}
}
Here's an option to do it lazily -
let extracted = videos.map(|x|json_to_video(x))
.take_while(|x|x.is_ok())
.map(|x|x.ok().unwrap())
.collect()
You can call unwrap as you dropped everything starting at first failure. Now you return Ok if extracted.len() == videos.len()

Resources