Result
Result<T, E>
is the type used for returning and propagating errors. It is an alternative to throwing exceptions. It is a sealed type with the variants, Ok(T)
, representing success and containing a value, and Err(E)
, representing error and containing an error value.
To better understand the motivation around the Result
type refer to this article.
Example
By using the Result
type, there is no web of try
/catch
statements to maintain and hidden control flow bugs, all control flow is defined.
import 'package:rust/result.dart';
void main() {
final result = processOrder("Bob", 2);
switch(result) {
Ok(:var ok) => print("Success: $ok"),
Err(:var err) => print("Error: $err"),
};
}
Result<String, String> processOrder(String user, int orderNumber) {
final result = makeFood(orderNumber);
if(result case Ok(:final ok)) {
final paymentResult = processPayment(user);
if(paymentResult case Ok(ok:final paymentOk)) {
return Ok("Order of $ok is complete for $user. Payment: $paymentOk");
}
return paymentResult;
}
return result;
}
Result<String, String> makeFood(int orderNumber) {
if (orderNumber == 1) {
return makeHamburger();
} else if (orderNumber == 2) {
return makePizza();
}
return Err("Invalid order number.");
}
Result<String,String> makeHamburger() {
return Err("Failed to make the hamburger.");
}
Result<String,String> makePizza() {
return Ok("pizza");
}
Result<String,String> processPayment(String user) {
if (user == "Bob") {
return Ok("Payment successful.");
}
return Err("Payment failed.");
}
Chaining
Effects on a Result
can be chained in a safe way without
needing to inspect the type.
Result<int,String> result = Ok(4);
Result<String, String> finalResult = initialResult
.map((okValue) => okValue * 2) // Doubling the `Ok` value if present.
.andThen((okValue) => okValue != 0 ? Ok(10 ~/ okValue) : Err('Division by zero')) // Potentially failing operation.
.map((okValue) => 'Result is $okValue') // Transforming the successful result into a string.
.mapErr((errValue) => 'Error: $errValue'); // Transforming any potential error.
// Handling the final `Result`:
finalResult.match(
ok: (value) => print('Success: $value'),
err: (error) => print('Failure: $error'),
);
See the docs for all methods and extensions.
Adding Predictable Control Flow To Legacy Dart Code
At times, you may need to integrate with legacy code that may throw or code outside your project. To handle these situations you can just wrap the code in a helper function like guard
void main() {
Result<int,Object> result = guard(functionWillThrow).mapErr((e) => "$e but was guarded");
print(result);
}
int functionWillThrow() {
throw "this message was thrown";
}
Output:
this message was thrown but was guarded
Dart Equivalent To The Rust "?" Early Return Operator
In Dart, the Rust "?" operator (Early Return Operator) functionality in x?
, where x
is a Result
, can be
accomplished in two ways
into()
if (x case Err()) {
return x.into(); // may not need "into()"
}
into
may be needed to change the S
type of Result<S,F>
for x
to that of the functions return type if
they are different.
into
only exits if after the type check, so you will never mishandle a type change since the compiler will stop you.
Note: There also exists
intoUnchecked
that does not require implicit cast of a Result
Type.
Early Return Key Notation
"Early Return Key Notation" is a take on "Do Notation" and the "Early Return Key"
functions the same way as the "Early Return Operator". The
Early Return Key is typically denoted with $
and when
passed to a Result it unlocks the inner value, or returns to the surrounding context. e.g.
Result<int, String> innerErrFn() => Err("message");
Result<int, String> earlyReturn() => Result(($) { // Early Return Key
int y = 2;
// The function will return here
int x = innerErrFn()[$];
return Ok(x + y);
});
expect(earlyReturn().unwrapErr(), "message");
Using the Early Return Key Notation reduces the need for pattern matching or checking, in a safe way. This is quite a powerful tool. See here for another example.
For async, use the Result.async
e.g.
FutureResult<int, String> earlyReturn() => Result.async(($) async {
...
});