Files
saselang-bible/chapters/018-result-erreurs-et-exceptions.md
2026-09-13 10:20:16 +02:00

451 lines
13 KiB
Markdown

# 18. `Result`, erreurs, exceptions et faults
## 18.1 Hiérarchie Core — V1 REQUIS — FIGÉ
La hiérarchie V1 est :
```text
Object
└── Error
├── ResultError
├── Exception
└── Fault
```
Les quatre classes racines sont abstraites et ne sont jamais instanciées directement.
`Error` est la racine commune. Elle ne choisit pas à elle seule le mécanisme de propagation.
```text
ResultError
échec transporté explicitement comme une valeur dans Result<T,E>
Exception
échec checked propagé par throw / throws
Fault
échec unchecked, capturable, pouvant être documenté par faults
```
Les trois branches sont sœurs. Aucune n'est implicitement convertible vers une autre.
Exemple :
```text
public final class ParseError extends ResultError {
}
public final class FileNotFoundException extends Exception {
}
public final class IndexOutOfBoundsFault extends Fault {
}
```
Il n'existe pas d'interface `Throwable` en V1.
## 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, une Exception ou un Fault
n'est pas automatiquement propagée
i18nMessage
clé et paramètres de localisation
ne contient pas un tableau de traductions
```
`cause` n'accorde aucun droit de propagation. Une valeur statiquement typée `Error` ne peut être utilisée ni avec `throw` ni avec `fault` sans preuve préalable de sa branche exacte.
`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, au SDK ou aux couches de logging/diagnostic lorsqu'elles sont pertinentes.
## 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.
Ni `Exception` ni `Fault` n'ont de code universel obligatoire ; leurs sous-types peuvent naturellement en définir lorsqu'il est utile.
## 18.4 Diagnostic de `Exception` et `Fault` — V1 REQUIS — FIGÉ EN PRINCIPE
`Exception` et `Fault` peuvent recevoir des informations de stack trace liées à leur propagation runtime.
La création de l'objet ne doit pas obligatoirement capturer immédiatement une stack trace. Le contexte peut être attaché ou capturé lors de :
```text
throw exception;
fault someFault;
```
ou lorsqu'un `Fault` est produit intrinsèquement par le runtime/Core.
La représentation exacte (`StackTrace`, `StackFrame`, capture lazy ou autre optimisation) relève du Core/runtime. Cette information reste diagnostique et n'intervient pas dans la sélection des `catch`.
`ResultError` n'a 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,Fault>
Result<Data,FileNotFoundException>
```
La forme courte :
```text
Result<T>
```
est équivalente à :
```text
Result<T,ResultError>
```
`Result<Void,E>` et `Result<Void>` sont autorisés. `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);
```
restent explicites.
`Result` suit les règles générales des enums algébriques. V1 n'introduit ni `unwrap`, ni opérateur `?`, ni extraction implicite d'une variante.
## 18.6 Choix du mécanisme d'échec — V1 REQUIS — FIGÉ EN PRINCIPE
Les trois branches ne sont pas trois orthographes du même concept.
```text
ResultError / Result
l'échec est une donnée normale du résultat et l'appelant doit l'inspecter explicitement
Exception
l'échec appartient au contrat checked de la callable et doit être capturé ou propagé par throws
Fault
l'échec est unchecked ; l'appelant peut le capturer mais n'y est pas obligé
```
Une API directe n'est donc plus obligée de retourner `Result` uniquement parce qu'elle peut échouer.
Exemple :
```text
method elementAt(uint64 index) -> T
faults IndexOutOfBoundsFault
```
peut retourner directement `T`.
Inversement, lorsqu'une absence ou un échec constitue une donnée normale du domaine, `Option` ou `Result` reste préférable :
```text
map::get(key) -> Option<V>
parseExternalInput(...) -> Result<Value, ParseError>
```
Le Core ne doit pas créer mécaniquement un couple `op()` / `tryOp()` uniquement pour offrir d'un côté une valeur directe et de l'autre un `Result`. Deux opérations coexistantes doivent avoir des sémantiques réellement distinctes.
Aucune conversion automatique n'existe entre `ResultError`, `Exception` et `Fault`. Une traduction entre branches se fait par encapsulation explicite, éventuellement via `cause`.
## 18.7 `throws` — V1 REQUIS — FIGÉ
`throws` est indépendant du type de retour.
Sont valides :
```text
T
T throws SomeException
Result<T,E>
Result<T,E> throws SomeException
Void throws SomeException
```
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 même clause ne doit pas contenir de types redondants lorsqu'un type déclaré couvre déjà entièrement un autre type de la liste.
`Fault`, `Error` et `ResultError` sont interdits dans `throws`.
## 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 checked 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. 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(...); // OK
throw ParseError(...); // ERROR
throw IndexOutOfBoundsFault(...); // ERROR
throw Error(...); // ERROR
```
La repropagation reste explicite :
```text
catch (IOException error) {
throw error;
}
```
Il n'existe pas de forme spéciale `throw;` en V1.
## 18.10 `fault` et `faults` — V1 REQUIS — FIGÉ EN PRINCIPE
`fault` produit explicitement un `Fault` :
```text
fault InvalidStateFault(...);
```
Le type statique de la valeur doit être `Fault` ou un descendant de `Fault`.
```text
fault InvalidStateFault(...); // OK
fault FileNotFoundException(...); // ERROR
fault ParseError(...); // ERROR
```
Une callable peut documenter des faults significatifs directement dans sa signature :
```text
method elementAt(uint64 index) -> T
faults IndexOutOfBoundsFault
```
Plusieurs familles peuvent être listées explicitement :
```text
faults FirstFault, SecondFault
```
Chaque type listé doit être `Fault` ou un descendant de `Fault`; une liste ne doit pas contenir de famille redondante déjà couverte par un parent déclaré.
La clause `faults` est **optionnelle et non exhaustive**.
Elle signifie que les faults listés font explicitement partie du contrat/documentation utile de l'API. Elle ne signifie pas qu'aucun autre fault runtime ne peut survenir.
Contrairement à `throws` :
```text
aucun catch n'est obligatoire
aucune propagation de la clause faults n'est obligatoire
un appelant n'a pas à recopier les faults d'une callable appelée
```
Exemple valide :
```text
func outer() -> Void {
inner(); // inner peut déclarer faults SomeFault
return Void;
}
```
`outer` peut, mais n'est pas obligé de déclarer à son tour :
```text
faults SomeFault
```
`faults` ne fait pas partie de la signature d'overload et n'impose pas les restrictions de covariance contractuelle de `throws`. Un override peut documenter un ensemble différent de faults, puisque l'absence d'un fault dans la clause n'est jamais une garantie statique d'impossibilité.
Une API publique qui produit explicitement un fault important devrait normalement le documenter avec `faults`; un outil/linter peut signaler les omissions sans en faire une erreur de compilation du langage.
## 18.11 `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 (SomeFault faultValue) {
...
} finally {
...
}
```
`finally` est facultatif et ne peut apparaître qu'après au moins un `catch`.
Le type explicite d'un `catch` doit être :
```text
Exception ou un descendant de Exception
Fault ou un descendant de Fault
```
Sont donc interdits :
```text
catch (Error error)
catch (ResultError error)
```
Il n'existe pas de multi-catch `A | B` en V1.
Les `catch` sont évalués dans l'ordre source. Un `catch` entièrement couvert par un précédent dans la même branche d'héritage est une erreur de compilation.
Une `Exception` non capturée continue à se propager et doit être couverte par `throws`.
Un `Fault` non capturé continue à se propager de façon unchecked ; aucune clause `faults` n'est exigée sur les callables traversées.
Les variables de `catch` suivent les règles normales de scope et de non-shadowing.
## 18.12 `finally` non-escaping — V1 REQUIS — FIGÉ EN PRINCIPE
`finally` exécute un bloc commun avant de quitter la construction `try/catch`, notamment sur :
```text
fin normale du try
fin normale d'un catch
return traversant la construction
throw propagé
Fault propagé
break / continue traversant la construction
```
`finally` ne doit pas remplacer volontairement une sortie déjà en cours. Les sorties structurées explicites suivantes y sont interdites :
```text
return
throw
fault
break
continue
emit
```
Toute `Exception` produite indirectement depuis `finally` doit être gérée localement selon les règles checked de `throws`.
Un `Fault` dynamique peut néanmoins survenir dans le code exécuté par `finally`, puisqu'il est unchecked. L'interaction exacte entre un tel fault, l'unwind, les destructeurs et une sortie déjà en cours reste à fermer avec le modèle mémoire/runtime.
## 18.13 Sémantique runtime des `Fault` — V1 REQUIS — FIGÉ EN PRINCIPE
Les `Fault` couvrent notamment les échecs unchecked et violations runtime tels que :
```text
index dynamique hors limites
division entière dynamique par zéro
overflow checked
iterator invalidé
clé absente lors d'un accès strict map[key]
doublon lors d'un insert strict
conversion explicite directe dont les préconditions runtime ne sont pas satisfaites
représentation/encodage invalide lors d'une construction directe validée
```
Lorsqu'une violation d'une précondition intrinsèque est démontrable statiquement, le compilateur doit la diagnostiquer plutôt que générer un programme condamné à fauter.
Exemple :
```text
StaticArray<int32,3> values = [1, 2, 3];
int32 value = values[5]; // ERROR compilation
```
Cette règle ne rend évidemment pas illégal un statement `fault` explicite : `fault SomeFault(...)` est un mécanisme volontaire du programme et peut être utilisé pour implémenter une API.
Lorsqu'un `Fault` ne peut être déterminé qu'au runtime :
```text
capturé par catch
-> le catch correspondant s'exécute
non capturé
-> le fault remonte sans contrat checked
-> à la frontière de l'unité d'exécution, il provoque un échec runtime diagnostiqué
```
La définition exacte de « l'unité d'exécution » (processus, thread, task, isolate éventuel) et l'ordre final cleanup/destruction lors d'un fault seront fermés avec les chapitres mémoire et concurrence.
---