Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】How to Implement Rust’s Result Type in Dart Programming Language.

2025年10月04日に「Dev.to」が公開したITニュース「How to Implement Rust’s Result Type in Dart Programming Language.」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

DartでRustの「Result型」を実装し、処理の成功・失敗を明確に扱う方法。`Result`クラスを自作し、成功(Ok)なら値、失敗(Err)ならエラーを返す。null回避と型安全なエラー処理を実現する。エラー種類を列挙型で管理し`then`で分岐、信頼性・可読性を高める。

ITニュース解説

システム開発において、プログラムが常に完璧に動作するとは限らない。ファイルの読み込みに失敗したり、データベースからデータが見つからなかったり、入力値が不正だったりと、さまざまな問題が発生する可能性がある。これらの問題を適切に処理することが、堅牢で信頼性の高いソフトウェアを作る上で非常に重要である。 多くのプログラミング言語では、エラーが発生した場合に特別な値(例えばnull)を返したり、例外処理(try-catch)を使ったりする方法が一般的だ。しかし、nullを返す方法は、開発者がその値がnullである可能性を常に意識し、チェックする手間がかかる。チェックを怠ると、予期せぬエラー(NullPointerExceptionなど)が発生し、プログラムがクラッシュする原因となることがある。また、例外処理は、エラーが発生した瞬間に現在の処理の流れを中断し、別の場所でエラーを捕捉する仕組みだが、どの関数がどのような状況で例外を発生させる可能性があるのかをコードを読むだけでは把握しにくい場合がある。 そこで注目されるのが、Rust言語で採用されているResult型という考え方である。Result型は、ある操作が成功した場合は「成功の値」を、失敗した場合は「エラーの値」を、どちらか一方を明示的に返すことを強制する仕組みだ。これにより、開発者は成功と失敗の両方のケースを必ず考慮してコードを書く必要があり、エラーの取りこぼしが格段に減る。Dart言語にはこのResult型が標準では存在しないが、クラスを定義することで同様の仕組みを自作できる。

まず、Result型をDartで実現するために、Result<T, E>という名前のクラスを作成する。この<T, E>は「ジェネリクス」と呼ばれる機能で、Tには成功時に返される値の型を、Eにはエラー時に返される値の型を指定できる。例えば、整数を返す可能性のある処理で文字列エラーを扱う場合はResult<int, String>のように使う。 このResultクラスは、valueerrorという2つのフィールドを持つ。valueは成功時の値を格納し、errorはエラー時の値を格納する。ただし、Result型では成功と失敗のどちらか一方しか存在しないため、valueerrorのどちらか一方は必ずnullになる設計とする。 値を持つケースとエラーを持つケースを明確にするため、Result.Ok(T value)Result.Err(E error)という2つの特別なコンストラクタを用意する。Result.Okコンストラクタは、成功した値をvalueフィールドに格納し、Result.Errコンストラクタは、エラーの情報をerrorフィールドに格納する。これにより、関数の戻り値としてResult.Ok(具体的な成功値)またはResult.Err(具体的なエラー情報)のいずれかを返すことで、その操作が成功したのか失敗したのかを呼び出し元に明確に伝えることができる。

このResult型を使った具体的な例として、商品のレコードを管理し、IDを使って検索するRecordStoreクラスを考える。このRecordStoreクラスには、指定されたIDのレコードを探すgetRecordByIdというメソッドがある。 従来のプログラミングでは、getRecordByIdメソッドがレコードを見つけられなかった場合にnullを返すように実装することが一般的であった。しかし、Result型を使う場合は異なる。getRecordByIdメソッドは、Result<Record, String>という型を戻り値として返す。これは、成功すればRecord型のオブジェクトを返し、失敗すればString型のエラーメッセージを返すという意味である。 もし指定されたIDのレコードが見つかった場合、このメソッドはResult.Ok(見つかったRecordオブジェクト)を返す。一方、レコードが見つからなかった場合は、Result.Err('NotFound')というエラーメッセージと共にResultオブジェクトを返す。このように、成功と失敗が明示的に表現されるため、メソッドの呼び出し側は、戻り値がResult.OkなのかResult.Errなのかを必ず確認し、それぞれの状況に応じた処理を記述しなければならない。これは、Record?のようにnullかもしれないオブジェクトを返す場合と比べて、開発者がエラー処理を「うっかり忘れる」ことを防ぐ上で非常に有効な方法だ。

さらにエラーの種類を詳細に扱うために、エラーを単なる文字列ではなく、「列挙型(enum)」として定義することもできる。例えば、enum Error { recordNotFound, notValidId }のように、recordNotFound(レコードが見つからないエラー)とnotValidId(無効なIDのエラー)という具体的なエラー種別を定義する。 getRecordByIdメソッドを修正し、IDが0以下の場合はResult.Err(Error.notValidId)を、レコードが見つからない場合はResult.Err(Error.recordNotFound)を返すようにする。このように列挙型を使うことで、エラーの種類がより明確になり、開発者はエラーメッセージの文字列を間違える心配がなくなる。また、エラー処理を行う際にswitch文と組み合わせることで、考えられる全てのエラーケースを網羅しているかをコンパイラがチェックしてくれるため、より堅牢なコードを書くことができるようになる。

Result型をさらに使いやすくするために、Resultクラス自体にthenというメソッドを追加する。このthenメソッドは、成功時の処理を記述するokという関数と、失敗時の処理を記述するerrという関数の二つを引数として受け取る。 thenメソッドの内部では、現在のResultオブジェクトが成功(valueがnullでない)しているのか、それとも失敗(errorがnullでない)しているのかを判断する。そして、成功していればok関数にvalueを渡して実行し、失敗していればerr関数にerrorを渡して実行する。 これにより、getRecordByIdメソッドから返されたResultオブジェクトに対して、result.then(ok: (record) { ... }, err: (error) { ... }); のような形式で、成功した場合と失敗した場合の処理をスマートに記述できるようになる。特にerrの処理では、前述の列挙型とswitch文を組み合わせることで、エラーの種類ごとに適切なメッセージ表示や後続処理を簡潔に行える。例えば、IDが無効だった場合は「無効なIDです」、レコードが見つからなかった場合は「表示するものがありません」といった具体的なメッセージをユーザーに提示できる。

このように、Rust言語のResult型の考え方をDartに導入することで、成功と失敗を明確に区別し、エラー処理をコードに明示的に組み込むことができるようになる。Resultクラスと、それを補完する列挙型、そしてthenメソッドを組み合わせることで、開発者はエラーの取りこぼしを防ぎ、コードの信頼性を大幅に向上させることが可能となる。また、エラーの発生可能性がコードを読んだだけで理解しやすくなり、結果として保守しやすく、理解しやすい高品質なソフトウェア開発に繋がるだろう。このアプローチは、プログラムの安定性を高め、ユーザーエクスペリエンスを向上させるための強力なツールとなる。

関連コンテンツ

関連IT用語

関連ITニュース