Back to flin
flin

Gardes de type et rétrécissement de type à l'exécution

Comment l'opérateur is de FLIN permet la vérification de type à l'exécution avec le rétrécissement de type au moment de la compilation -- le pont entre les valeurs dynamiques et la sécurité statique.

Juste A. Gnimavo (Thales) & Claude | March 26, 2026 7 min flin
EN/ FR/ ES
flintype-guardsnarrowingruntime

Il y a une tension fondamentale dans tout langage statiquement typé qui interagit avec le monde réel. Le système de types connaît des choses au moment de la compilation. Le monde réel révèle des choses à l'exécution. Une réponse JSON pourrait contenir un nombre ou une chaîne. Une fonction pourrait retourner des types différents selon son entrée. Une variable typée par union pourrait être n'importe lequel de ses membres.

Les gardes de type sont le pont. Ce sont des vérifications à l'exécution que le compilateur comprend, lui permettant de rétrécir un type d'une possibilité large à une certitude spécifique. Dans FLIN, l'opérateur is est ce pont.

L'opérateur is

L'opérateur is vérifie si une valeur a un type spécifique à l'exécution :

flinvalue: int | text | bool = getData()

if value is int {
    // Ici, value est int -- pas int | text | bool
    result = value + 1
}

if value is text {
    // Ici, value est text
    upper = value.upper
}

if value is bool {
    // Ici, value est bool
    negated = !value
}

L'opérateur is fait deux choses simultanément :

  1. Exécution : vérifie le type réel de la valeur et produit un résultat booléen
  2. Compilation : rétrécit le type dans la branche vraie de la condition

Ce double rôle est ce qui rend les gardes de type puissants. Le développeur écrit une seule vérification, et à la fois l'exécution et le compilateur en bénéficient.

Comment fonctionne le rétrécissement

Le vérificateur de types maintient un environnement de types -- une correspondance des noms de variables aux types. Quand il entre dans un bloc if dont la condition est une garde de type, il crée une nouvelle portée avec le type rétréci :

rustfn check_if_with_type_guard(
    &mut self,
    variable: &str,
    checked_type: &FlinType,
    original_type: &FlinType,
    then_block: &Block,
    else_block: &Option<Block>,
) {
    // Branche then : rétrécir au type vérifié
    self.push_scope();
    self.env.insert(variable.to_string(), checked_type.clone());
    self.check_block(then_block);
    self.pop_scope();

    // Branche else : exclure le type vérifié de l'union
    if let Some(else_block) = else_block {
        self.push_scope();
        let remaining = self.subtract_type(original_type, checked_type);
        self.env.insert(variable.to_string(), remaining);
        self.check_block(else_block);
        self.pop_scope();
    }
}

Soustraction de type

La branche else effectue la soustraction de type : retirer le type vérifié de l'union. Si une variable est int | text | bool et que vous vérifiez value is int, la branche else sait que la valeur est text | bool :

rustfn subtract_type(&self, base: &FlinType, removed: &FlinType) -> FlinType {
    match base {
        FlinType::Union(members) => {
            let remaining: Vec<FlinType> = members
                .iter()
                .filter(|m| m != removed)
                .cloned()
                .collect();

            match remaining.len() {
                0 => FlinType::Never,
                1 => remaining.into_iter().next().unwrap(),
                _ => FlinType::Union(remaining),
            }
        }
        _ => {
            if base == removed {
                FlinType::Never
            } else {
                base.clone()
            }
        }
    }
}

Si tous les types sont retirés, le résultat est Never -- un type sans valeurs, indiquant du code inatteignable. Si un type reste, l'union se réduit à ce type unique.

Gardes de type chaînées

Les gardes de type se chaînent à travers les blocs if-else if-else, rétrécissant progressivement le type :

flinvalue: int | text | bool | number = getData()

if value is int {
    // value: int
    print("Integer: " + text(value))
} else if value is text {
    // value: text (était text | bool | number, rétréci par is text)
    print("Text: " + value)
} else if value is bool {
    // value: bool (était bool | number, rétréci par is bool)
    print("Boolean: " + text(value))
} else {
    // value: number (la seule possibilité restante)
    print("Number: " + text(value))
}

Chaque else if voit un type progressivement plus étroit. La branche else finale a le type qui reste après toutes les vérifications. Le compilateur suit cela automatiquement.

Gardes de type sur les entités

L'opérateur is fonctionne avec les types d'entités :

flinentity Dog { name: text, breed: text }
entity Cat { name: text, indoor: bool }

animal: Dog | Cat = getAnimal()

if animal is Dog {
    print(animal.breed)    // sûr -- Dog a breed
}

if animal is Cat {
    print(animal.indoor)   // sûr -- Cat a indoor
}

Rétrécissement optionnel

Vérifier si une valeur optionnelle est présente la rétrécit de T? à T :

flinuser: User? = User.find(id)

if user is User {
    print(user.name)
}

// Ou la forme courte via la vérification de véracité :
if user {
    print(user.name)
}

Les deux formes produisent le même rétrécissement.

Combiner les gardes avec les opérateurs logiques

