Result Typeとは何か、Resultコンテナ型とモバイル開発での仕組み

著者: IT Sectr 公開日: 2026-05-26 読了時間: 9 分

Result Type — 成功(Success)または失敗(Failure)で完了する操作の結果を表すコンテナ型です。例外とは異なり、Resultはスタック巻き戻しなしでエラーを通常の値として渡すため、想定されるエラーの処理がより安全で合成可能になります。Apple Swift Documentation (2026)によると、SwiftのResult<Success, Failure>は、プログラムの実行を中断することなく、mapとflatMapによる自動エラー処理で操作を連鎖できます。

重要なポイント

  • Result Type — 2つの状態(Success(データ)とFailure(エラー))を持つジェネリックコンテナで、スタック巻き戻しなし
  • Swiftの場合 Result<Success, Failure> — map、flatMap、mapError、getメソッドを持つビルトイン型
  • Kotlinの場合 Result<T>は成功またはThrowableを表し、getOrNull、getOrDefault、fold関数を持つ
  • Dartの場合 標準のResult<T>はDart 3.0から利用可能。fpdartパッケージのEitherも使用可能
  • 合成 mapとflatMapにより、Resultを返す複数の操作をネストしたチェックなしで結合可能

Result Typeとは

Result Typeは、成功または失敗する可能性のある操作の結果をカプセル化するジェネリック型です。エラーが通常のフローを中断し、catchブロックを見つけるためにスタック巻き戻しが必要な例外とは異なり、Resultはエラーを通常の値として渡します。呼び出し側は常にオブジェクトを受け取り、その処理方法を決定します。これは特に想定されるエラー(無効な入力、ビジネスルール、サーバー拒否など)に有用で、例外では重すぎるメカニズムになります。

Resultの概念は関数型プログラミングに由来し、同様の型はEither(またはLeft/Right)と呼ばれます。SwiftではResultはSwift 5.0で標準型になりました。KotlinではResult<T>が標準ライブラリに登場し、Dart 3.0ではビルトインのResult<T>が導入されました。各実装はコンテナを操作するメソッドを提供します:map(成功値の変換)、flatMap(Result関数の連鎖)、mapError(エラーの変換)、fold(両方のケースを処理)。Result Typeはモバイル開発における関数型エラーハンドリングの要であり、想定されるシナリオに対するtry-catchの代替手段です。

Resultの主な利点は合成可能性です。それぞれが失敗する可能性のある複数の操作を、ネストしたtry-catchブロックなしで1つのチェーンに結合できます。チェーン内のいずれかの操作がFailureで失敗すると、チェーン全体が中断されFailureを返します — if条件やcatchブロックは一切不要です。これにより、特に複数のシーケンシャルなAPIリクエストやビジネスルールチェックのシナリオで、コードが直線的で読みやすくなります。

SwiftのResult

Swiftでは、Result<Success, Failure>は2つのケースを持つenumです:.success(Success)と.failure(Failure)。FailureはErrorプロトコルによって制約されます。SwiftのResultはmap、flatMap、mapError、getメソッドを持つ完全な関数型です。getは特別なメソッドで、結果が成功の場合はSuccess値を返し、Failureの場合はエラーをスローします。これにより、Resultは関数型スタイルと例外ベースのスタイルの間の橋渡しとして機能します。チェーン内でmap/flatMapで処理し、最後にdo-catch付きのgetを使用してthrowsコードと統合します。

Result enumと網羅的なswitch

SwiftのResultはジェネリックパラメータを持つenumであり、コンパイラはswitchやdo-catchによる網羅的な処理を強制できます。NetworkError enumに新しいケースを追加すると、そのケースが処理されていないすべてのswitch式でコンパイラがエラーを生成します。網羅的チェックは、例外に対するResultの主な利点です。コンパイラはビルド時にすべての可能なエラーが考慮されていることを保証します。対照的に、Swiftでは例外はコンパイラによってチェックされません(throwsのみが宣言され、エラー型は宣言されません)。

swift
enum NetworkError: Error {
    case badURL
    case requestFailed(String)
    case decodingFailed
}

