Modules, imports et exports
Un programme grandit en plusieurs fichiers. Les modules donnent une adresse logique à chaque fichier, tandis que les imports et exports définissent comment les morceaux se parlent.
Un module a un chemin
Une déclaration space donne au fichier un chemin de module. Le dépôt montre souvent le chemin correspondant à l’arborescence, avec des barres obliques, et les imports utilisent des segments séparés par ::. Les deux graphies sont prévues par la grammaire pour les chemins.
space outils::calcul;
export proc doubler(valeur: i64) -> i64 {
return valeur * 2;
}
Conservez le chemin logique du module cohérent avec sa place dans le projet. Lorsqu’une erreur « module introuvable » apparaît, vérifiez le chemin déclaré, le chemin d’import et la racine de recherche configurée par l’outil.
Importer un nom
use rend une déclaration ou un module accessible au fichier courant. Le chemin complet garde la provenance visible. as donne un alias local plus court :
use outils::calcul as calcul;
proc resultat() -> i64 {
return calcul::doubler(21);
}
La grammaire prévoit également des imports nommés par groupe, par exemple use core::types::option::{Option, option_some};, ainsi que les imports globaux. Un import ciblé est généralement plus facile à lire : on sait d’où vient chaque nom et on limite les collisions.
Exporter une interface publique
Une déclaration peut être rendue visible depuis un autre module avec un modificateur de visibilité, comme export. On peut exporter une procédure, une constante, une forme ou un choix. Le module conserve ainsi ses détails internes tout en publiant les points d’entrée nécessaires.
space outils::prix;
const TAUX: i64 = 20;
export proc avec_taxe(prix: i64) -> i64 {
return prix + (prix * TAUX / 100);
}
Le nom TAUX reste privé dans cet exemple, tandis que avec_taxe est exporté. Une interface publique stable permet de réorganiser les détails internes sans demander aux appelants de changer. La grammaire accepte aussi des listes d’exports et l’export d’un module entier ; les règles de réexport ne sont pas toutes prises en charge dans chaque profil de compilation.
Conflits de noms et visibilité
Deux modules peuvent publier le même nom. En conservant les noms qualifiés, on distingue comptes::Point de dessin::Point. Les imports globaux rendent les collisions plus probables et cachent la provenance d’un nom. Utilisez des alias explicites si deux noms se ressemblent.
Il faut aussi distinguer un module importé de ses membres visibles. Importer un module ne doit pas automatiquement donner accès à ses déclarations privées. Si un appel qualifié échoue, vérifiezz que le nom est bien exporté et qu’il n’y a pas de faute dans un segment du chemin.
Un exemple à deux fichiers
Dans calcul.vit, un chemin de module est déclaré avec une procédure additionner exportée. Dans main.vit, le module est importé sous un alias puis la procédure est appelée. Les deux fichiers confirmés se trouvent dans examples/tutorials/modules/.
Le résolveur de modules cherche les sources selon ses chemins configurés et met en cache des résolutions. Pour un programme de production, documente la racine du projet et les conventions de modules au lieu de dépendre d’un chemin local implicite.
Depuis la racine du dépôt, la variable VITTE_MODULE_PATH=examples ajoute le dossier qui contient les sources importées.
VITTE_MODULE_PATH=examples build/bin/vitte run examples/tutorials/modules/main.vit --quiet --no-colorLes sources sont main.vit et calcul.vit. Résultat confirmé : code de sortie 0.
Avant d’exporter un nom, demandez-vous si les autres modules ont vraiment besoin de le connaître. Moins l’interface publique expose de détails, plus le module peut évoluer sereinement.
Tracer la résolution d’un import
Pour comprendre un import, écrivez son chemin segment par segment. Dans use tutorials::modules::calcul as calcul;, le résolveur doit trouver le module tutorials, puis son enfant modules, puis la déclaration calcul. L’alias final calcul est le nom local à utiliser dans le fichier courant.
| Couche | Information vérifiée |
|---|---|
| Racine de recherche | La racine fournie à la CLI contient le premier segment du chemin. |
| Chemin logique | Le fichier source déclare le chemin attendu par son appelant. |
| Déclaration | Le nom final correspond à un type, une procédure ou un module existant. |
| Visibilité | La déclaration appelée depuis un autre module est exportée. |
| Nom qualifié | L’appel utilise les segments et l’alias attendus. |
La commande vérifiée de l’exemple donne une racine explicite avec VITTE_MODULE_PATH=examples. Si l’on omet cette racine, la source principale existe toujours mais le compilateur peut ne pas retrouver la source importée. Cela montre pourquoi un échec de résolution n’est pas nécessairement une erreur dans la procédure importée.
Découper les responsabilités d’un petit projet
Un bon découpage suit les raisons de changer le code. Le module de calcul possède les règles numériques. Un module de modèle peut posséder les types et leurs validations. Le point d’entrée choisit les opérations à appeler et gère les entrées ou sorties disponibles. Un fichier par procédure crée souvent des chemins difficiles à suivre sans apporter de frontière utile.
Le sens des dépendances doit rester lisible. Un module de bas niveau ne devrait pas dépendre du point d’entrée ; le point d’entrée peut appeler les modules spécialisés. Lorsqu’un import forme un cycle ou que deux modules dépendent mutuellement de détails internes, cherchez une interface plus stable ou un module commun qui porte les types partagés.
Stabiliser une interface publique
Une signature exportée devient une dépendance pour ses appelants. Renommer le module, changer les paramètres ou transformer un type public peut demander des corrections dans plusieurs fichiers. Avant une modification, recherche tous les imports concernés, puis actualise le producteur et les appelants ensemble.
Expose une constante seulement si sa valeur fait partie du contrat. Si les appels doivent utiliser un comportement et non la valeur interne d’un réglage, exporte plutôt une procédure qui applique ce réglage. Cette séparation permet de changer le détail sans réécrire chaque appelant.
Le module calcul exporte additionner ; main importe le chemin sous le nom local calcul ; l’appel est calcul::additionner(20, 22). Il faut aussi fournir la racine de recherche examples. Le code de sortie confirmé est 0.
Identité du module et résolution du fichier
Un module possède un nom logique déclaré avec space et se trouve également dans une organisation de fichiers. Ces deux informations doivent rester cohérentes pour l’outil qui résout les imports. Le nom qualifié tutorials::modules::calcul indique un chemin logique ; VITTE_MODULE_PATH=examples indique à la CLI une racine de recherche dans l’exemple vérifié. Le chemin physique du fichier et le chemin utilisé dans le code jouent donc des rôles liés, mais distincts.
Une erreur « module introuvable » se situe avant l’accès à ses procédures. Une erreur « déclaration inconnue » peut indiquer que le module est présent, mais que le nom demandé n’existe pas sous cette graphie. Une erreur de visibilité indique que la déclaration est trouvée mais pas accessible depuis cet endroit. Enfin, une erreur d’appel peut porter sur les paramètres ou les types. Reconnaître ce classement réduit le nombre de pistes à examiner.
Les imports explicitent les dépendances
Dans use tutorials::modules::calcul as calcul;, le chemin identifie le module importé et l’alias fournit un nom local. L’appel calcul::additionner(20, 22) conserve la provenance de la procédure. Cette qualification aide à distinguer deux déclarations qui porteraient le même nom dans des modules différents et permet au lecteur de voir quelles parties du programme dépendent de quelle interface.
Les imports globaux, réexports et cycles de dépendances peuvent compliquer cette lecture. Une dépendance circulaire rend plus difficile l’identification du propriétaire d’un type ou d’une règle. Une organisation où les modèles centraux sont importés par des modules de calcul et de présentation, sans dépendance inverse, donne une direction plus claire au graphe. Les capacités précises de la CLI restent à confirmer pour les formes qui dépassent les imports déjà exercés.
Visibilité et stabilité d’une API
export expose une déclaration comme partie de l’interface du module. Les paramètres, types référencés et valeur de retour sont alors visibles pour les appelants. Exporter un type dont les champs ou les règles changent fréquemment peut rendre les consommateurs dépendants de détails internes. À l’inverse, une procédure exportée qui exprime une opération métier peut permettre au module de changer sa représentation sans modifier les appels.
Une API stable se juge par ses promesses : nom compréhensible, types accessibles, comportement documenté et règles d’erreur cohérentes. Lorsqu’une signature évolue, les appels, imports, tests et chemins de résolution concernés doivent évoluer avec elle. L’exemple à deux fichiers du dépôt montre la forme concrète de cette frontière : le module calcul possède le calcul, puis main importe et appelle l’opération qualifiée.
Les erreurs d’import se distinguent par étape
La résolution commence par la localisation du module. Si le chemin source n’est pas trouvé, l’environnement et la racine de recherche sont en cause. Quand le module est chargé, le compilateur doit résoudre le nom importé et vérifier sa visibilité. Une fois le nom disponible, la vérification de la signature compare les arguments. Ces étapes forment une chaîne : la correction d’un chemin ne peut pas réparer un type de paramètre incorrect, et l’ajout d’un export ne peut pas créer un fichier manquant.
Dans l’exemple confirmé, VITTE_MODULE_PATH=examples rend le module accessible, l’alias calcul raccourcit son chemin et export publie additionner. Le code de retour 0 établit que cet ensemble précis de fichiers et de paramètres fonctionne avec le binaire natif utilisé. Une structure de modules plus complexe nécessite ses propres preuves.
Chemins et portabilité
Les chemins de module peuvent être affectés par la disposition du dépôt et la manière de lancer la CLI. Un chemin relatif dépend du répertoire de travail ; une variable d’environnement peut modifier la racine ; un système de fichiers peut distinguer les majuscules de manière différente selon la plateforme. Un projet partageable doit documenter la commande qui charge ses modules et éviter les chemins secrets connus seulement de l’environnement du développeur.
Cette portabilité se vérifie avec des noms cohérents et une configuration explicite. Les noms logiques, les alias et les chemins physiques doivent être examinés séparément. La grammaire définit ce qui est permis dans le source ; l’outil d’importation décide où chercher ; la plateforme détermine les détails des chemins de fichiers.