451 lines
13 KiB
Markdown
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.
|
|
|
|
---
|