Module for error handling with the Result type.
The Result type is used for returning and propagating errors. It has two variants:
#ok(Ok)
, representing success and containing a value, and #err(Err)
, representing
error and containing an error value.
Import from the base library to use this module.
motoko name=import
import Result "mo:base/Result";
The Result type used for returning and propagating errors.
The simplest way of working with Results is to pattern match on them. For example:
motoko include=import
import Text "mo:base/Text";
type Email = Text;
type ErrorMessage = Text;
func validateEmail(email : Text) : Result.Result<Email, ErrorMessage> {
let parts = Text.split(email, #char '@');
let beforeAt = parts.next();
let afterAt = parts.next();
switch (beforeAt, afterAt) {
case (?local, ?domain) {
if (local == "") return #err("Username cannot be empty");
if (not Text.contains(domain, #char '.')) return #err("Invalid domain format");
#ok(email)
};
case _ #err("Email must contain exactly one @ symbol")
}
};
assert validateEmail("user@example.com") == #ok("user@example.com");
assert validateEmail("invalid.email") == #err("Email must contain exactly one @ symbol");
assert validateEmail("@domain.com") == #err("Username cannot be empty");
assert validateEmail("user@invalid") == #err("Invalid domain format");
public func equal<Ok, Err>(
result1 : Result<Ok, Err>,
result2 : Result<Ok, Err>,
equalOk : (Ok, Ok) -> Bool,
equalErr : (Err, Err) -> Bool
) : Bool
Compares two Results for equality.
Example:
motoko include=import
import Nat "mo:base/Nat";
import Text "mo:base/Text";
let result1 = #ok 10;
let result2 = #ok 10;
let result3 = #err "error";
assert Result.equal<Nat, Text>(result1, result2, Nat.equal, Text.equal);
assert not Result.equal<Nat, Text>(result1, result3, Nat.equal, Text.equal);
public func compare<Ok, Err>(
result1 : Result<Ok, Err>,
result2 : Result<Ok, Err>,
compareOk : (Ok, Ok) -> Order.Order,
compareErr : (Err, Err) -> Order.Order
) : Order.Order
Compares two Result values. #ok
is larger than #err
. This ordering is
arbitrary, but it lets you for example use Results as keys in ordered maps.
Example:
motoko include=import
import Nat "mo:base/Nat";
import Text "mo:base/Text";
let result1 = #ok 5;
let result2 = #ok 10;
let result3 = #err "error";
assert Result.compare<Nat, Text>(result1, result2, Nat.compare, Text.compare) == #less;
assert Result.compare<Nat, Text>(result2, result1, Nat.compare, Text.compare) == #greater;
assert Result.compare<Nat, Text>(result1, result3, Nat.compare, Text.compare) == #greater;
public func chain<Ok1, Ok2, Err>(result : Result<Ok1, Err>, f : Ok1 -> Result<Ok2, Err>) : Result<Ok2, Err>
Allows sequencing of Result values and functions that return Results themselves.
motoko include=import
type Result<Ok,Err> = Result.Result<Ok, Err>;
func largerThan10(x : Nat) : Result<Nat, Text> =
if (x > 10) { #ok(x) } else { #err("Not larger than 10.") };
func smallerThan20(x : Nat) : Result<Nat, Text> =
if (x < 20) { #ok(x) } else { #err("Not smaller than 20.") };
func between10And20(x : Nat) : Result<Nat, Text> =
Result.chain(largerThan10(x), smallerThan20);
assert between10And20(15) == #ok(15);
assert between10And20(9) == #err("Not larger than 10.");
assert between10And20(21) == #err("Not smaller than 20.");
public func flatten<Ok, Err>(result : Result<Result<Ok, Err>, Err>) : Result<Ok, Err>
Flattens a nested Result.
motoko include=import
assert Result.flatten<Nat, Text>(#ok(#ok(10))) == #ok(10);
assert Result.flatten<Nat, Text>(#err("Wrong")) == #err("Wrong");
assert Result.flatten<Nat, Text>(#ok(#err("Wrong"))) == #err("Wrong");
public func mapOk<Ok1, Ok2, Err>(result : Result<Ok1, Err>, f : Ok1 -> Ok2) : Result<Ok2, Err>
Maps the Ok
type/value, leaving any Err
type/value unchanged.
Example:
motoko include=import
let result1 = #ok(42);
let result2 = #err("error");
let doubled1 = Result.mapOk<Nat, Nat, Text>(result1, func x = x * 2);
assert doubled1 == #ok(84);
let doubled2 = Result.mapOk<Nat, Nat, Text>(result2, func x = x * 2);
assert doubled2 == #err("error");
public func mapErr<Ok, Err1, Err2>(result : Result<Ok, Err1>, f : Err1 -> Err2) : Result<Ok, Err2>
Maps the Err
type/value, leaving any Ok
type/value unchanged.
Example:
motoko include=import
let result1 = #ok(42);
let result2 = #err("error");
let mapped1 = Result.mapErr<Nat, Text, Text>(result1, func x = x # "!");
assert mapped1 == #ok(42);
let mapped2 = Result.mapErr<Nat, Text, Text>(result2, func x = x # "!");
assert mapped2 == #err("error!");
public func fromOption<Ok, Err>(x : ?Ok, err : Err) : Result<Ok, Err>
Create a result from an option, including an error value to handle the null
case.
motoko include=import
assert Result.fromOption(?42, "err") == #ok(42);
assert Result.fromOption(null, "err") == #err("err");
public func toOption<Ok, Err>(result : Result<Ok, Err>) : ?Ok
Create an option from a result, turning all #err into null
.
motoko include=import
assert Result.toOption(#ok(42)) == ?42;
assert Result.toOption(#err("err")) == null;
public func forOk<Ok, Err>(result : Result<Ok, Err>, f : Ok -> ())
Applies a function to a successful value and discards the result. Use
forOk
if you're only interested in the side effect f
produces.
motoko include=import
var counter : Nat = 0;
Result.forOk<Nat, Text>(#ok(5), func (x : Nat) { counter += x });
assert counter == 5;
Result.forOk<Nat, Text>(#err("Error"), func (x : Nat) { counter += x });
assert counter == 5;
public func forErr<Ok, Err>(result : Result<Ok, Err>, f : Err -> ())
Applies a function to an error value and discards the result. Use
forErr
if you're only interested in the side effect f
produces.
motoko include=import
var counter : Nat = 0;
Result.forErr<Nat, Text>(#err("Error"), func (x : Text) { counter += 1 });
assert counter == 1;
Result.forErr<Nat, Text>(#ok(5), func (x : Text) { counter += 1 });
assert counter == 1;
public func isOk(result : Result<Any, Any>) : Bool
Whether this Result is an #ok
.
Example:
motoko include=import
assert Result.isOk(#ok(42));
assert not Result.isOk(#err("error"));
public func isErr(result : Result<Any, Any>) : Bool
Whether this Result is an #err
.
Example:
motoko include=import
assert Result.isErr(#err("error"));
assert not Result.isErr(#ok(42));
public func assertOk(result : Result<Any, Any>)
Asserts that its argument is an #ok
result, traps otherwise.
Example:
motoko include=import
Result.assertOk(#ok(42)); // succeeds
// Result.assertOk(#err("error")); // would trap
public func assertErr(result : Result<Any, Any>)
Asserts that its argument is an #err
result, traps otherwise.
Example:
motoko include=import
Result.assertErr(#err("error")); // succeeds
// Result.assertErr(#ok(42)); // would trap
public func fromUpper<Ok, Err>(result : {#Ok : Ok; #Err : Err}) : Result<Ok, Err>
Converts an upper cased #Ok
, #Err
result type into a lowercased #ok
, #err
result type.
On the IC, a common convention is to use #Ok
and #Err
as the variants of a result type,
but in Motoko, we use #ok
and #err
instead.
Example:
motoko include=import
let upper = #Ok(42);
let lower = Result.fromUpper(upper);
assert lower == #ok(42);
public func toUpper<Ok, Err>(result : Result<Ok, Err>) : {#Ok : Ok; #Err : Err}
Converts a lower cased #ok
, #err
result type into an upper cased #Ok
, #Err
result type.
On the IC, a common convention is to use #Ok
and #Err
as the variants of a result type,
but in Motoko, we use #ok
and #err
instead.
Example:
motoko include=import
let lower = #ok(42);
let upper = Result.toUpper(lower);
assert upper == #Ok(42);