DartでSwiftのResult型のような型を定義する

やりたいこと

Dart言語でSwiftのResult型のようなunion型を定義したい。Swiftの場合は以下のように定義ができる。 (ビルトインタイプがあるので実際には不要だけど)

// 定義
enum Result<T, E: Error> {
    case success(T)
    case failure(E)
}

// 利用

func do(result: Result<String, SomeError>) {
    switch result {
    case let .success(value):
        debugPrint(value)
    case let .failure(error):
        debugPrint(error)
    }
}

swiftのassociated enumは非常に強力で、上記のように成功時・失敗時を名前の通りに分岐でき、そのケースの場合だけにある値を安全に取り出すことができる。

これと同様のことをDartでも行いたい

解決案

以下のようなコードで近いことができる。is演算子で検査することでif文の中ではその変数を検査した型として扱うことができる。ついでにfactoryコンストラクタでResult.successのように書けるようにしてそれっぽくしている。

// 定義

abstract class Result<Value, FailureType extends Exception> {
  factory Result.success(Value value) => ResultSuccess(value);
  factory Result.failure(FailureType error) => ResultFailure(error);
  Result();
}

class ResultSuccess<Value, FailureType extends Exception>
    extends Result<Value, FailureType> {
  final Value value;
  ResultSuccess(this.value);
}

class ResultFailure<Value, FailureType extends Exception>
    extends Result<Value, FailureType> {
  final FailureType error;
  ResultFailure(this.error);
}

// 利用

void process() {
  processResult(Result.success("value"));
  processResult(Result.failure(Exception("message")));
}

void processResult(Result<String, Exception> result) {
  if (result is ResultSuccess<String, Exception>) {
    log(result.value);
  } else if (result is ResultFailure<String, Exception>) {
    log(result.error.toString());
  }
}

まとめ

swift enumのassociated valuesほどではないが、unionぽいものが作れた。 ちなみにunionのように使えるものは、freezedを使えば便利なメソッドまで含めて生成してくれるので自前で定義するよりもそちらのほうが良い。 この記事ではライブラリのコードジェネレータに頼らなくても簡単なことならできるぞ、という実験がしたかったのでした。