func fetchUser(id: Int) -> Result<User, NetworkError> {
    guard let url = URL(string: "https://api.example.com/users/\(id)") else {
        return .failure(.badURL)
    }
    let result = performRequest(url: url)
    switch result {
    case let .success(data):
        if let user = try? JSONDecoder().decode(User.self, from: data) {
            return .success(user)
        }
        return .failure(.decodingFailed)
    case let .failure(error):
        return .failure(.requestFailed(error.localizedDescription))
    }
}

// switchでの使用
let result = fetchUser(id: 42)
switch result {
case .success(let user):
    showUser(user)
case .failure(.badURL):
    logError("Invalid URL")
case .failure(.requestFailed(let msg)):
    showAlert(msg)
case .failure(.decodingFailed):
    logError("Decoding error")
}

Result<Success, Failure>は型レベルでエラーを型付けできます。fetchUser関数はResult<User, NetworkError>を返し、NetworkErrorは3つのケースを持つ具体的なenumです。呼び出し側は網羅的なカバレッジでswitchを使用して各ケースを処理します — コンパイラはすべてのバリアントが処理されたことを確認します。網羅的なswitchは例外に対するResultの重要な利点です。コンパイラは.badURL、.requestFailed、.decodingFailedの処理を忘れないことを保証します。例外の場合、コンパイラは処理を要求せず、コードレビューでcatch(.badURL)の欠落が見逃される可能性があります。

KotlinのResult

Kotlinでは、Result<T>は標準ライブラリのビルトイン型で、成功(T)またはエラー(Throwable)を表します。Swiftとは異なり、KotlinのResultは具体的なエラー型を指定できません — Throwableのみです。これは簡潔さのために行われましたが、when式による追加のエラー型チェックが必要です。KotlinのResultはfold(両方のケースを処理)、getOrNull(成功またはnull)、getOrDefault(成功またはデフォルト)、map、recover、andThen関数をサポートしています。Kotlinの特徴として、Resultは関数の境界を越えた直接的な伝搬を意図していません — 追加の適応なしにAndroid SDKメソッドやKotlin Coroutines APIの戻り値型として使用できません。

kotlin
fun parseJson(input: String): Result<JsonObject> {
    return runCatching {
        JsonParser.parseString(input).asJsonObject()
    }
}

fun validateEmail(email: String): Result<String> {
    return if (email.contains("@")) {
        Result.success(email.trim())
    } else {
        Result.failure(IllegalArgumentException("Invalid email"))
    }
}

data class SignupData(val name: String, val email: String)

fun processSignup(name: String, email: String): SignupResult {
    return validateEmail(email).fold(
        onSuccess = { validateName(name) },
        onFailure = { SignupResult.Error("Invalid email") }
    )
}

runCatchingはKotlinの便利なラッパーで、任意の例外をキャッチしてResult.failureを返します。validateEmailは検証に応じてResult.successまたはResult.failureを返します。foldは両方のケースをコンパクトに処理します。Successの場合は次の検証関数が呼び出され、Failureの場合はSignupResult.Errorが返されます。重要:KotlinのResultはdata classフィールドへの格納やsuspend関数の境界を越えた直接的な受け渡しを意図していません — Jetpack ComposeやMVVMでUI状態を表現するには、カスタムのsealed class(Success/Error/Loading)を使用してください。

DartとFlutterのResult

Dart 3.0以降、標準ライブラリにはビルトインのResult<T>が含まれています — 2つのコンストラクタを持つsealed classです:T.ok()(成功)とError.error()(ObjectとStackTraceを持つエラー)。Dart 3.0以前は、Flutter開発者はdartzパッケージのEither<L, R>やカスタムのsealed classを使用していました。DartのビルトインResultはミニマルで、map/flatMapを直接提供しません — これらの関数はwhenを使用するか、拡張メソッドを使って実装する必要があります。本格的な関数型処理には、fpdartのEitherがmap、flatMap、mapLeft、fold、andThen、bind演算子をサポートするより強力なソリューションです。

fpdartのEither

Either<L, R>はfpdartパッケージの左バイアス型で、Leftがエラー、Rightが成功です。ビルトインのResult<T>とは異なり、Eitherは型パラメータレベル(L)でエラーを型付けし、コンパイル時にエラー型を区別できます。fpdartパッケージは関数型コンビネータの完全なセットを提供します:map(Right -> Right)、mapLeft(Left -> Left)、flatMap(bind — ネストしたEither)、andThen(変換なしの連鎖)、fold(Eitherからの脱出)、getOrElse(デフォルト値)。関数型アプローチのFlutterアプリケーションでは、Eitherが事実上の標準です。

