Result Type : ce que c’est, le type conteneur Result et son fonctionnement dans le développement mobile

Auteur : IT Sectr Publié le : 2026-05-26 Temps de lecture : 9 min

Result Type — un type conteneur qui représente le résultat d’une opération pouvant aboutir avec succès (Success) ou échec (Failure). Contrairement aux exceptions, Result transmet l’erreur comme une valeur normale sans déroulement de pile, ce qui rend la gestion des erreurs attendues plus sûre et composable. Selon Apple Swift Documentation (2026), Result<Success, Failure> en Swift permet d’enchaîner des opérations avec une gestion automatique des erreurs via map et flatMap sans interrompre l’exécution du programme.

Points clés

  • Result Type — un conteneur générique avec deux états : Success (données) et Failure (erreur), sans déroulement de pile
  • En Swift Result<Success, Failure> — un type intégré avec les méthodes map, flatMap, mapError et get
  • En Kotlin Result<T> représente un succès ou Throwable, avec les fonctions getOrNull, getOrDefault, fold
  • En Dart Result<T> standard disponible depuis Dart 3.0, ainsi que Either du package fpdart
  • Composition via map et flatMap permet de combiner plusieurs opérations retournant Result sans vérifications imbriquées

Qu’est-ce que Result Type ?

Result Type est un type générique qui encapsule le résultat d’une opération pouvant réussir ou échouer. Contrairement aux exceptions, où une erreur interrompt le flux normal et nécessite un déroulement de pile pour trouver un bloc catch, Result transmet l’erreur comme une valeur normale — l’appelant reçoit toujours un objet et décide comment le traiter. C’est particulièrement utile pour les erreurs attendues : entrée invalide, règles métier, rejet du serveur, où les exceptions seraient un mécanisme trop lourd.

Le concept de Result provient de la programmation fonctionnelle, où des types similaires sont appelés Either (ou Left/Right). En Swift, Result est devenu un type standard dans Swift 5.0 ; en Kotlin, Result<T> est apparu dans la bibliothèque standard ; en Dart 3.0, Result<T> intégré a été introduit. Chaque implémentation fournit des méthodes pour travailler avec le conteneur : map (transformer la valeur de succès), flatMap (enchaîner des fonctions Result), mapError (transformer l’erreur), fold (traiter les deux cas). Result Type est une pierre angulaire de la gestion fonctionnelle des erreurs dans le développement mobile, une alternative à try-catch pour les scénarios attendus.

L’avantage clé de Result est la composabilité. Vous pouvez combiner plusieurs opérations, chacune pouvant échouer, en une seule chaîne sans blocs try-catch imbriqués. Si une opération dans la chaîne échoue avec Failure, toute la chaîne est interrompue et retourne Failure — sans une seule condition if ou bloc catch. Cela rend le code linéaire et lisible, en particulier dans les scénarios avec plusieurs requêtes API séquentielles ou vérifications de règles métier.

Result en Swift

En Swift, Result<Success, Failure> est une énumération avec deux cas : .success(Success) et .failure(Failure), où Failure est contraint par le protocole Error. Le Result de Swift est un type entièrement fonctionnel avec les méthodes map, flatMap, mapError et get. get est une méthode spéciale : elle retourne la valeur Success si le résultat est réussi et lance l’erreur si c’est Failure. Cela permet à Result d’agir comme un pont entre les styles fonctionnel et basé sur les exceptions : traiter via map/flatMap dans une chaîne, et à la fin utiliser get avec do-catch pour s’intégrer avec du code throws.

Énumération Result et switch exhaustif

