In the process of learning Rust, I am getting acquainted with error propagation and the choice between unwrap and the ? operator. After writing some prototype code that only uses unwrap(), I would like to remove unwrap from reusable parts, where panicking on every error is inappropriate.
How would one avoid the use of unwrap in a closure, like in this example?
// todo is VecDeque<PathBuf>
let dir = fs::read_dir(&filename).unwrap();
todo.extend(dir.map(|dirent| dirent.unwrap().path()));
The first unwrap can be easily changed to ?, as long as the containing function returns Result<(), io::Error> or similar. However, the second unwrap, the one in dirent.unwrap().path(), cannot be changed to dirent?.path() because the closure must return a PathBuf, not a Result<PathBuf, io::Error>.
One option is to change extend to an explicit loop:
let dir = fs::read_dir(&filename)?;
for dirent in dir {
todo.push_back(dirent?.path());
}
But that feels wrong - the original extend was elegant and clearly reflected the intention of the code. (It might also have been more efficient than a sequence of push_backs.) How would an experienced Rust developer express error checking in such code?
How would one avoid the use of unwrap in a closure, like in this example?
Well, it really depends on what you wish to do upon failure.
should failure be reported to the user or be silent
if reported, should one failure be reported or all?
if a failure occur, should it interrupt processing?
For example, you could perfectly decide to silently ignore all failures and just skip the entries that fail. In this case, the Iterator::filter_map combined with Result::ok is exactly what you are asking for.
let dir = fs::read_dir(&filename)?;
let todos.extend(dir.filter_map(Result::ok));
The Iterator interface is full of goodies, it's definitely worth perusing when looking for tidier code.
Here is a solution based on filter_map suggested by Matthieu. It calls Result::map_err to ensure the error is "caught" and logged, sending it further to Result::ok and filter_map to remove it from iteration:
fn log_error(e: io::Error) {
eprintln!("{}", e);
}
(|| {
let dir = fs::read_dir(&filename)?;
todo.extend(dir
.filter_map(|res| res.map_err(log_error).ok()))
.map(|dirent| dirent.path()));
})().unwrap_or_else(log_error)
Related
When would we ever need with in Kotlin if we can already use apply, run, also and let?
Can anyone give me a clear example?
In most situations, a with call can be transformed to a run like this:
with(foo) {
// some code ...
}
// is the same as:
foo.run {
// the same code ...
}
run and with will both return the lambda result, and will use foo as the lambda receiver.
However, I can think of one case where this wouldn't work - when foo declares its own run method that takes a lambda, e.g.
// having something like this isn't too uncommon, right?
fun run(x: () -> Unit) {}
The lambda type doesn't have to be exactly the same as the scope function run. Any function type should work. Then overload resolution wouldn't resolve to the built-in run.
You can force the resolution by doing some casts, but using with in this case is much better. Don't you agree?
I don’t think there’s any better example than with(context). Maybe it’s not clear if English isn’t one of your primary languages, but it semantically is translated into English much clearer than context.run when the object is being used to produce a result but isn’t the primary actor, so it makes code a little easier to read.
This of course raises the question of why run exists. Well, it semantically makes more sense in English when the object is the thing doing the action. In English, the context of an action is what you’re doing something with. But if the object is what is directly producing the result, then it is running the action.
Also, you can’t do ?.with.
We have an API service call that returns a bunch of validation messages. In each message there is a string that contains an error code.
Our implementation converts the validation string into an enum value and then we process the enumeration as there are some error code we just don't care about.
The question becomes, how to handle the loop of messages in a Kotlin way:
response.validationErrors?.forEach {
val mediaFailure = decodeValidationMessage(it.message)
if (mediaFailure != MediaFailure.Unknown) {
return when (mediaFailure) {
MediaFailure.Encrypted -> DomainResponse(ErrorReasonCode.ERR_DOCUMENT_ENCRYPTED)
MediaFailure.NotSupported -> Response.validationFailed()
MediaFailure.InternalError -> Response.serviceFailed()
else -> throw NotImplementedError()
}
}
}
Here we loop through all the messages, then once the message error is not "Unknown" it returns the necessary response to the caller.
However, IntelliJ wants the else path, even though the if prevents that from happening.
Is there a proper Kotlin way of implementing this kind of loop?
From what I understood, you want to return a response for the first mediaFailure which is not MediaFailure.Unknown and you don't want that throw NotImplementedError() part in your function.
One way to fix this is to remove the if condition and continue the forEach loop when MediaFailure.Unknown is found.
response.validationErrors?.forEach {
val mediaFailure = decodeValidationMessage(it.message)
return when (mediaFailure) {
MediaFailure.Encrypted -> DomainResponse(ErrorReasonCode.ERR_DOCUMENT_ENCRYPTED)
MediaFailure.NotSupported -> Response.validationFailed()
MediaFailure.InternalError -> Response.serviceFailed()
MediaFailure.Unknown -> return#forEach // continue the loop
}
}
I think this is one of the many cases when it pays to step back from the code a bit and try to look at the big picture. To ask “What's the ultimate goal here? What am I trying to achieve with this code?”
(In traditional, lower-level languages, almost anything you want to do with a list or array requires a loop, so you get into the habit of reaching for a for or while without thinking. But there are often alternative approaches in Kotlin that can be more concise, clearer, and harder to get wrong. They tend to be more about what you're trying to achieve, rather than how.)
In this case, it looks you want to find the first item which decodes to give a known type (i.e. not MediaFailure.Unknown), and return a value derived from that.
So here's an attempt to code that:
val message = response.validationErrors?.asSequence()
?.map{ decodeValidationMessage(it.message) }
?.firstOrNull{ it != MediaFailure.Unknown }
return when (message) {
MediaFailure.Encrypted -> DomainResponse(ErrorReasonCode.ERR_DOCUMENT_ENCRYPTED)
MediaFailure.NotSupported -> Response.validationFailed()
MediaFailure.InternalError, null -> Response.serviceFailed()
else -> throw NotImplementedError()
}
This is still fairly similar to your code, and it's about as efficient. (Thanks to the asSequence(), it doesn't decode any more messages than it needs to.) But the firstOrNull() makes clear what you're looking for; and it's obvious that you go on to process only that one message — a fact which is rather lost in the original version.
(If there are no valid messages, message will be null and so this will return serviceFailed(), as per comments.)
There are of course many ways to skin a cat, and I can think of several variations. (It's often a worthwhile exercise to come up with some — if nothing else, it gives you more confidence in the version you end up with!) Try to pick whichever seems clearest, simplest, and best matches the big picture of what you're doing; that tends to work out best in the long run.
I am working on testing a flux. I don't know how many elements exactly the flux has. Initially I have tried with StepVerifier and faced issues as i do not know the elements. Later I have referred this question and tried the same but I am getting the below error:
java.lang.AssertionError: expectation "expectComplete" failed (expected: onComplete(); actual: onNext
My understanding is that, my code is expecting a complete signal but the flux has some more elements left(so it gives onNext() instead of onComplete()). Please help me to understand where I am missing things. Below is my code:
StepVerifier.create(flux)
.recordWith(ArrayList::new)
.consumeRecordedWith(elements-> {assertThat(elements.size()).isGreaterThan(0);})
.verifyComplete();
You're not actually consuming your Flux, you're just setting up what happens when it's consumed. Your verifyComplete(); call then fails, understandably, because the Flux hasn't been consumed at all, and it's thus not complete!
You need to add a thenConsumeWhile() call to actually consume it.
If you really need to use AssertJ as you do above, then you can do:
StepVerifier.create(flux)
.recordWith(ArrayList::new)
.thenConsumeWhile(x -> true)
.consumeRecordedWith(elements -> {
assertThat(elements.isEmpty()).isFalse();
})
.verifyComplete();
However, there's no need for AssertJ here - the reactor test package is enough, and adding additional testing frameworks makes the testing code much less clear IMHO. So if you're not wedded to AssertJ, just do:
StepVerifier.create(flux)
.recordWith(ArrayList::new)
.thenConsumeWhile(x -> true)
.expectRecordedMatches(elements -> !elements.isEmpty())
.verifyComplete();
Note that in real-world use, you'd probably want to adjust the predicate in thenConsumeWhile so that it runs a check against each element in turn, too. I've also adjusted the above code to use isEmpty() rather than checking if size()>0, as it's semantically clearer while achieving the same purpose.
From the same issue, with something new: I had so many entries in my flux that it couldn't fit into the memory (yes, those test case fixtures were designed that way)...
So buffering everything into a List wasn't an option.
And I tried different API methods on StepVerifier and found the following to work:
StepVerifier.create( myFlux )
.thenConsumeWhile( Predicate<T>, Consumer<T> )
.verifyComplete();
I literally did
StepVerifier.create( myFlux )
.thenConsumeWhile( __ -> true, entry -> {
// assertions
} )
.verifyComplete();
I noticed that Rust does not have exceptions. How to do error handling in Rust and what are the common pitfalls? Are there ways to control flow with raise, catch, reraise and other stuff? I found inconsistent information on this.
Rust generally solves errors in two ways:
Unrecoverable errors. Once you panic!, that's it. Your program or thread aborts because it encounters something it can't solve and its invariants have been violated. E.g. if you find invalid sequences in what should be a UTF-8 string.
Recoverable errors. Also called failures in some documentation. Instead of panicking, you emit a Option<T> or Result<T, E>. In these cases, you have a choice between a valid value Some(T)/Ok(T) respectively or an invalid value None/Error(E). Generally None serves as a null replacement, showing that the value is missing.
Now comes the hard part. Application.
Unwrap
Sometimes dealing with an Option is a pain in the neck, and you are almost guaranteed to get a value and not an error.
In those cases it's perfectly fine to use unwrap. unwrap turns Some(e) and Ok(e) into e, otherwise it panics. Unwrap is a tool to turn your recoverable errors into unrecoverable.
if x.is_some() {
y = x.unwrap(); // perfectly safe, you just checked x is Some
}
Inside the if-block it's perfectly fine to unwrap since it should never panic because we've already checked that it is Some with x.is_some().
If you're writing a library, using unwrap is discouraged because when it panics the user cannot handle the error. Additionally, a future update may change the invariant. Imagine if the example above had if x.is_some() || always_return_true(). The invariant would changed, and unwrap could panic.
? operator / try! macro
What's the ? operator or the try! macro? A short explanation is that it either returns the value inside an Ok() or prematurely returns error.
Here is a simplified definition of what the operator or macro expand to:
macro_rules! try {
($e:expr) => (match $e {
Ok(val) => val,
Err(err) => return Err(err),
});
}
If you use it like this:
let x = File::create("my_file.txt")?;
let x = try!(File::create("my_file.txt"));
It will convert it into this:
let x = match File::create("my_file.txt") {
Ok(val) => val,
Err(err) => return Err(err),
};
The downside is that your functions now return Result.
Combinators
Option and Result have some convenience methods that allow chaining and dealing with errors in an understandable manner. Methods like and, and_then, or, or_else, ok_or, map_err, etc.
For example, you could have a default value in case your value is botched.
let x: Option<i32> = None;
let guaranteed_value = x.or(Some(3)); //it's Some(3)
Or if you want to turn your Option into a Result.
let x = Some("foo");
assert_eq!(x.ok_or("No value found"), Ok("foo"));
let x: Option<&str> = None;
assert_eq!(x.ok_or("No value found"), Err("No value found"));
This is just a brief skim of things you can do. For more explanation, check out:
http://blog.burntsushi.net/rust-error-handling/
https://doc.rust-lang.org/book/ch09-00-error-handling.html
http://lucumr.pocoo.org/2014/10/16/on-error-handling/
If you need to terminate some independent execution unit (a web request, a video frame processing, a GUI event, a source file to compile) but not all your application in completeness, there is a function std::panic::catch_unwind that invokes a closure, capturing the cause of an unwinding panic if one occurs.
let result = panic::catch_unwind(|| {
panic!("oh no!");
});
assert!(result.is_err());
I would not grant this closure write access to any variables that could outlive it, or any other otherwise global state.
The documentation also says the function also may not be able to catch some kinds of panic.
I've made a large program that opens and closes files and databases, perform writes and reads on them etc among other things. Since there no such thing as "exception handling in go", and since I didn't really know about "defer" statement and "recover()" function, I applied error checking after every file-open, read-write, database entry etc. E.g.
_,insert_err := stmt.Run(query)
if insert_err != nil{
mylogs.Error(insert_err.Error())
return db_updation_status
}
For this, I define db_updation_status at the beginning as "false" and do not make it "true" until everything in the program goes right.
I've done this in every function, after every operation which I believe could go wrong.
Do you think there's a better way to do this using defer-panic-recover? I read about these here http://golang.org/doc/articles/defer_panic_recover.html, but can't clearly get how to use them. Do these constructs offer something similar to exception-handling? Am I better off without these constructs?
I would really appreciate if someone could explain this to me in a simple language, and/or provide a use case for these constructs and compare them to the type of error handling I've used above.
It's more handy to return error values - they can carry more information (advantage to the client/user) than a two valued bool.
What concerns panic/recover: There are scenarios where their use is completely sane. For example, in a hand written recursive descent parser, it's quite a PITA to "bubble" up an error condition through all the invocation levels. In this example, it's a welcome simplification if there's a deferred recover at the top most (API) level and one can report any kind of error at any invocation level using, for example
panic(fmt.Errorf("Cannot %v in %v", foo, bar))
If an operation can fail and returns an error, than checking this error immediately and handling it properly is idiomatic in go, simple and nice to check if anything gets handled properly.
Don't use defer/recover for such things: Needed cleanup actions are hard to code, especially if stuff gets nested.
The usual way to report an error to a caller is to return an error as an extra return value. The canonical Read method is a well-known instance; it returns a byte count and an error.
But what if the error is unrecoverable? Sometimes the program simply cannot continue.
For this purpose, there is a built-in function panic that in effect creates a run-time error that will stop the program (but see the next section). The function takes a single argument of arbitrary type—often a string—to be printed as the program dies. It's also a way to indicate that something impossible has happened, such as exiting an infinite loop.
http://golang.org/doc/effective_go.html#errors