Motoko では, エラーの値を表現し, 取り扱う方法がおもに三つあります:

There are three primary ways to represent and handle errors values in Motoko:

Our Example API

ここでわたしたちは, Todo アプリケーションのための API を構築しており, ユーザが自分の Todo の中の一つに Done の印を付けられるような関数を公開しようとしているとしましょう. 簡単のため, TodoId を受け取り, その Todo が何秒間オープンになっていたかを示す Int を返すことにします. また, この API はアクタの中で動いているものとするので, 非同期値を返します. 特に悪いことが起きないとすれば, 次のような API になります:

Let’s assume we’re building an API for a Todo application and want to expose a function that lets a user mark one of their Todo’s as Done. To keep it simple we’ll accept a TodoId and return an Int that represents how many seconds the Todo has been open. We’re also assuming we’re running in our own actor so we return an async value. If nothing would ever go wrong that would leave us with the following API:

func markDone(id : TodoId) : async Int

このドキュメントで用いるすべての型とヘルパを参考までに載せておきます:

The full definition of all types and helpers we’ll use in this document is included for reference:

<div class="informalexample">

import Int "mo:base/Int";
import Hash "mo:base/Hash";
import Map "mo:base/HashMap";
import Time "mo:base/Time";
import Result "mo:base/Result";
import Error "mo:base/Error";

type Time = Int;
type Seconds = Int;

func secondsBetween(start : Time, end : Time) : Seconds =
  (end - start) / 1_000_000_000;

public type TodoId = Nat;

type Todo = { #todo : { text : Text; opened : Time }; #done : Time };
type TodoMap = Map.HashMap<TodoId, Todo>;

var idGen : TodoId = 0;
let todos : TodoMap = Map.HashMap(32, Int.equal, Hash.hash);

private func nextId() : TodoId {
  let id = idGen;
  idGen += 1;
  id
};

/// Creates a new todo and returns its id
public shared func newTodo(txt : Text) : async TodoId {
  let id = nextId();
  let now = Time.now();
  todos.put(id, #todo({ text = txt; opened = now }));
  id
};

</div>

When things go wrong

Todo に done の印を付けるのが失敗するような条件にはどんなものがあるでしょうか.

We now realize that there are conditions under which marking a Todo as done fails.

We’ll now talk about the different ways to communicate these errors in Motoko and slowly improve our solution.

Motoko でこれらのエラーをやり取りするいろいろなやり方について述べるので, 我々のソリューションを少しずつ良くしていきましょう.

What error type to prefer

How not to do things

エラーを参照報告する, とりわけ簡単ではあるけれど 良くない やり方として, 番兵 と呼ばれるような特別な値を使うやり方があります. 例えば markDone 関数では, 値 -1 を使って何か間違いが起きたことを知らせることにしましょう. でもこの場合に起こりがちなのは, そのエラー条件をチェックせず, コード中でその値のまま仕事を続けることです. これにより, エラー検出の遅延や欠落につながる可能性があり, まったくお奨めできません.

One particularly easy and bad way of reporting errors is through the use of a sentinel value. For example, for our markDone function we might decide to use the value -1 to signal that something failed. The callsite then has to check the return value against this special value and report the error. But it’s way too easy to not check for that error condition and continue to work with that value in our code. This can lead to delayed or even missing error detection and is strongly discouraged.

Definition:

public shared func markDoneBad(id : TodoId) : async Seconds {
  switch (todos.get(id)) {
    case (?(#todo(todo))) {
      let now = Time.now();
      todos.put(id, #done(now));
      secondsBetween(todo.opened, now)
    };
    case _ { -1 };
  }
};