Le Result de Swift est une énumération avec des paramètres génériques, ce qui permet au compilateur de vérifier le traitement exhaustif via switch ou do-catch. Si vous ajoutez un nouveau cas à l’énumération NetworkError, le compilateur générera une erreur dans toutes les expressions switch où ce cas n’est pas traité. La vérification exhaustive est le principal avantage de Result par rapport aux exceptions : le compilateur garantit que toutes les erreurs possibles sont prises en compte au moment de la compilation. En revanche, les exceptions ne sont pas vérifiées par le compilateur en Swift (seulement throws est déclaré, mais pas le type d’erreur).

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))
    }
}

// Utilisation avec 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> permet de typer l’erreur au niveau du type : la fonction fetchUser retourne Result<User, NetworkError>, où NetworkError est une énumération concrète avec trois cas. L’appelant traite chaque cas via switch avec une couverture exhaustive — le compilateur vérifie que toutes les variantes sont traitées. Le switch exhaustif est un avantage clé de Result par rapport aux exceptions : le compilateur garantit que vous n’oublierez pas de traiter .badURL, .requestFailed ou .decodingFailed. Avec les exceptions, le compilateur n’exige pas de traitement, et un catch(.badURL) manquant peut facilement passer inaperçu lors de la révision de code.

Result en Kotlin

En Kotlin, Result<T> est un type intégré de la bibliothèque standard qui représente un succès (T) ou une erreur (Throwable). Contrairement à Swift, le Result de Kotlin ne permet pas de spécifier un type d’erreur concret — seulement Throwable. Cela est fait pour la simplicité mais nécessite une vérification supplémentaire du type d’erreur via l’expression when. Le Result de Kotlin prend en charge fold (traiter les deux cas), getOrNull (succès ou null), getOrDefault (succès ou valeur par défaut), map, recover, andThen. Une particularité de Kotlin : Result n’est pas conçu pour la propagation directe à travers les limites de fonctions — il ne peut pas être utilisé comme type de retour pour les méthodes Android SDK ou l’API Kotlin Coroutines sans adaptation supplémentaire.

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 est un wrapper pratique en Kotlin qui attrape toute exception et retourne Result.failure. validateEmail retourne Result.success ou Result.failure selon la validation. fold traite les deux cas de manière compacte : en cas de Success, la fonction de validation suivante est appelée ; en cas de Failure, SignupResult.Error est retourné. Important : le Result de Kotlin n’est pas conçu pour le stockage dans des champs de data class ou la transmission directe à travers les limites de fonctions suspend — utilisez des classes sealed personnalisées (Success/Error/Loading) pour représenter les états UI dans Jetpack Compose ou MVVM.

Result en Dart et Flutter

Depuis Dart 3.0, la bibliothèque standard inclut un Result<T> intégré — une classe sealed avec deux constructeurs : T.ok() (succès) et Error.error() (erreur avec Object et StackTrace). Avant Dart 3.0, les développeurs Flutter utilisaient Either<L, R> du package dartz ou des classes sealed personnalisées. Le Result intégré dans Dart est minimaliste : il ne fournit pas map/flatMap directement — ces fonctions doivent être implémentées via when ou en utilisant des méthodes d’extension. Pour un traitement fonctionnel sérieux, Either de fpdart reste une solution plus puissante avec le support de map, flatMap, mapLeft, fold, andThen et des opérateurs bind.

Either de fpdart

Either<L, R> est un type biaisé à gauche du package fpdart, où Left est l’erreur et Right le succès. Contrairement au Result<T> intégré, Either type l’erreur au niveau du paramètre de type (L), permettant la différenciation du type d’erreur à la compilation. Le package fpdart fournit un ensemble complet de combinateurs fonctionnels : map (Right -> Right), mapLeft (Left -> Left), flatMap (bind — Either imbriqué), andThen (enchaînement sans transformation), fold (sortie de Either), getOrElse (valeur par défaut). Pour les applications Flutter avec une approche fonctionnelle, Either est le standard de facto.

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));
        }
    }
}

// Utilisation avec fold
final result = await service.fetchUser('42');
result.fold(
    (left)  => showError(left.message),
    (right) => showUser(right),
);