flinvalue: int | text = getData()

if value is int && value > 0 {
    // value: int (rétréci par is int)
    // La vérification > 0 est valide parce que value est connu comme int
    print("Positive integer")
}

L'opérateur && applique le rétrécissement de gauche à droite. L'opérande gauche value is int rétrécit le type, et l'opérande droit value > 0 est vérifié avec le type rétréci.

L'opérateur || ne rétrécit pas parce que l'un ou l'autre côté pourrait être vrai :

flinif value is int || value is text {
    // value est toujours int | text -- pas de rétrécissement
}

Patterns pratiques

Accès sûr aux entités

flinfn get_display_name(entity: User | Organization) -> text {
    if entity is User {
        return entity.first_name + " " + entity.last_name
    }
    // entity est Organization ici
    return entity.company_name
}

Gestion de réponses API

flinresponse: Success | NotFound | ServerError = fetch("/api/data")

if response is Success {
    render(response.data)
} else if response is NotFound {
    show_404()
} else {
    // response: ServerError
    log(response.message)
    show_error_page()
}

Traitement de collections

flinitems: [int | text] = [1, "hello", 2, "world", 3]

numbers = items.where(x => x is int)    // [int]
strings = items.where(x => x is text)   // [text]

La méthode where avec une garde de type produit un type de liste rétréci. Le compilateur infère que filtrer par is int produit [int], pas [int | text].

Pourquoi le rétrécissement à l'exécution compte

Les gardes de type résolvent un vrai problème. Les systèmes de types statiques sont conservateurs -- ils suivent ce qu'une valeur pourrait être, pas ce qu'elle est. Sans gardes de type, le développeur ne peut pas effectuer en toute sécurité des opérations spécifiques à un type.

L'alternative est le casting explicite, qui est à la fois verbeux et dangereux :

flin// Mauvais : cast explicite (pas de sécurité au moment de la compilation)
value = getData() as int    // plante si value est en fait text

// Bon : garde de type (sécurité compilation et exécution)
if value is int {
    // le compilateur vérifie que toutes les opérations sont valides pour int
}

Les gardes de type fournissent la sécurité des vérifications explicites avec l'ergonomie du rétrécissement implicite. Le développeur écrit une seule vérification is, et le compilateur propage l'information de type à travers le bloc entier.

C'est la promesse fondamentale du système de types de FLIN : une sécurité que vous obtenez toujours. Pas une sécurité pour laquelle vous devez vous battre. Pas une sécurité qui nécessite des annotations verbeuses. Une sécurité qui coule naturellement de la façon dont vous écrivez le code.


Ceci est la partie 40 de la série « Comment nous avons construit FLIN », documentant comment un CEO à Abidjan et un CTO IA ont conçu et implémenté un langage de programmation à partir de zéro.

Navigation de la série : - [38] L'opérateur pipeline : composition fonctionnelle dans FLIN - [39] Tuples, enums et structs - [40] Gardes de type et rétrécissement de type à l'exécution (vous êtes ici) - [41] Le type Never et la vérification d'exhaustivité - [42] Contraintes génériques et clauses where

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles

Thales & Claude deblo

Le Step Zero ne suffisait pas : comment valider un constructeur sans valider le runtime a fait tomber toutes les sessions vocales de Déblo l’heure où nous avons livré le streaming caméra temps réel

La phase 14 a livré Déblo Eyes — streaming caméra temps réel via LiveKit vers Gemini Live native audio. Le premier deploy a fait tomber toutes les sessions vocales en production en quatre-vingt-dix secondes parce que notre Step 0 avait validé le constructeur sans exercer le runtime. Le build log de comment Déblo a eu des yeux, ce qu’un pré-vol incomplet a coûté, et quels points de polish ont été livrés ou reportés.

33 min May 20, 2026
debloclaude-opus-4.7claude-codegemini-live +25
Thales & Claude deblo

Le tiret cadratin qui a tué la production : comment un slogan marketing dans un header HTTP a fait tomber le chat de Déblo pendant 24 heures

Deux jours avant la soumission App Store, tout le produit chat de Déblo s’est cassé silencieusement. Pas de spinner, pas de toast, aucune erreur dans l’UI — juste un silence radio. L’incident de 24 heures se résumait à un seul « é » dans la valeur d’un header HTTP qui levait une UnicodeEncodeError avant qu’aucune requête vers OpenRouter ne quitte le backend. Post-mortem d’une fausse hypothèse, d’une trace Sentry, et d’un fix de six lignes qui a débloqué le lancement.

30 min May 19, 2026
debloclaude-opus-4.7claude-codeincident +19
Thales & Claude deblo

Six heures, d’une page blanche à la review Apple — Comment nous avons soumis Déblo à l’App Store, en direct

Marche à marche en direct de la soumission de Déblo à l’App Store iOS en six heures : ce que les validateurs d’Apple ont rejeté (un superscript Unicode), ce que nous avons corrigé (un Promotional Text gaspillé sur des marques tierces), et les rouages de l’ASO iOS que presque tout le monde rate.

30 min May 13, 2026
debloclaude-opus-4.7claude-codeapp-store +16