dart
import 'dart:convert';
import 'package:fpdart/fpdart.dart';

class UserService {
    Either<AppError, User> fetchUser(String id) {
        try {
            final response = await http.get(
                Uri.parse('https://api.example.com/users/$id')
            );
            if (response.statusCode == 200) {
                final user = User.fromJson(
                    json.decode(response.body)
                );
                return Either.of(user);
            }
            return Either.left(
                AppError.serverError(response.statusCode)
            );
        } on SocketException catch (e) {
            return Either.left(AppError.networkError(e.message));
        }
    }
}

// foldでの使用
final result = await service.fetchUser('42');
result.fold(
    (left)  => showError(left.message),
    (right) => showUser(right),
);

Either<L, R>(fpdart)は左バイアス型です:Left — エラー、Right — 成功。fetchUserはEither<AppError, User>を返し、AppErrorは具体的なエラー型(serverError、networkError)を持つsealed classです。foldは両方のケースを処理します:最初のコールバックはLeft(エラー)用、2番目はRight(成功)用です。Dart 3.0ではビルトインのResultもfoldをサポートしていますが、map/flatMapは提供しません。チェーンには、fpdartのEitherがmap、flatMap(bind)、mapLeft、andThenを提供します — エラー合成のための関数型コンビネータの完全なセットです。

mapとflatMapによるResultの合成

例外に対するResultの主な利点は合成です。それぞれが失敗する可能性のある複数の操作がある場合、ネストしたifやtry-catchなしでmapとflatMapを使って連鎖できます。mapは成功値を変換します:Result.success(x) -> Result.success(f(x))。flatMap(bindやandThenとも呼ばれる)は、変換自体がResultを返す場合に使用します:Result.success(x) -> f(x) -> Result<Y>。いずれかのステップがFailureで失敗すると、後続の操作は実行されません — チェーンは中断されます。

kotlin
data class UserRequest(val userId: String, val token: String)

sealed class AuthError {
    data object InvalidToken : AuthError()
    data class UserNotFound(val id: String) : AuthError()
}

typealias Outcome<T> = Either<AuthError, T>

fun validateToken(token: String): Outcome<String> =
    if (token.isNotBlank()) Either.right(token)
    else Either.left(AuthError.InvalidToken)

fun fetchProfile(userId: String): Outcome<Profile> =
    if (userId == "42") Either.right(Profile("Alice"))
    else Either.left(AuthError.UserNotFound(userId))

// flatMapによる合成(fpdartのandThen)
val result = validateToken("abc123")
    .flatMap { fetchProfile("42") }
    .map { it.name }
    .getOrElse { "Guest" }

println(result) // "Alice"

チェーン:validateToken -> fetchProfile -> map name -> getOrElse “ゲスト”。validateTokenがLeft(InvalidToken)を返すと、チェーンは中断され“ゲスト”を返します。fetchProfileがLeft(UserNotFound)を返す場合も同じく“ゲスト”です。両方の操作が成功すればプロフィール名が返されます。flatMapは、それぞれが失敗する可能性のあるEither関数を1つの線形チェーンに結合できます。従来の例外スタイルでは、同じコードに2つのネストしたtry-catchまたはnullチェックが必要です。最後のgetOrElseは合成の終了点で、Failureケースのデフォルト値を提供します。

Result vs 例外: 使い分け

Resultと例外は相互に排他的なアプローチではありません。それぞれに適用領域があり、適切に設計されたモバイルアプリケーションでは両方が使用されます。選択はエラーが想定内か想定外かによって異なります。Resultはビジネスロジックの一部である想定されたエラー用です:無効なメール、残高不足、レート制限超過など。例外は想定外のシステムエラー用です:ネットワーク喪失、OutOfMemoryError、NullPointerException(発生すべきではないが発生するもの)。

基準Result Type例外(Exception/Error)
エラーの種類想定内(ビジネスロジック)想定外(システム)
パフォーマンス低コスト(スタック巻き戻しなし)高コスト(スタック巻き戻し、StackTrace取得)
合成map/flatMapによる線形チェーンネストしたtry-catch — 読みにくい
コンパイラ網羅的チェック(switch/when)Javaのchecked exceptionsのみ
実行フロー中断されない — エラーは値として最も近いcatchまで中断される
テスト容易:結果を確認、isSuccess/isErrorをアサートassertThrowsとモックオブジェクトが必要
使用すべき場合ビジネス検証、リクエストチェーン、フォームネットワーク喪失、I/Oエラー、システムクラッシュ