Either<L, R> de fpdart est un type biaisé à gauche : Left — erreur, Right — succès. fetchUser retourne Either<AppError, User>, où AppError est une classe sealed avec des types d’erreur concrets (serverError, networkError). fold traite les deux cas : le premier callback pour Left (erreur), le second pour Right (succès). Dans Dart 3.0, le Result intégré prend également en charge fold, mais ne fournit pas map/flatMap. Pour les chaînes, Either de fpdart fournit map, flatMap (bind), mapLeft, andThen — un ensemble complet de combinateurs fonctionnels pour la composition d’erreurs.

Composition de Result via map et flatMap

Le principal avantage de Result par rapport aux exceptions est la composition. Si vous avez plusieurs opérations, chacune pouvant échouer, vous pouvez les enchaîner en utilisant map et flatMap sans un seul if imbriqué ou try-catch. map transforme la valeur de succès : Result.success(x) -> Result.success(f(x)). flatMap (également appelé bind ou andThen) est pour les cas où la transformation elle-même retourne un Result : Result.success(x) -> f(x) -> Result<Y>. Si une étape échoue avec Failure, les opérations suivantes ne sont pas exécutées — la chaîne est interrompue.

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))

// Composition via flatMap (andThen dans fpdart)
val result = validateToken("abc123")
    .flatMap { fetchProfile("42") }
    .map { it.name }
    .getOrElse { "Guest" }

println(result) // "Alice"

Chaîne : validateToken -> fetchProfile -> map name -> getOrElse « Invité ». Si validateToken retourne Left (InvalidToken), la chaîne est interrompue et retourne « Invité ». Si fetchProfile retourne Left (UserNotFound) — également « Invité ». Si les deux opérations réussissent — le nom du profil. flatMap permet de combiner des fonctions Either, chacune pouvant échouer, en une seule chaîne linéaire. Dans le style traditionnel d’exceptions, le même code nécessiterait deux try-catch imbriqués ou des vérifications null. getOrElse à la fin est le point de sortie de la composition, fournissant une valeur par défaut pour le cas Failure.

Result vs Exceptions : quand choisir quoi

Result et exceptions ne sont pas des approches mutuellement exclusives. Chacune a son propre domaine, et dans une application mobile bien conçue, les deux sont utilisés. Le choix dépend du fait que l’erreur soit attendue ou inattendue. Result est pour les erreurs attendues qui font partie de la logique métier : e-mail invalide, fonds insuffisants, limite de taux dépassée. Exceptions sont pour les erreurs système inattendues : perte de réseau, OutOfMemoryError, NullPointerException (qui ne devraient pas se produire mais se produisent).

CritèreResult TypeExceptions (Exception/Error)
Type d’erreurAttendues (logique métier)Inattendues (système)
PerformanceCoût faible (sans déroulement de pile)Coût élevé (déroulement de pile, capture de StackTrace)
CompositionVia map/flatMap — chaînes linéairesTry-catch imbriqués — difficiles à lire
CompilateurVérification exhaustive (switch/when)Exceptions vérifiées seulement en Java
Flux d’exécutionNon interrompu — erreur comme valeurInterrompu jusqu’au catch le plus proche
TestFacile : vérifier le résultat, assert isSuccess/isErrorNécessite assertThrows et objets mock
Quand utiliserValidation métier, chaînes de requêtes, formulairesPerte de réseau, erreurs E/S, pannes système

Règle pratique : si l’erreur fait partie du flux de travail normal de l’application (utilisateur a saisi un e-mail invalide, permissions insuffisantes) — utilisez Result. Si l’erreur est une situation exceptionnelle (serveur ne répond pas, mémoire épuisée) — utilisez les exceptions. Dans le développement mobile, Result aux limites des couches (UseCase -> ViewModel) et les exceptions à l’intérieur des couches (API -> Repository) est un motif courant qui combine les avantages des deux approches.

Stratégie de migration des exceptions vers Result

La migration du code existant basé sur les exceptions vers Result doit être progressive. Commencez par les limites des couches : enveloppez les appels de fonctions throws dans Result { try ... } (Swift) ou runCatching { ... } (Kotlin). Remplacez ensuite le type de retour des méthodes Repository et UseCase par Result/Either, en laissant l’implémentation interne sur les exceptions. Dans la dernière étape, migrez le ViewModel : au lieu de UiState avec exceptions, utilisez une classe sealed UiState<T> (Loading, Success, Error), où Error stocke une erreur de domaine, pas un Throwable. La migration progressive permet de tester chaque couche séparément sans refactorisation globale.

Questions fréquentes

En quoi Result Type diffère-t-il de Optional/Option ?

Optional (T?) représente la présence ou l’absence d’une valeur — nil signifie « pas de données » mais n’explique pas pourquoi. Result (Success/Failure) contient non seulement le succès mais aussi la raison de l’erreur avec un type concret. Utilisez Optional quand l’absence est normale (par exemple, un champ de profil optionnel), et Result quand vous avez besoin d’informations sur l’erreur.

Comment convertir une fonction throws en Result ?

En Swift, utilisez Result { try throwingFunc() } — le constructeur Result prend une fermeture throws. En Kotlin, utilisez runCatching { throwingFunc() }, qui retourne Result<T>. En Dart, utilisez Result<T>.tryCatch(() => throwingFunc()). Cela permet une intégration facile du code basé sur les exceptions dans les chaînes Result.

Comment traiter une erreur dans Result sans perte d’informations ?

Utilisez mapError (Swift) ou mapLeft (Either dans Dart/Kotlin) pour transformer le type d’erreur sans changer la valeur de succès. Si vous devez traiter les deux cas et retourner une valeur unique, utilisez fold. Pour la journalisation sans interrompre la chaîne, utilisez onFailure (Kotlin) ou un point d’inspection de préfixe.

Peut-on utiliser Result en Kotlin avec les coroutines ?

Oui, mais avec précaution. Le Result<T> de Kotlin n’est pas recommandé comme type de retour pour les fonctions suspend directement en raison de particularités du compilateur K2 et de la réflexion. Utilisez votre propre classe sealed NetworkResult<T> (Success, Error, Loading) pour représenter les états dans les coroutines. Pour les erreurs attendues dans la logique métier, Either d’Arrow est une alternative plus puissante.

Qu’est-ce que fold dans le contexte de Result ?

fold est une méthode qui prend deux callbacks : onSuccess (pour le cas de succès) et onFailure (pour le cas d’erreur), et retourne une valeur unique de n’importe quel type. C’est l’équivalent d’une expression switch mais sous forme de fonction d’ordre supérieur. fold est le principal point de sortie des chaînes Result, où vous convertissez Success/Failure en UiState, une chaîne pour l’utilisateur ou un autre Result.

Résumé

  • Result Type — un conteneur générique (Success/Failure) pour la gestion sûre des erreurs attendues sans déroulement de pile
  • En Swift Result<Success, Failure> avec Failure : Error fournit une vérification exhaustive via switch avec des erreurs type-safe
  • En Kotlin Result<T> encapsule le succès ou Throwable, runCatching est un constructeur pratique à partir de code throws
  • En Dart Result<T> intégré (Dart 3.0) et Either<L, R> de fpdart pour la composition avancée avec map/flatMap
  • Composition via map (transformer le succès) et flatMap (enchaîner des fonctions Result) remplace les try-catch imbriqués
  • Result vs exceptions : Result pour les erreurs métier attendues, exceptions pour les pannes système inattendues
  • Utilisez Result aux limites des couches pour une gestion des erreurs explicite et testable sans interrompre le flux d’exécution

Nous développerons une application mobile clé en main

IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.

Discuter du projet

Lisez aussi