Files
saselang-bible/chapters/018-result-erreurs-et-exceptions.md
2026-09-12 08:56:27 +02:00

356 lines
11 KiB
Markdown

# 18. `Result`, erreurs et exceptions
## 18.1 Hiérarchie Core — V1 REQUIS — FIGÉ
La hiérarchie d'erreurs V1 est :
```text
Object
└── Error
├── ResultError
└── Exception
```
Les trois classes racines sont abstraites et ne sont donc jamais instanciées directement.
`Error` est la racine abstraite commune des erreurs Saselang. Elle ne détermine pas à elle seule le mécanisme de propagation.
`ResultError` représente exclusivement la famille des erreurs transportables par `Result<T,E>`.
`Exception` représente exclusivement la famille des erreurs propagées par `throw` / `throws` / `try` / `catch` / `finally`.
`ResultError` et `Exception` sont des branches sœurs. Une classe utilisateur destinée à un `Result` hérite directement ou indirectement de `ResultError`. Une exception utilisateur hérite directement ou indirectement de `Exception`.
Exemple :
```text
public final class ParseError extends ResultError {
}
public final class FileNotFoundException extends Exception {
}
```
L'interface `Throwable` ne fait pas partie de Saselang V1. Elle pourra être réintroduite ultérieurement si un besoin réel apparaît sans modifier la règle fondamentale : `Result` transporte des `ResultError`, tandis que `throw` transporte des `Exception`.
## 18.2 Informations communes de `Error` — V1 REQUIS — FIGÉ EN PRINCIPE
`Error` reste volontairement généraliste et minimal. Il porte conceptuellement :
```text
message: String
cause: Option<Error>
i18nMessage: Option<I18nMessage>
```
Rôles :
```text
message
message humain canonique / fallback
cause
erreur causale purement informative
peut contenir un ResultError ou une Exception
n'est pas automatiquement propagée
i18nMessage
clé et paramètres de localisation
ne contient pas un tableau de traductions
```
`cause` est de type `Option<Error>` et non `Nullable<Error>` : l'absence de cause est une absence sémantique. Une cause de type statique `Error` ne peut pas être passée directement à `throw`; il faut d'abord prouver par `is` qu'elle est une `Exception`, ou l'encapsuler explicitement dans une nouvelle exception.
`message`, `cause` et `i18nMessage` sont initialisés lors de la construction et ne sont pas publiquement mutables.
`Error` ne porte pas de propriétés universelles supplémentaires telles que timestamp, thread id, process id, host, severity ou code plateforme. Ces informations appartiennent aux sous-classes, SDKs ou couches de logging/diagnostic lorsqu'elles sont pertinentes.
Le type exact `I18nMessage`, sa clé et la représentation de ses paramètres seront finalisés avec Core/SDK et le système de formatting/i18n.
## 18.3 `ResultError` et code machine-readable — V1 REQUIS — FIGÉ EN PRINCIPE
`ResultError` ajoute conceptuellement un code stable, non localisé et exploitable par le programme :
```text
code: ResultErrorCode
```
Le rôle de `ResultErrorCode` est distinct de `message` :
```text
code -> stable, machine-readable, non localisé
message -> humain, canonique / fallback
i18nMessage -> localisation externe
```
La représentation exacte de `ResultErrorCode` sera finalisée avec Core/SDK. `Exception` n'a pas de code obligatoire.
## 18.4 `Exception` et stack trace — V1 REQUIS — FIGÉ EN PRINCIPE
`Exception` reste généraliste. Elle peut recevoir des informations de stack trace liées à sa propagation.
La création d'un objet `Exception` ne doit pas obligatoirement capturer immédiatement une stack trace. Le contexte de propagation peut être attaché ou capturé lors de :
```text
throw exception;
```
La représentation exacte (`StackTrace`, `StackFrame`, capture lazy ou autre optimisation) relève de Core/runtime. Cette information reste diagnostique et n'intervient pas dans la sélection des `catch`.
`Error` et `ResultError` n'ont pas de stack trace automatique obligatoire.
## 18.5 `Result<T,E>` — V1 REQUIS — FIGÉ
`Result<T,E>` est un type Core algébrique avec exactement deux variantes conceptuelles :
```text
Result::Ok(T)
Result::Err(E)
```
Le paramètre d'erreur est contraint :
```text
E doit être ResultError ou un descendant de ResultError
```
Sont valides :
```text
Result<Data,ResultError>
Result<Data,ParseError>
Result<Void,ResultError>
```
Sont invalides :
```text
Result<Data,Error>
Result<Data,Exception>
Result<Data,FileNotFoundException>
```
La forme courte :
```text
Result<T>
```
est équivalente à :
```text
Result<T,ResultError>
```
`Result<Void,E>` et `Result<Void>` sont autorisés pour représenter un succès sans payload utile. `Option<Void>` reste interdit.
Il n'existe aucune conversion implicite de `T` vers `Result<T,E>` ni de `E` vers `Result<T,E>` :
```text
return Result::Ok(value);
return Result::Err(error);
```
sont explicites.
`Result` suit les règles générales des enums algébriques. Son inspection et l'extraction sûre de ses payloads utilisent `match` avec bindings typés. V1 n'introduit ni `unwrap`, ni opérateur `?`, ni extraction implicite d'une variante.
Des helpers Core futurs tels que :
```text
result::expectOk(...)
result::expectErr(...)
```
peuvent être étudiés si un besoin réel apparaît. Ils resteraient une API de `Result`, pas une syntaxe du langage, et leur comportement d'échec devrait être explicitement spécifié.
## 18.6 Séparation `ResultError` / `Exception` — V1 REQUIS — FIGÉ
Aucune conversion automatique n'existe entre les deux branches :
```text
ResultError -> Exception
Exception -> ResultError
```
La traduction d'un mécanisme vers l'autre se fait par encapsulation explicite dans une nouvelle erreur de la branche cible, éventuellement en conservant l'erreur source dans `cause`.
Exemples conceptuels :
```text
throw ParseException(..., Option::Some(parseError));
```
ou :
```text
catch (FileNotFoundException error) {
return Result::Err(FileResultError(..., Option::Some(error)));
}
```
Un simple cast ne transforme pas un `ResultError` en `Exception` ni l'inverse.
## 18.7 `throws` — V1 REQUIS — FIGÉ
`throws` est interdit sur une `func` / `method` / `clsmethod` à retour direct. Il n'est permis que si le retour est `Result<T>` ou `Result<T,E>`.
```text
T + throws -> interdit
Result<T,E> -> valide sans throws
Result<T,E> + throws -> valide
```
Tout type déclaré dans `throws` doit être `Exception` ou un descendant de `Exception`.
Une callable doit déclarer toute famille d'exception susceptible d'atteindre sa frontière sans être gérée localement. Cette obligation est transitive : une exception déclarée par une callable appelée doit être soit capturée localement, soit couverte par le `throws` de l'appelant.
Un type déclaré dans `throws` couvre ses descendants. Une clause :
```text
throws IOException
```
peut donc couvrir `FileNotFoundException` si cette dernière hérite de `IOException`.
Une même clause `throws` ne doit pas contenir de types redondants lorsqu'un type déclaré couvre déjà entièrement un autre type de la liste.
## 18.8 `throws` dans les contrats, interfaces et overrides — V1 REQUIS — FIGÉ
`throws` ne fait pas partie de la signature d'overload. Il fait partie du contrat de la callable.
Un override ou une implémentation peut :
```text
supprimer entièrement des exceptions déclarées
restreindre une famille à une ou plusieurs sous-familles compatibles
gérer localement tout ou partie des exceptions du contrat parent
```
Il ne peut jamais ajouter une exception non couverte par le contrat parent ni élargir une famille déclarée.
La même règle s'applique aux contrats d'interface. Lorsque plusieurs interfaces portent la même signature, l'implémentation doit satisfaire simultanément tous leurs contrats `throws`. Une implémentation sans exception sortante est toujours compatible avec un contrat qui en autorise.
## 18.9 `throw` — V1 REQUIS — FIGÉ
`throw` ne peut lancer qu'une valeur dont le type statique est `Exception` ou un descendant de `Exception`.
```text
throw FileNotFoundException(...); // valide
throw ParseError(...); // erreur
throw Error(...); // erreur
```
Les racines abstraites `Error`, `ResultError` et `Exception` ne sont jamais instanciables directement.
La forme de repropagation reste explicite :
```text
catch (IOException error) {
throw error;
}
```
Il n'existe pas de forme spéciale `throw;` en V1.
## 18.10 `try` / `catch` / `finally` — V1 REQUIS — FIGÉ
Un `try` doit être suivi d'au moins un `catch`.
Sont interdits :
```text
try { ... }
try { ... } finally { ... }
finally { ... }
```
La forme générale est :
```text
try {
...
} catch (SpecificException error) {
...
} catch (ParentException error) {
...
} finally {
...
}
```
`finally` est facultatif et ne peut apparaître qu'après au moins un `catch`.
Chaque `catch` contient exactement un type explicite d'`Exception` ou descendant. Il n'existe pas de multi-catch `A | B` en V1 : des blocs `catch` distincts sont utilisés.
Les `catch` sont évalués dans l'ordre source. Le chevauchement par héritage est normal et utile : les cas spécifiques peuvent précéder un fallback de famille générale.
Un `catch` entièrement couvert par un `catch` précédent est une erreur de compilation, pas un simple warning.
Exemple valide :
```text
catch (FileNotFoundException error) {
...
} catch (IOException error) {
...
}
```
Exemple invalide si `FileNotFoundException extends IOException` :
```text
catch (IOException error) {
...
} catch (FileNotFoundException error) {
...
}
```
Un ensemble de `catch` n'a pas à être exhaustif. Toute `Exception` non capturée continue à se propager et doit être couverte par le contrat `throws` de la callable englobante.
Les variables de `catch` suivent les règles normales de scope et de non-shadowing. Des `catch` distincts peuvent réutiliser le même nom parce que leurs scopes sont disjoints.
## 18.11 `finally` non-escaping — V1 REQUIS — FIGÉ
`finally` exécute un bloc commun avant de quitter la construction `try/catch`, qu'il y ait :
```text
fin normale du try
fin normale d'un catch
return traversant la construction
throw propagé
break / continue traversant la construction
Exception non capturée par les catch
```
`finally` ne doit jamais remplacer une sortie déjà en cours. Les sorties structurées suivantes y sont interdites :
```text
return
throw
break
continue
emit
```
Aucune `Exception` non capturée ne peut sortir indirectement d'un `finally`. Toute callable appelée depuis `finally` dont le contrat comporte `throws` doit avoir ses exceptions entièrement gérées à l'intérieur du `finally`.
Une faute runtime non récupérable reste distincte de ce contrat d'exception.
## 18.12 Faute runtime / panic — V1 REQUIS — À FINALISER
Les violations de contrat du langage telles que :
```text
index hors limites
division entière par zéro
overflow checked
représentation mémoire invalide
borne dynamique invalide lors d'une construction directe lorsque la règle du type le définit
```
ne sont pas nécessairement des `ResultError` ou des `Exception` récupérables.
Le modèle exact de faute runtime/fatal error/panic et son interaction avec cleanup/destruction doit être défini avant la finalisation V1.