実用的なルール:エラーがアプリケーションの通常のワークフローの一部(ユーザーが無効なメールを入力、権限不足)の場合はResultを使用します。エラーが例外的な状況(サーバー応答なし、メモリ不足)の場合は例外を使用します。モバイル開発では、レイヤー境界でのResult(UseCase -> ViewModel)とレイヤー内部での例外(API -> Repository)は、両方のアプローチの利点を組み合わせた一般的なパターンです。

例外からResultへの移行戦略

既存の例外ベースのコードからResultへの移行は段階的に行う必要があります。レイヤー境界から始めます。throws関数の呼び出しをResult { try ... }(Swift)やrunCatching { ... }(Kotlin)でラップします。次にRepositoryやUseCaseメソッドの戻り値の型をResult/Eitherに置き換え、内部実装は例外のままにします。最後のステップでViewModelを移行します。例外のあるUiStateの代わりに、sealed class UiState<T>(Loading、Success、Error)を使用し、ErrorはThrowableではなくドメインエラーを格納します。段階的な移行により、グローバルなリファクタリングなしで各レイヤーを個別にテストできます。

よくある質問

Result TypeとOptional/Optionの違いは?

Optional(T?)は値の有無を表します — nilは“データなし”を意味しますが、理由は説明しません。Result(Success/Failure)は成功だけでなく、具体的な型を持つエラー理由も含みます。Optionalは不在が正常な場合(例:オプションのプロフィールフィールド)に使用し、Resultはエラー情報が必要な場合に使用します。

throws関数をResultに変換するには?

SwiftではResult { try throwingFunc() }を使用します — Resultコンストラクタはthrowsクロージャを受け取ります。KotlinではrunCatching { throwingFunc() }を使用し、Result<T>を返します。DartではResult<T>.tryCatch(() => throwingFunc())を使用します。これにより、例外ベースのコードをResultチェーンに簡単に統合できます。

情報を失わずにResultのエラーを処理するには?

成功値を変更せずにエラー型を変換するにはmapError(Swift)またはmapLeft(Dart/KotlinのEither)を使用します。両方のケースを処理して単一の値を返す必要がある場合はfoldを使用します。チェーンを中断せずにログを記録するには、onFailure(Kotlin)またはプリフィックス検査ポイントを使用します。

KotlinでResultをコルーチンと一緒に使用できますか?

はい、ただし注意が必要です。KotlinのResult<T>は、K2コンパイラとリフレクションの特性により、suspend関数の戻り値の型として直接推奨されません。コルーチンで状態を表現するには、独自のsealed class NetworkResult<T>(Success、Error、Loading)を使用してください。ビジネスロジックの想定エラーには、ArrowのEitherがより強力な代替手段です。

Resultのコンテキストにおけるfoldとは?

foldは2つのコールバック(onSuccessとonFailure)を受け取り、任意の型の単一の値を返すメソッドです。switch式と同等ですが、高階関数として提供されます。foldはResultチェーンからの主要な終了点であり、Success/FailureをUiState、ユーザー向け文字列、または別のResultに変換します。

まとめ

  • Result Type — スタック巻き戻しなしで想定エラーを安全に処理するためのジェネリックコンテナ(Success/Failure)
  • Swiftの場合 Result<Success, Failure>(Failure: Error)は、型安全なエラーでswitchによる網羅的チェックを提供
  • Kotlinの場合 Result<T>は成功またはThrowableをラップし、runCatchingはthrowsコードからの便利なコンストラクタ
  • Dartの場合 ビルトインResult<T>(Dart 3.0)とfpdartのEither<L, R>でmap/flatMapによる高度な合成
  • 合成 map(成功の変換)とflatMap(Result関数の連鎖)でネストしたtry-catchを置き換え
  • Result vs 例外: Resultは想定されたビジネスエラー用、例外は想定外のシステム障害用
  • 実行フローを中断せずに明示的でテスト可能なエラー処理を行うにはレイヤー境界でResultを使用

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください