Vitte Light — Guide Prêt

VITTE LIGHT — Guide utilisateur complet

Révision courante

0) Introduction — VITTE LIGHT

En clair

Vitte Light (abrégé VITL) est une déclinaison minimaliste du langage Vitte. L’objectif : offrir une syntaxe cohérente, des outils intégrés (formatage, analyse statique, tests, génération de doc) et un modèle mémoire prévisible, afin que débutants, développeurs intermédiaires et professionnels soient vite productifs — que ce soit pour un script d’atelier, un petit binaire livré à un client, ou un module embarqué via FFI dans une base C existante.

0.1 Philosophie du langage

  • Minimalisme utile : peu de concepts, bien polis (immutabilité par défaut, Result, match exhaustif).
  • Cohérence : même logique partout (noms, modules, erreurs explicites, conversions intentionnelles).
  • Polyvalence : itération rapide via VM ; livraison stable via compilation native.
  • Sécurité pragmatique : pas d’unsafe hors FFI, contrôle des bornes, messages d’erreurs guidants.
  • Portabilité : mêmes commandes, mêmes sources, mêmes résultats sur les OS majeurs.

Pourquoi ce choix ?

La majorité des tâches quotidiennes (scripts, outils CLI, glue entre systèmes) ne nécessitent ni threads lourds ni runtime sophistiqué. VITL couvre les “80 %” de besoins fréquents, sans la complexité d’un écosystème massif.

Quand il faut “sortir du cadre” (concurrence native, libs spécialisées, frameworks complets), on isole la partie critique en externe (C, etc.) et on relie via FFI.

Ce que VITL n’est pas
  • Un langage à runtime lourd : pas de GC complet, pas de VM omniprésente en production si vous compilez en natif.
  • Un framework web clé en main : vous pouvez écrire des serveurs, mais VITL privilégie les outils et binaires simples.
  • Un substitut aux écosystèmes “batteries incluses” massifs : utilisez le FFI pour tirer parti d’une lib existante si nécessaire.

0.2 Pour qui est fait VITL ?

Débutant

  • Apprendre sur une base saine : variables immuables par défaut, fonctions claires, erreurs explicites.
  • Éviter les pièges (pointeurs bruts, allocation manuelle) et se concentrer sur la logique.
  • Progresser par étapes : Result, match, types/conversions, lecture/écriture de fichiers.

Développeur intermédiaire

  • Automatiser avec des scripts solides, relisibles et versionnables.
  • Créer des apps CLI portables (un binaire, zéro surprise d’environnement).
  • Profiter du formateur (fmt), de l’analyse (check) et des tests (test).

Professionnel

  • Compiler en natif avec optimisation (-O3) et symboles de debug (-g).
  • Interopérer avec la base C existante (FFI stable) ; emballer les appels unsafe proprement.
  • Intégrer en CI/CD, produire la documentation depuis les commentaires ///.

Ce que vous ferez en 10 minutes

  1. Créer src/main.vitl avec un “Hello” minimal.
  2. Lancer vitl run pour tester en VM.
  3. Compiler en natif avec vitl build et exécuter le binaire généré.

Pas de projet à “initialiser”, pas de configuration cachée : un fichier, une commande, c’est tout.

Parcours d’apprentissage conseillé
  1. Jour 1 : variables/fonctions, println, lecture d’un fichier, codes de sortie (0/1/2/3).
  2. Jour 2 : Result + ?, match exhaustif, conversions numériques et chaînes.
  3. Jour 3 : structures (struct), méthodes (impl), petits tests avec test "nom".
  4. Jour 4 : packaging des binaires, génération de doc, introduction au FFI.

0.3 Domaines d'utilisation

  1. Scripts CLI robustes : parsing d’arguments, transformation de texte, traitements batch sur fichiers, petits ETL.
  2. Exécutables natifs : outils internes distribuables sans dépendances, utilitaires DevOps, petits daemons.
  3. Langage embarqué : pilotage d’un moteur, extension de logiciels C via chargement de .vitbc ou FFI direct.
Patterns classiques
  • “Lire → transformer → écrire” (CSV/JSON/texte) avec erreurs explicites et logs sobres.
  • “Observer → réagir” (watcher de fichiers) en combinant VITL + utilitaires du système.
  • “Intégrer → encapsuler” un appel C via FFI dans une API VITL sûre.

Quand éviter VITL ?

Si votre cœur de besoin est un framework web complet, une UI riche multi-plateforme, ou une concurrence massive multi-threads, vous serez plus efficace en déléguant ces aspects à un composant externe (C/C++/Rust/Go) — VITL reste un excellent ciment autour.

0.4 Points forts

Besoin des détails sur les compromis (pas de threads natifs, etc.) ? Voir 11) Limites.

0.5 Exemple rapide (Hello World)

Créez src/main.vitl et collez :

module app.main
import std.io

fn main() -> i32 {
  std.io::println("Bonjour Vitte Light")
  return 0
}
Pas à pas
  1. Exécuter (VM) : vitl run src/main.vitl
  2. Compiler (natif) : vitl build -O2 -o build/app src/main.vitl
  3. Lancer le binaire : ./build/app (Linux/macOS) ou build\\app.exe (Windows)
Variante : lire un argument
module app.main
import std.{io, cli, str}

fn main() -> i32 {
  let argv = cli::args()
  if str::len(argv) < 2 { io::eprintln("usage: app <nom>"); return 2 }
  io::println("Bonjour " + argv[1])
  return 0
}
Outils utiles dès maintenant
# Formater
vitl fmt src/

# Analyser
vitl check src/

# Lancer les tests (dans les fichiers)
vitl test

# Générer doc depuis ///-commentaires
vitl doc src/ -o build/docs.txt

0.6 Installation & mise à jour

Téléchargez l’exécutable adapté à votre OS, placez-le dans votre PATH et vérifiez : vitl --version. Pour les environnements verrouillés, vous pouvez l’installer localement dans le projet (scripts CI/CD) et l’appeler via un chemin relatif.

Astuce : en CI, mettez en cache le binaire VITL pour accélérer vos pipelines.

0.7 Aperçu d’un projet minimal

mon_projet/
  src/
    main.vitl        # module app.main
  build/             # artefacts (binaire, docs, bytecode)

Un fichier = un module. Le nom du module correspond à son chemin disque (ex. src/util/str.vitlmodule util.str). Importez explicitement ce que vous utilisez (import std.io, import util.str).

0.8 FAQ express

VITL a-t-il un ramasse-miettes ? Non, la mémoire est gérée par Rc/Weak : quand plus rien ne référence un objet, il est libéré.

Et les threads ? Pas de threads natifs dans la version Light ; utilisez des processus externes ou une lib C via FFI si c’est crucial.

Puis-je faire du réseau / des sockets ? Oui via la stdlib et/ou via FFI pour des stacks spécialisées.

Comment gérer les erreurs proprement ? Utilisez Result<T,E> et propagez avec ? ; couvrez les cas avec match.

0.9 Glossaire rapide

0.10 Où aller ensuite ?

Explorez 1) Structure de projet pour organiser vos fichiers, puis 3) Syntaxe pour ancrer les bases (variables, match, Result). Besoin de livrer ? Lisez 2) Outils en ligne de commande. Intégration C ? Voir 7) FFI. Enfin, gardez un œil sur 11) Limites pour comprendre les compromis qui gardent VITL rapide et prévisible.

1) Structure de projet — Guide complet

En clair

Trois dossiers suffisent : /src (code VITL), /libs (C/FFI), /build (artefacts). Cette séparation garde les sources propres, facilite les revues et les pipelines CI.

Une structure claire évite des heures perdues : on sait où créer un fichier, où trouver une fonctionnalité, où tombent les binaires. Cette section propose une arborescence de base puis des variantes (simple → complexe), des conventions de nommage, des exemples copier/coller (.gitignore, tâches build, CI), et des intégrations C (CMake/Meson).

1.1 Arborescence type minimale

/src   → code source principal (.vitl)
/libs  → bibliothèques natives C/FFI (.c, .so, .dll, .a)
/build → binaires, artefacts intermédiaires, bytecode (.vitbc)

mon_projet/
  src/
    main.vitl
    util/str.vitl
  libs/
    mylib.c
    mylib.a
  build/
    app        (binaire natif)
    app.vitbc  (bytecode)

1.2 Convention des modules

Fichier : src/std/io.vitl
Contenu : module std.io

Fichier : src/app/http/client.vitl
Contenu : module app.http.client

Imports clairs : import std.io, import util.str. Évite les conflits, favorise la réutilisation.

Raison pratique

Quand le chemin et le module correspondent, on sait toujours vit une fonction. Plus simple pour les revues, la navigation IDE et la CI.

1.3 Variantes d’organisation (selon la taille)

Projet simple
mon_projet/
  src/main.vitl
  src/util/print.vitl
  build/

Objectif : aller vite. Tests intégrés au bas des fichiers.

Projet moyen
mon_projet/
  src/app/main.vitl
  src/app/cli/args.vitl
  src/std/extra/time.vitl
  src/util/fs.vitl
  libs/fast.c
  build/{debug,release,docs}

On sépare app/, util/, std/ local. Dossiers debug/ et release/ dans build/.

Projet complexe (modulaire)
mon_projet/
  src/
    app/main.vitl
    app/sub/feature.vitl
    core/error.vitl
    core/config.vitl
    net/http.vitl
    net/tcp.vitl
    std/extra/...
  tests/
    test_core.vitl
    test_net.vitl
  libs/
    sqlite/sqlite3.c
    wrapper/foo.c
  build/
    debug/
    release/
    docs/
  scripts/
    build.sh
    release.sh
  docs/
    README.md
    CHANGELOG.md

Tests isolés dans /tests, scripts utilitaires dans /scripts, documentation dédiée dans /docs.

1.4 Rôles des dossiers (rappel express)

1.5 Conventions de nommage

1.6 Chemins & portabilité (Linux/macOS/Windows)

1.7 Tests : où les placer ?

src/core/math.vitl
  ... code ...
  test "somme de base" { assert(add(1,2) == 3) }

tests/test_math.vitl
  ... scénarios plus riches, I/O, golden files ...

1.8 Documentation générée

Documentez vos APIs avec /// en tête d’éléments publics, puis générez avec vitl doc src/ -o build/docs.txt. Conservez les docs “métier” plus longues dans /docs.

1.9 Sorties de build (organisation)

/build/
  debug/     → binaires -O0 -g (débogage)
  release/   → binaires -O3 (livraison)
  docs/      → doc générée (ex. docs.txt)
  *.vitbc    → bytecode (exécution VM)

1.10 Fichier .gitignore (copier/coller)

# VITL
/build/
*.vitbc

# OS
.DS_Store
Thumbs.db

# Éditeurs/IDE
.vscode/
.idea/
*.iml

# Logs/temp
*.log
.tmp/
.cache/

1.11 Tâches build pratiques

bash (Linux/macOS)
# scripts/build.sh
set -euo pipefail
mkdir -p build/release
vitl fmt src/
vitl check src/
vitl build -O3 -o build/release/app src/app/main.vitl
echo "OK → build/release/app"
PowerShell (Windows)
# scripts/build.ps1
$ErrorActionPreference = "Stop"
New-Item -ItemType Directory -Force -Path build/release | Out-Null
vitl fmt src/
vitl check src/
vitl build -O3 -o build/release/app.exe src/app/main.vitl
Write-Host "OK → build/release/app.exe"

1.12 Intégration CI (GitHub Actions minimal)

# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
  build-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install VITL
        run: |
          mkdir -p ~/bin
          curl -L <URL_VITL_LINUX> -o ~/bin/vitl
          chmod +x ~/bin/vitl
          echo "$HOME/bin" >> $GITHUB_PATH
      - name: Format & Check
        run: |
          vitl fmt src/
          vitl check src/
      - name: Tests
        run: vitl test
      - name: Build (release)
        run: vitl build -O3 -o build/release/app src/app/main.vitl
      - name: Upload artefacts
        uses: actions/upload-artifact@v4
        with:
          name: app-linux
          path: build/release/app

Adaptez l’étape “Install VITL” à votre source de binaire (URL/paquet interne).

1.13 Intégration d’une lib C (FFI) — CMake

# libs/CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(vitl_ffi C)
add_library(foo STATIC foo.c)
target_include_directories(foo PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
# Construire la lib puis lier côté VITL
mkdir -p libs/build && cd libs/build
cmake ..
cmake --build . --config Release
# Ensuite, côté VITL :
vitl build -O2 -L libs/build -lfoo -o build/app src/main.vitl

1.14 Intégration d’une lib C (FFI) — Meson

# libs/meson.build
project('vitl_ffi', 'c')
libfoo = static_library('foo', 'foo.c', include_directories: include_directories('.'))
# Build Meson/Ninja
meson setup libs/build libs
meson compile -C libs/build
# Puis lier lors du build VITL :
vitl build -O2 -L libs/build -lfoo -o build/app src/main.vitl

1.15 Packaging & release

1.16 Versionning & docs projet

1.17 Fichiers de configuration (exemples)

README.md (squelette)
# Mon Projet VITL
But : une ligne claire ici.

## Installation
- Télécharger le binaire.
- Ajouter au PATH.

## Utilisation
\`\`\`sh
vitl --help
\`\`\`
.editorconfig
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true

2) Outils en ligne de commande — Guide ultra-complet

Vue d’ensemble

2.1 Exécution immédiate (VM/JIT)

# Lancer un fichier
vitl run src/main.vitl

# Passer des arguments utilisateur au programme (utilisez -- pour séparer)
vitl run src/cat.vitl -- README.md

# Exécuter plusieurs fichiers (point d’entrée = premier fichier avec fn main)
vitl run src/app/main.vitl src/util/str.vitl

2.2 Compilation en exécutable natif

# Build simple (optimisation par défaut -O2)
vitl build -o build/app src/main.vitl

# Profils typiques
vitl build -O0 -g -o build/debug/app    src/main.vitl   # Débogage (rapide à compiler)
vitl build -O3    -o build/release/app  src/main.vitl   # Livraison (perf max)

# Émettre des artefacts intermédiaires
vitl build --emit-ir       -o build/app src/main.vitl   # IR lisible
vitl build --emit-bytecode -o build/app src/main.vitl   # Bytecode VM (.vitbc)

Conseils

2.3 Formateur, analyseur, tests, doc

# Formater tout le code (à intégrer en pre-commit)
vitl fmt src/

# Analyse statique (imports manquants, types, règles de base)
vitl check src/

# Tests intégrés (cherche et exécute tous les blocs `test "..." {}`)
vitl test

# Documentation à partir des commentaires /// en tête d’API publiques
vitl doc src/ -o build/docs.txt

2.4 Lier des bibliothèques C (FFI)

# Fichiers de shim C compilés séparément dans libs/
# Lier une lib partagée ou statique pendant le build
vitl build -O2 -L libs -lfoo -o build/app src/main.vitl

# Définir un rpath pour retrouver les .so/.dylib à l’exécution (Linux/macOS)
vitl build -O2 -L libs -lfoo -Wl,-rpath,./libs -o build/app src/main.vitl

2.5 Exemples concrets (sessions)

# Hello World (VM puis binaire)
vitl run   src/main.vitl
vitl build -O2 -o build/hello src/main.vitl
./build/hello
# Programme CLI avec arguments et codes d’erreur
vitl run src/cat.vitl -- data.txt     # 0 si succès, 2 usage, 3 I/O
vitl build -O2 -o build/cat src/cat.vitl
./build/cat data.txt
echo $?   # vérifie le code de sortie
# Pipeline CI local minimal (bash)
set -euo pipefail
vitl fmt src/
vitl check src/
vitl test
vitl build -O3 -o build/release/app src/app/main.vitl

2.6 Débogage & diagnostics

2.7 Organisation des sorties

mkdir -p build/{debug,release,docs}
vitl build -O0 -g -o build/debug/app   src/app/main.vitl
vitl build -O3    -o build/release/app src/app/main.vitl
vitl doc src/ -o build/docs/api.txt

2.8 Bonnes pratiques (tous niveaux)

2.9 Problèmes fréquents & solutions

“Permission denied” au lancement
  • Linux/macOS : chmod +x build/app, exécuter ./build/app.
  • Windows : exécuter build\\app.exe directement.
Options mal placées
  • Placez -o après build : vitl build -O2 -o build/app src/main.vitl.
  • Les arguments au programme se placent après -- : vitl run file.vitl -- arg1.

2.10 Table de référence rapide

Résumé — Utilisez run pour explorer, build pour livrer. Automatisez fmt/check/test en CI. Inspectez avec --emit-ir/--emit-bytecode. Pour le FFI, liez proprement avec -L/-l et un shim C clair.


3) Syntaxe : découverte progressive

Pour les débutants (d’abord l’intuition)

Un programme VITL est une suite d’instructions lues de haut en bas. Vous donnez des noms à des valeurs (variables), vous définissez des fonctions (recettes réutilisables), vous contrôlez l’ordre d’exécution (si/alors, boucles), et vous gérez les erreurs proprement (Result et ?).

3.0 Votre premier “mini-lab” (2 minutes)

  1. Créez src/play.vitl avec le contenu ci-dessous.
  2. Lancez vitl run src/play.vitl et observez la sortie.
module app.play
import std.io

fn main() -> i32 {
  let msg = "Bonjour"        // immuable
  let mut n = 1              // mutable
  n = n + 1
  std.io::println(msg + " n=" + n.to_string())
  return 0
}
// Sortie attendue : Bonjour n=2

3.1 Commentaires & documentation

Les commentaires expliquent pourquoi (pas juste “ce que ça fait”). Les commentaires de doc (///) apparaissent dans vitl doc.

// Ligne unique
/* Bloc multi-lignes */

/// Additionne deux entiers (sans débordement contrôlé)
fn add(a:i32, b:i32) -> i32 { return a + b }

3.2 Nommage lisible (conseils débutant)

let user_name = "Alice"
struct Point2D { x:f64, y:f64 }

3.3 Types de base & littéraux

Pas besoin de tout retenir : l’éditeur et les erreurs vous guident.

const PI:f64 = 3.14159
let ok:bool = true
let hex:i32 = 0xCAFE

3.4 Variables, constantes, mutabilité

Immuable par défaut = code plus sûr. Ajoutez mut uniquement où nécessaire.

const MAX_RETRY:i32 = 3         // constante
let count:i32 = 0               // immuable
let mut total:f64 = 0.0         // mutable
total = total + 1.5

// Inférence de type quand c'est clair
let message = "Salut"  // :str

3.5 Opérateurs & conversions

VITL évite les conversions implicites “magiques”. Convertissez avec as (numérique) ou to_string() (texte).

let n:i32 = 3
let x:f64 = 0.5
let y = (n as f64) + x          // cast requis

std.io::println("n=" + n.to_string())  // convertir avant concaténation

3.6 Chaînes : str vs String (images mentales)

let s:str = "abc"
let mut buf = String::from("abc")
buf.push("d")            // "abcd"
std.io::println(buf)

3.7 Collections, slices & boucles

Les indices sont vérifiés : une erreur claire vaut mieux qu’un crash silencieux.

let mut v:[i32] = [1,2,3]
v.push(4)
for x in &v { std.io::println(x.to_string()) }
let third = v[2]   // panique si hors bornes → message explicite

3.8 Contrôle de flux (recettes courantes)

// if / else
if y > 0.0 { std.io::println("positif") } else { std.io::println("non positif") }

// while
let mut i = 0
while i < 3 { std.io::println(i.to_string()); i = i + 1 }

// for + intervalles
for k in 0..3  { std.io::println(k.to_string()) }   // 0,1,2
for k in 0..=3 { std.io::println(k.to_string()) }   // 0..3 inclus

// break / continue
for n in 0..10 {
  if n == 5 { break }
  if n % 2 == 0 { continue }
  std.io::println(n.to_string())
}

3.9 Fonctions (penser “boîte noire”)

Une fonction prend des entrées, produit des sorties, et peut signaler un échec avec Result. VITL n’infère pas le type de retour : écrivez-le.

// Paramètres nommés et type de retour explicite
fn square(x:i32) -> i32 { return x * x }

// Valeur ou erreur (Result)
fn div(a:i32, b:i32) -> Result<i32, str> {
  if b == 0 { return Result::Err("division par zéro") }
  return Result::Ok(a / b)
}

// Passer par référence pour éviter une copie lourde
fn print_len(s:&String) -> () {
  std.io::println(s.len().to_string())
}

Astuce

Gardez des fonctions courtes qui font une seule chose ; préférez retourner Result plutôt que d’appeler panic().

3.10 Erreurs : Result, Option et opérateur ?

Result<T,E> représente un succès (Ok(T)) ou un échec (Err(E)). L’opérateur ? “bulle” l’erreur au dessus : s’il y a Err, la fonction courante s’arrête et renvoie cette erreur.

// I/O : propage l’erreur si le fichier est introuvable/illisible
fn load(path:str) -> Result<String, str> {
  let txt = std.fs::read_to_string(path)?   // <— propage
  return Result::Ok(txt)
}

// Valeur optionnelle (présence/absence)
match std.cli::env("HOME") {
  Some(h) => std.io::println(h),
  None    => std.io::eprintln("HOME non défini"),
}

3.11 match exhaustif & enums

match impose de couvrir tous les cas (soit explicitement, soit via _ pour “autre”).

enum Status { Ok, Err(str) }

fn report(s:Status) -> () {
  match s {
    Status::Ok     => std.io::println("ok"),
    Status::Err(e) => std.io::eprintln("erreur:" + e),
  }
}

3.12 Structs, méthodes (impl) et récepteurs &self

Les méthodes se définissent dans un bloc impl. &self signifie “emprunter l’objet sans le copier”.

struct Vec2 { x:f64, y:f64 }

impl Vec2 {
  fn norm(&self) -> f64 { return (self.x*self.x + self.y*self.y).sqrt() }
  fn dot(&self, o:Vec2) -> f64 { return self.x*o.x + self.y*o.y }
}

fn main() -> i32 {
  let v = Vec2 { x:3.0, y:4.0 }
  std.io::println(v.norm().to_string())  // 5
  return 0
}

3.13 Modules & imports (organisation du code)

Le chemin disque détermine le nom de module. Gardez cette règle et vous saurez toujours “où vit quoi”.

// Fichier : src/util/str.vitl
module util.str
// ... API ...

// Fichier : src/app/main.vitl
module app.main
import std.{io, fs}
import util.str

fn main()->i32 {
  std.io::println("Ready")
  return 0
}

3.14 Génériques (usage simple)

La version Light supporte des génériques “légers” pour des cas typiques (fonctions d’ordre supérieur, conteneurs simples).

// Exécuter une fonction et mesurer son temps
fn timed<F>(label:str, f:F) -> ()
where F: fn()->() {
  let t0 = std.time::now()
  f()
  std.io::println(label + ":" + (std.time::now()-t0).to_string() + " ms")
}

3.15 Formats d’entrées/sorties (console & fichiers)

// Console
std.io::print("sans retour")
std.io::println("avec retour")
std.io::eprintln("erreur: usage invalide")

// Fichiers
_ = std.fs::write_string("out.txt", "ok\n")?
let txt = std.fs::read_to_string("out.txt")?

3.16 Bonnes pratiques (tous niveaux)

3.17 Pièges fréquents (et correctifs)

E0001 : symbole inconnu
// Mauvais
module app.main
fn main()->i32 { std.io::pritnln("hi"); return 0 }

// Correct
module app.main
import std.io
fn main()->i32 { std.io::println("hi"); return 0 }
E0002 : types incompatibles
// Mauvais
let n:i32 = 3
std.io::println("n=" + n)

// Correct
std.io::println("n=" + n.to_string())

3.18 Exercices guidés (à vous de jouer)

  1. Convertisseur : lisez un nombre depuis la ligne de commande et affichez son double (gérez l’erreur si l’argument manque).
  2. Concat : créez une String, ajoutez-y trois morceaux (push), affichez le résultat.
  3. Vec2 : reprenez la struct Vec2, ajoutez une méthode scale(&mut self, k:f64), testez-la.
Aide-mémoire (quick ref)

4) Premiers exemples — ultra détaillés (spécial débutants)

Objectif

Écrire, exécuter, compiler, tester et diagnostiquer des programmes VITL pas à pas. Chaque exemple inclut : le code, la commande pour l’exécuter, la sortie attendue, des variantes, des tests et les erreurs fréquentes.

4.0 Avant de commencer

  1. Créez un dossier de travail (ex. vitte-light-demo/).
  2. À l’intérieur, créez src/ et build/.
  3. Chaque exemple ci-dessous suppose que les fichiers sont dans src/.

Commandes clés : vitl run <fichier.vitl> (exécution rapide) · vitl build -O2 -o build/app <fichier.vitl> (binaire natif) · vitl fmt src/ (formateur) · vitl test (tests intégrés).

4.1 Débutant → programme minimal (Hello + variables + concat)

// src/main.vitl
module app.main
import std.io

fn main() -> i32 {
  let msg = "Bonjour Vitte Light"
  let mut n = 1
  n = n + 1
  std.io::println(msg + " · n=" + n.to_string())
  return 0
}

Exécution : vitl run src/main.vitl

Sortie attendue : Bonjour Vitte Light · n=2

Ce que vous apprenez

Erreurs fréquentes

Variantes utiles
// A) Bonjour personnalisé via variable
module app.hello
import std.io
fn main()->i32 {
  let name = "Alice"
  std.io::println("Bonjour, " + name)
  return 0
}

// B) Codes de sortie (1 = erreur générique)
module app.exit
import std.io
fn main()->i32 {
  if 2 + 2 != 4 {
    std.io::eprintln("arithmétique cassée")
    return 1
  }
  return 0
}

Tests (mini TDD)

// Ajoutez à la fin de src/main.vitl :
test "arithmétique de base" { assert(2 + 2 == 4) }

Lancez : vitl test → affichera les tests réussis.


4.2 Débutant+ → lecture de fichier (usage & erreurs)

// src/cat.vitl
module app.cat
import std.{fs, io, cli, str}

fn main() -> i32 {
  let argv = cli::args()
  if str::len(argv) < 2 {
    io::eprintln("usage: cat <fichier>")
    return 2 // erreur d'usage CLI
  }
  let path = argv[1]
  if !fs::exists(path) {
    io::eprintln("introuvable: " + path)
    return 3 // erreur I/O
  }
  let contenu = fs::read_to_string(path)? // propage l'erreur I/O si échec
  io::print(contenu)
  return 0
}

Exécution : vitl run src/cat.vitl -- data.txt

Extensions (comptez octets, multi-fichiers, chronométrage)
// A) Taille en octets
module app.size
import std.{fs, io}
fn main()->i32 {
  let bytes = fs::read("data.bin")?
  io::println("taille=" + bytes.len().to_string() + " octets")
  return 0
}

// B) Multi-fichiers (ignore les manquants)
module app.cat_multi
import std.{fs, io, cli, str}
fn main()->i32 {
  let a = cli::args()
  if str::len(a) < 2 { io::eprintln("usage: cat_multi <f1> [<f2>...]"); return 2 }
  for i in 1..str::len(a) {
    let p = a[i]
    if !fs::exists(p) { io::eprintln("skip: " + p); continue }
    io::print(fs::read_to_string(p)?)
  }
  return 0
}

// C) Chronométrage simple
module app.timed_cat
import std.{fs, io, cli, str, time}
fn main()->i32 {
  let a = cli::args()
  if str::len(a) != 2 { io::eprintln("usage: timed_cat <f>"); return 2 }
  let t0 = time::now()
  _ = fs::read_to_string(a[1])?
  io::println("ms=" + (time::now()-t0).to_string())
  return 0
}

Diagnostic enrichi (messages pédagogiques)

let path = argv[1]
if !fs::exists(path) {
  io::eprintln("E2001: fichier introuvable → " + path)
  io::eprintln("Astuce: lancez depuis la racine du projet ou passez un chemin absolu.")
  return 3
}

Tests

// Exemple de test "attendu en erreur"
test "cat sans argument renvoie 2" {
  // pseudo-test illustratif : dans une vraie CI, on lance le binaire et on vérifie le code
  assert(2 == 2)
}

4.3 Débutant++ → calculs et types (struct, méthodes, tests)

// src/geom.vitl
module app.geom
import std.{io, math}

struct Vec2 { x: f64, y: f64 }

impl Vec2 {
  fn norm(&self) -> f64 { return (self.x*self.x + self.y*self.y).sqrt() }
  fn dot(&self, o: Vec2) -> f64 { return self.x*o.x + self.y*o.y }
}

fn main() -> i32 {
  let v = Vec2 { x: 3.0, y: 4.0 }
  io::println(v.norm().to_string()) // 5
  return 0
}

// Tests intégrés
test "norme 3-4-5" {
  let v = Vec2 { x:3.0, y:4.0 }
  assert(v.norm() == 5.0)
}
test "produit scalaire orthogonal" {
  let a = Vec2 { x:1.0, y:0.0 }
  let b = Vec2 { x:0.0, y:1.0 }
  assert(a.dot(b) == 0.0)
}

Exécution : vitl run src/geom.vitl → affiche 5

Tests : vitl test

Variante : paramètres CLI & parsing
// src/geom_cli.vitl
module app.geom_cli
import std.{io, cli, str}

struct Vec2 { x:f64, y:f64 }
impl Vec2 { fn norm(&self)->f64 { return (self.x*self.x + self.y*self.y).sqrt() } }

fn parse_f64(s:str) -> Result<f64,str> { return str::to_float(s) }

fn main()->i32 {
  let a = cli::args()
  if str::len(a) != 3 { io::eprintln("usage: geom_cli <x> <y>"); return 2 }
  let x = parse_f64(a[1])?
  let y = parse_f64(a[2])?
  let v = Vec2 { x:x, y:y }
  io::println(v.norm().to_string())
  return 0
}

4.4 Mini-projets guidés (copier / adapter)

4.4.1 Greeter (salut personnalisé + validation)

// src/greeter.vitl
module app.greeter
import std.{io, cli, str}

fn main()->i32 {
  let a = cli::args()
  if str::len(a) != 2 { io::eprintln("usage: greeter <nom>"); return 2 }
  let name = a[1]
  if str::len(name) == 0 { io::eprintln("nom vide"); return 2 }
  io::println("Bonjour, " + name)
  return 0
}

Test manuel : vitl run src/greeter.vitl -- AliceBonjour, Alice

4.4.2 Compteur de lignes (LC) avec messages clairs

// src/wc_lines.vitl
module app.wc_lines
import std.{io, fs, cli, str}

fn main()->i32 {
  let a = cli::args()
  if str::len(a) != 2 { io::eprintln("usage: wc_lines <fichier>"); return 2 }
  let p = a[1]
  if !fs::exists(p) { io::eprintln("introuvable: " + p); return 3 }
  let txt = fs::read_to_string(p)?
  // Compte naïf des lignes : nombre de '\n' + 1 si non vide
  let mut n:i64 = 0
  for ch in &txt {
    if ch == '\n' { n = n + 1 }
  }
  if str::len(txt) > 0 && txt[str::len(txt)-1] != '\n' { n = n + 1 }
  io::println(n.to_string())
  return 0
}

4.4.3 Mesure de temps & micro-bench

// src/bench.vitl
module app.bench
import std.{io, time}

fn heavy()->() {
  let mut s:i64 = 0
  for i in 0..100000 { s = s + i }
  io::println("acc=" + s.to_string())
}

fn main()->i32 {
  let t0 = time::now()
  heavy()
  io::println("ms=" + (time::now() - t0).to_string())
  return 0
}

4.5 Gérer les erreurs proprement (Result, Option, messages)

// src/config_load.vitl
module app.config_load
import std.{io, fs, str}

fn load_threshold(path:str) -> Result<i64, str> {
  if !fs::exists(path) { return Result::Err("absent: " + path) }
  let s = fs::read_to_string(path)?
  // Nettoie un éventuel retour chariot / fin de ligne
  let cleaned = str::replace(str::replace(s, "\r", ""), "\n", "")
  return str::to_int(cleaned)
}

fn main()->i32 {
  match load_threshold("threshold.txt") {
    Result::Ok(v) => { io::println("seuil=" + v.to_string()); return 0 }
    Result::Err(e) => { io::eprintln("E: " + e); return 3 }
  }
}

4.6 Modèle de projet minimal (à répliquer)

mon_projet/
  src/
    main.vitl         // point d'entrée
    greeter.vitl      // sous-outil
    wc_lines.vitl     // sous-outil
  build/              // binaires
// src/main.vitl
module app.main
import std.io

fn main()->i32 {
  std.io::println("Projet prêt. Voir src/greeter.vitl et src/wc_lines.vitl")
  return 0
}

Formatez : vitl fmt src/ · Testez : vitl test · Compilez : vitl build -O2 -o build/app src/main.vitl


4.7 Checklist mini-projet (qualité débutant → pro)

Récap’ éclair (débutant)

5) Mémoire et sécurité — ultra-guide débutant

En clair (version simple)

5.1 Pourquoi parler de mémoire ?

Un programme manipule des valeurs (nombres, textes, tableaux…). Ces valeurs occupent de la mémoire. Deux grands risques : utiliser de la mémoire libérée (crashes, comportements bizarres) et oublier de libérer (fuites). VITL vous aide : il libère automatiquement quand plus personne n’utilise la donnée (comptage de références), et il encadre les appels « bas niveau » via FFI C.

5.2 Modèle mental (débutant)

5.3 Références comptées : Rc<T>

import std.io

fn main() -> i32 {
  let a = Rc<i32>::new(42) // 1 détenteur fort
  let b = a.clone()        // 2 détenteurs forts
  std.io::println("ok: " + (*b).to_string())
  a.drop()                 // 1 détenteur fort
  b.drop()                 // 0 → libération automatique
  return 0
}

Quand utiliser : configuration partagée, cache simple, nœuds d’arbre, tables consultées par plusieurs modules.

À éviter

5.4 Lien faible : Weak<T> pour briser les cycles

Exemple classique : un parent garde ses enfants (fort), chaque enfant garde un lien vers son parent (faible).

// src/tree.vitl
module app.tree
import std.{io, str}

struct Node {
  name: String,
  parent: Weak<Node>,      // lien FAIBLE
  children: [Rc<Node>],     // liens FORTS
}

fn Node::new(name:str) -> Rc<Node> {
  return Rc<Node>::new(Node { name: String::from(name), parent: Weak<Node>::new(), children: [] })
}

fn Node::add_child(this:&Rc<Node>, child:Rc<Node>) -> () {
  // le parent garde fort
  this.children.push(child.clone())
  // l'enfant garde FAIBLE vers le parent
  let w = Rc::downgrade(this)       // Rc -> Weak
  child.parent.set(w)               // (API imagée : associer le Weak)
}

fn main()->i32 {
  let root = Node::new("root")
  let a = Node::new("A")
  Node::add_child(&root, a)
  io::println("children=" + root.children.len().to_string())
  // On peut faire "upgrade" d'un Weak en Rc pour utiliser temporairement le parent
  match root.children[0].parent.upgrade() {
    Some(p) => io::println("parent de A = " + p.name),
    None    => io::println("parent libéré"),
  }
  return 0
}

Idée : si le parent meurt, le Weak devient « vide » (impossible à upgrade()), donc pas de fuite.

5.5 Chaînes : str (vue) vs String (tampon possédé)

let s:str = "abc"
let mut buf = String::from("abc")
buf.push("d") // "abcd"
std.io::println(buf)

Erreurs typiques

5.6 Collections & slices (éviter les copies coûteuses)

import std.{io, vec}
let mut v:[i32] = vec::with_capacity(16) // évite les réallocations
v.push(1); v.push(2); v.push(3)
for x in &v { io::println(x.to_string()) } // itération par référence (pas de copie)
let third = v[2] // panique si hors bornes

Attention aux pointeurs « invalidés »

Ne gardez pas un pointeur (ou une référence brute côté C) vers l’intérieur d’un vecteur si vous continuez à push() : une réallocation peut déplacer la mémoire.

5.7 FFI C en sécurité (pont contrôlé)

Le C attend des *const char terminés par nul et ne comprend pas les String de VITL. Convertissez toujours via CString. Décidez qui libère quoi.

// Déclaration (ex.)
extern "C" { fn puts(msg:*const char) -> i32 }

fn c_puts(s:str) -> Result<(), str> {
  let c = std.c::CString::from_str(s)?   // valide l'absence de nul
  unsafe { _ = puts(c.as_ptr()) }        // frontière unsafe MINIMALE
  return Result::Ok(())
}

5.8 Libération automatique : quand ?

5.9 Petites recettes « zéro fuite »

5.10 Diagnostic & debug

// Message enrichi
let path = "data/config.txt"
if !std.fs::exists(path) {
  std.io::eprintln("E2001: fichier introuvable → " + path)
  std.io::eprintln("Astuce: lancez depuis la racine du projet ou passez un chemin absolu.")
  return 3
}

5.11 Mini-FAQ (débutant)

« Pourquoi Weak plutôt qu’un simple Rc ? »

Parce que deux Rc qui se référencent l’un l’autre ne tombent jamais à zéro → fuite. Weak n’augmente pas le compteur fort, donc la libération redevient possible.

« Je veux juste passer un texte à une fonction ; str ou String ? »

Si la fonction lit seulement, utilisez str. Si la fonction doit conserver et modifier plus tard, utilisez String.

« Puis-je garder un pointeur C vers un buffer VITL ? »

Évitez. Le C ne connaît pas les mouvements/redimensionnements. Copiez vers une zone gérée côté C, ou figez la durée de vie et documentez-la solidement.

5.12 Exemples « prêt à copier »

a) Partage simple sans cycle

// src/shared_config.vitl
module app.shared_config
import std.{io}

struct Config { name:String, retries:i32 }

fn main()->i32 {
  let cfg = Rc<Config>::new(Config { name:String::from("demo"), retries:3 })
  let worker1 = cfg.clone()
  let worker2 = cfg.clone()
  io::println("retries=" + worker2.retries.to_string())
  // À la fin, worker1, worker2 et cfg tombent à 0 → libération
  return 0
}

b) Encapsuler une ressource C

// src/ffi_wrapper.vitl
module app.ffi_wrapper
import std.{io, c}

// C opaque
extern "C" {
  fn obj_create() -> *mut void
  fn obj_destroy(p:*mut void) -> void
  fn obj_set_name(p:*mut void, name:*const char) -> i32
}

struct Obj { handle:*mut void }

fn Obj::new() -> Result<Obj, str> {
  let h = unsafe { obj_create() }
  if h == std.ptr::null_mut() { return Result::Err("ffi: create failed") }
  return Result::Ok(Obj{ handle:h })
}

fn Obj::set_name(&mut self, name:str) -> Result<(), str> {
  let cname = c::CString::from_str(name)?
  let rc = unsafe { obj_set_name(self.handle, cname.as_ptr()) }
  if rc != 0 { return Result::Err("ffi: set_name failed") }
  return Result::Ok(())
}

fn Obj::close(&mut self) -> () {
  if self.handle != std.ptr::null_mut() {
    unsafe { obj_destroy(self.handle) }
    self.handle = std.ptr::null_mut()
  }
}

fn main()->i32 {
  let mut o = Obj::new()?
  _ = o.set_name("demo")?
  o.close() // libération explicite
  return 0
}

5.13 Récapitulatif débutant

Mémo

Checklist « mémoire saine »


6) Gestion des erreurs — ultra-guide débutant

À retenir en 30 secondes

6.1 Pourquoi gérer les erreurs ?

Une bonne gestion d’erreurs rend votre outil prévisible et aimable : message clair, action proposée, code de sortie stable (pour les scripts/CI). VITL favorise l’approche explicite : on retourne un Result (ou Option) au lieu de masquer l’échec.

6.2 Le type Result<T,E> (base)

import std.{io, fs}

// Lecture de fichier : renvoie le texte ou une erreur (str)
fn read_cfg(path: str) -> Result<str, str> {
  if !fs::exists(path) {
    return Result::Err("absent: " + path)
  }
  let txt = fs::read_to_string(path)?   // propage l'erreur I/O si lecture échoue
  return Result::Ok(txt)
}

// Consommation : pattern matching
fn main() -> i32 {
  match read_cfg("config.txt") {
    Result::Ok(txt) => io::println(txt),
    Result::Err(e)  => { io::eprintln("E: " + e); return 3 }
  }
  return 0
}

Astuce — Commencez par une version « simple » où E est une chaîne str. Plus tard, passez à un type d’erreur structuré (cf. §6.9).

6.3 L’opérateur ? (propagation automatique)

? ne marche que dans une fonction qui retourne un Result<...>. Si l’expression à droite retourne Err, la fonction renvoie immédiatement cette erreur.

import std.{fs}

fn load_or_err(path: str) -> Result<str, str> {
  // Vérif claire + propagation compacte
  if !fs::exists(path) { return Result::Err("introuvable: " + path) }
  let txt = fs::read_to_string(path)?  // <= ici `?` propage l'erreur I/O
  return Result::Ok(txt)
}

Bon motif (garde précoce)

Validez tôt, échouez tôt avec un message actionnable. Cela évite les cascades d’erreurs plus loin.

6.4 Option<T> : absence de valeur ≠ erreur

import std.{io, cli}

fn print_home() -> () {
  match cli::env("HOME") {
    Some(h) => io::println(h),
    None    => io::eprintln("HOME non défini")
  }
}

Utilisez Option quand l’absence est attendue (ex. variable d’environnement facultative). Utilisez Result quand c’est une erreur.

6.5 Messages clairs : quoi / où / comment agir

// Mauvais : vague
io::eprintln("échec")

// Meilleur : contexte + astuce
io::eprintln("E2001: fichier introuvable → " + path)
io::eprintln("Astuce: lancez depuis la racine du projet ou passez un chemin absolu.")

6.6 Codes de sortie (stables)

6.7 Schéma recommandé pour main()

import std.{io, str, cli, fs}

// 1) "run" fait le travail et renvoie Result
fn run() -> Result<(), str> {
  let args = cli::args()
  if str::len(args) < 2 {
    return Result::Err("USAGE: app <fichier> (code 2)")
  }
  let path = args[1]
  if !fs::exists(path) { return Result::Err("E2001: absent → " + path) }
  io::print(fs::read_to_string(path)?)  // ? propage l'I/O
  return Result::Ok(())
}

// 2) "main" mappe proprement vers un code de sortie
fn main() -> i32 {
  match run() {
    Result::Ok(()) => return 0,
    Result::Err(msg) => {
      io::eprintln(msg)
      // Petite heuristique pour l'exemple :
      if str::find(msg, "USAGE:") != -1 { return 2 }
      if str::find(msg, "E2001") != -1 { return 3 }
      return 1
    }
  }
}

6.8 Validation d’entrée (avant l’I/O)

import std.{io, str}

fn parse_port(s: str) -> Result<i32, str> {
  match str::to_int(s) {
    Result::Ok(n) => {
      if n < 1 || n > 65535 { return Result::Err("port hors plage (1..65535)") }
      return Result::Ok(n as i32)
    }
    Result::Err(_) => return Result::Err("port invalide: " + s)
  }
}

6.9 Aller plus loin : erreurs structurées

Pour les applis plus grandes, définissez un type d’erreur unique. Exemple avec un enum et un to_msg() :

import std.{io, fs, str}

enum AppErr {
  Usage(str),   // USAGE/validation
  Io(str),      // fichiers/disque
  Parse(str),   // conversion/format
  Ffi(str),     // pont C
  Other(str),
}

fn err_usage(m:str) -> AppErr { return AppErr::Usage(m) }
fn err_io(m:str)    -> AppErr { return AppErr::Io(m) }
fn err_parse(m:str) -> AppErr { return AppErr::Parse(m) }
fn err_ffi(m:str)   -> AppErr { return AppErr::Ffi(m) }
fn err_other(m:str) -> AppErr { return AppErr::Other(m) }

fn to_msg(e:AppErr) -> str {
  match e {
    AppErr::Usage(m) => return "USAGE: " + m,
    AppErr::Io(m)    => return "I/O: " + m,
    AppErr::Parse(m) => return "PARSE: " + m,
    AppErr::Ffi(m)   => return "FFI: " + m,
    AppErr::Other(m) => return "ERR: " + m,
  }
}

// Mapping type d'erreur -> code de sortie
fn to_exit(e:AppErr) -> i32 {
  match e {
    AppErr::Usage(_) => return 2,
    AppErr::Io(_)    => return 3,
    AppErr::Ffi(_)   => return 4,
    _                => return 1,
  }
}

fn load_cfg(path:str) -> Result<str, AppErr> {
  if !fs::exists(path) { return Result::Err(err_io("absent: " + path)) }
  match fs::read_to_string(path) {
    Result::Ok(t) => return Result::Ok(t),
    Result::Err(e) => return Result::Err(err_io(e)),  // on ré-étiquette I/O
  }
}

fn run() -> Result<(), AppErr> {
  let a = std.cli::args()
  if str::len(a) != 2 { return Result::Err(err_usage("app <fichier>")) }
  let txt = load_cfg(a[1])?
  io::println(txt)
  return Result::Ok(())
}

fn main()->i32 {
  match run() {
    Result::Ok(())  => return 0,
    Result::Err(e)  => { io::eprintln(to_msg(e)); return to_exit(e) }
  }
}

6.10 FFI : convertir et relayer proprement

extern "C" { fn foo_do(p:*const char) -> i32 } // 0 = OK, <0 = erreur
import std.{c}

fn foo_call(s:str) -> Result<(), AppErr> {
  let cs = c::CString::from_str(s)?           // si nul interne: Err(...) auto
  let rc = unsafe { foo_do(cs.as_ptr()) }
  if rc < 0 { return Result::Err(err_ffi("foo_do rc=" + rc.to_string())) }
  return Result::Ok(())
}

6.11 Tests d’erreurs (unitaires)

test "parse_port rejette 0" {
  match parse_port("0") {
    Result::Ok(_)   => assert(false),  // ne devrait pas arriver
    Result::Err(m)  => assert(m == "port hors plage (1..65535)")
  }
}

6.12 Écrire moins, expliquer plus (gabarits « prêt à copier »)

a) Lecture de fichier conviviale

fn show(path:str) -> Result<(), str> {
  if !std.fs::exists(path) {
    return Result::Err("E2001: fichier introuvable → " + path + "\nAstuce: passez un chemin absolu.")
  }
  std.io::print(std.fs::read_to_string(path)?)
  return Result::Ok(())
}

b) Chaînage d’étapes avec ?

fn pipeline(p:str) -> Result<(), str> {
  let raw = std.fs::read_to_string(p)?        // 1) I/O
  let cleaned = std.str::replace(raw, "\r\n", "\n")
  let n = std.str::to_int(cleaned)?           // 2) parse
  if n <= 0 { return Result::Err("valeur non positive") }
  std.io::println("ok:" + n.to_string())      // 3) ok
  return Result::Ok(())
}

6.13 Panic : cas limites uniquement

// Invariant interne brisé: continuer serait dangereux
fn expect_even(n:i32) -> () {
  if n % 2 != 0 { panic("invariant: pair attendu, reçu " + n.to_string()) }
}

À proscrire en prod

Ne « paniquez » pas pour des erreurs d’utilisateur (fichier manquant, mauvais argument). Préférez Result + message + code de sortie.

6.14 Mini-FAQ débutant

« Pourquoi ? ne marche pas dans main() ? »

Parce que main() retourne un i32, pas un Result. Placez le ? dans une fonction run() -> Result<...>, puis faites le mapping dans main().

« Option ou Result ? »

Option si l’absence est normale (clé facultative). Result si c’est inattendu (erreur d’I/O, format).

« Comment ajouter du contexte à une erreur ? »

Préfixez le message avec la tâche en cours et l’élément fautif : "parse seuil: " + e, "ouvrir " + path + ": " + e.

6.15 Récapitulatif

Mémo

Checklist « erreurs propres »

let contenu = std.fs::read_to_string("config.txt")?

match std.fs::read_to_string("config.txt") {
  Result::Ok(txt) => std.io::println(txt),
  Result::Err(e)  => std.io::eprintln(e),
}

7) Interopérabilité C (FFI) — ultra-guide débutant (et au-delà)

Pourquoi le FFI ?

Le FFI (Foreign Function Interface) permet à un programme VITL d’appeler du code C existant (librairies système, libs tierces, moteurs natifs). On réutilise ainsi des décennies d’APIs sans tout réécrire.

Checklist sécurité FFI (imprimez-moi)

7.1 Vocabulaire express

7.2 Premier appel C (chaîne VITL → C)

import std.{io, c}

// Déclaration d'une fonction C : int puts(const char*);
extern "C" { fn puts(msg:*const char) -> i32 }

fn main() -> i32 {
  let cs = c::CString::from_str("Hello C!\n")?   // valide, pas de '\0' interne
  unsafe { _ = puts(cs.as_ptr()) }               // appel dans `unsafe`
  return 0
}

7.3 Chaînes C → VITL (qui libère ?)

De nombreuses libs C renvoient un char* allocé par elles. La règle est : on copie côté VITL, puis on libère avec free() (ou une fonction dédiée).

// C (entête côté .h)
// char* make_msg();           // alloue (malloc)
// void  free_msg(char* p);    // libère

extern "C" {
  fn make_msg() -> *mut char
  fn free_msg(p:*mut char) -> void
}

fn get_message() -> Result<String, str> {
  let p = unsafe { make_msg() }
  if p == std.ptr::null_mut() { return Result::Err("ffi: make_msg a renvoyé NULL") }

  // Copier le contenu C->VITL puis libérer côté C
  let s = std.c::copy_cstring_into_string(p)?   // <= utilitaire (copie jusqu'au '\0')
  unsafe { free_msg(p) }
  return Result::Ok(s)
}

Variante : si la lib documente « utilisez free() standard », exposez extern "C" { fn free(p:*mut void) -> void } et appelez-la, pas un delete maison.

7.4 Buffers binaires (pointeur + longueur)

Motif courant : passer un bloc mémoire à C et recevoir des octets en sortie.

// C
// int sha256(const uint8_t* data, size_t len, uint8_t* out32); // 0=OK

extern "C" { fn sha256(data:*const u8, len:usize, out:*mut u8) -> i32 }

fn hash256(input:[u8]) -> Result<[u8;32], str> {
  let mut out:[u8;32] = [0; 32]
  let rc = unsafe { sha256(input.as_ptr(), input.len(), out.as_mut_ptr()) }
  if rc != 0 { return Result::Err("ffi: sha256 rc=" + rc.to_string()) }
  return Result::Ok(out)
}

7.5 Handles opaques (pattern recommandé)

Exposez une poignée opaque et des fonctions d’usine/destruction côté C, puis encapsulez en VITL.

// C (shim)
// typedef struct Foo Foo;
// Foo* foo_create(int cap); void foo_destroy(Foo*);
// int   foo_push(Foo*, const char*); int foo_len(const Foo*);

extern "C" {
  fn foo_create(cap:i32)   -> *mut void
  fn foo_destroy(h:*mut void) -> void
  fn foo_push(h:*mut void, s:*const char) -> i32
  fn foo_len(h:*const void) -> i32
}

struct Foo { handle:*mut void }

impl Foo {
  fn new(cap:i32) -> Result<Foo,str> {
    let h = unsafe { foo_create(cap) }
    if h == std.ptr::null_mut() { return Result::Err("ffi: create failed") }
    return Result::Ok(Foo{ handle:h })
  }
  fn push(&mut self, s:str) -> Result<(),str> {
    let cs = std.c::CString::from_str(s)?
    let rc = unsafe { foo_push(self.handle, cs.as_ptr()) }
    if rc != 0 { return Result::Err("ffi: push rc=" + rc.to_string()) }
    return Result::Ok(())
  }
  fn len(&self) -> i32 { return unsafe { foo_len(self.handle) } }
  fn close(&mut self) -> () {
    if self.handle != std.ptr::null_mut() {
      unsafe { foo_destroy(self.handle) }
      self.handle = std.ptr::null_mut()
    }
  }
}

Discipline : un seul endroit « unsafe » par méthode, vérifications avant/après (NULL, codes).

7.6 Ownership & allocation — 3 règles d’or

  1. Celui qui alloue libère (avec la même famille d’allocateurs).
  2. Copiez de C vers VITL si vous voulez conserver la donnée après l’appel.
  3. Pas de pointeur C vers une donnée VITL temporaire (durée de vie insuffisante).

7.7 Cartographier les erreurs C → Result

fn c_ok(rc:i32) -> Result<(), str> {
  if rc == 0 { return Result::Ok(()) }
  return Result::Err("ffi: rc=" + rc.to_string())
}

7.8 Liens & édition (build)

Nom des bibliothèques : libfoo.so (Linux), libfoo.dylib (macOS), foo.dll (Windows).

# Linux/macOS (bibliothèque partagée)
vitl build -O2 -L libs -lfoo -o build/app src/main.vitl

# rpath (trouver la lib au runtime)
vitl build ... -Wl,-rpath,$ORIGIN/../libs

# Statique (si libfoo.a disponible)
vitl build -O3 -L libs -Wl,-Bstatic -lfoo -Wl,-Bdynamic -o build/app src/main.vitl

# Windows (MinGW, exemple)
vitl build -O2 -L libs -lfoo -o build/app.exe src/main.vitl

7.9 En-têtes C « propres »

// foo.h
#pragma once
#include <stddef.h>   // size_t
#include <stdint.h>   // int32_t, uint8_t

#ifdef __cplusplus
extern "C" {
#endif

typedef struct Foo Foo;
Foo*  foo_create(int32_t cap);
void  foo_destroy(Foo*);
int32_t foo_push(Foo*, const char* s);     // s: NUL-terminée, UTF-8
int32_t foo_len(const Foo*);

#ifdef __cplusplus
}
#endif

Note : documentez « qui libère quoi » et la null-termination des chaînes.

7.10 Callbacks & user_data

Beaucoup d’APIs C vous rappellent via une fonction. Passez un identifiant opaque et rassemblez la logique côté C si possible.

// C
// typedef void (*on_item_fn)(const char* s, void* user);
// int enumerate(on_item_fn cb, void* user);

extern "C" { fn enumerate(cb:*const void, user:*mut void) -> i32 }

// Côté VITL, préférez écrire un petit shim C si vous devez capturer de l'état.
// Sinon, passez un identifiant (index) et rangez l'état dans une table VITL.

7.11 Tests d’intégration FFI

7.12 Mini-FAQ débutant

« Puis-je passer une String VITL directement au C ? »

Non. Convertissez en CString (NUL-terminée), conservez-la vivante pendant l’appel, puis laissez VITL la libérer.

« La lib C me donne un char*. Je fais quoi ? »

Copiez dans une String VITL, puis libérez le char* avec la fonction fournie (free_msg ou free documenté).

« Pourquoi unsafe ? »

Parce que VITL ne peut pas vérifier la validité des pointeurs/tailles C. Isolez l’unsafe dans des wrappers courts et testés.

7.13 Mémo final

À mémoriser
extern "C" { fn puts(msg:*const char) -> i32 }
let cstr = std.c::CString::from_str("Hello C!\n")?
unsafe { _ = puts(cstr.as_ptr()) }

8) Stdlib — ultra-guide débutant (et utile aux pros)

À quoi sert la stdlib ?

La bibliothèque standard (stdlib) regroupe des modules prêts à l’emploi pour les tâches courantes : afficher du texte, lire/écrire des fichiers, manipuler les chaînes et vecteurs, mesurer le temps, accéder aux arguments de la ligne de commande, intéragir avec le C, etc.

Carte mentale

8.1 std.io — Entrées/sorties console

But : écrire sur la sortie standard (utilisateur) et la sortie d’erreur (diagnostics).

import std.io

io::print("Hello ")          // sans saut de ligne
io::println("world")         // avec saut de ligne
io::eprintln("attention!")   // sur STDERR

8.2 std.fs — Fichiers et chemins

But : lire/écrire des fichiers texte ou binaires, tester l’existence.

import std.{fs, io}

// Lire tout un fichier en chaîne (UTF-8)
let txt = fs::read_to_string("input.txt")?   // <= `?` propage l'erreur I/O
io::println("lu=" + txt)

// Écrire une chaîne (écrase si existe)
_ = fs::write_string("out.txt", "ok\n")?

// Lire binaire
let bytes = fs::read("image.bin")?
io::println("taille=" + bytes.len().to_string())

// Vérifier l'existence avant d'ouvrir
if !fs::exists("config.toml") {
  io::eprintln("absent: config.toml")
  // retourner un code 3 (I/O) dans `main` si approprié
}

Pièges

8.3 std.str — Chaînes et utilitaires

But : opérations usuelles sur les chaînes de caractères (UTF-8).

import std.{str, io}

let s = "a b c"
io::println(str::len(s).to_string())         // 5
let parts = str::split(s, ' ')               // ["a","b","c"]
let i = str::find("abc", "b")                // 1
let out = str::replace("a_b", "_", "-")      // "a-b"

// Parsing vers nombres (renvoie Result)
let n = str::to_int("42")?     // i64
let f = str::to_float("2.5")?  // f64

8.4 std.math — Maths

import std.{math, io}

let d = math::sqrt(3.0*3.0 + 4.0*4.0) // 5.0
let a = math::abs(-12)                 // 12
io::println(d.to_string())

8.5 std.time — Horloge & mesure

But : mesurer un temps d’exécution, estampiller un événement.

import std.{time, io}

let t0 = time::now()
heavy_work()
io::println("ms=" + (time::now() - t0).to_string())

8.6 std.cli — Arguments et variables d’environnement

import std.{cli, io, str}

let argv = cli::args()
if str::len(argv) < 2 {
  io::eprintln("usage: app <fichier>")
  // return 2 dans `main` (erreur d'usage)
}

// Variables d'environnement
match cli::env("HOME") {
  Some(h) => io::println(h),
  None    => io::eprintln("HOME non défini"),
}

Bons réflexes

8.7 std.vec — Vecteurs dynamiques

But : gérer des tableaux extensibles de valeurs.

import std.{vec, io}

let mut v:[i32] = vec::with_capacity(16)
v.push(1); v.push(2); v.push(3)
for x in &v { io::println(x.to_string()) }   // itération par référence (pas de copie)

match v.pop() {
  Some(last) => io::println("last=" + last.to_string()),
  None       => {}
}
io::println("len=" + v.len().to_string())

Piège

L’accès v[i] vérifie les bornes et peut panique si hors-limites. Si l’index peut être invalide, préférez une API sûre (selon la stdlib dispo) ou testez i < v.len() avant.

8.8 std.debug — Assertions & debug

import std::debug

debug::assert(2 + 2 == 4)
debug::dump("state=INIT")        // trace lisible rapide
// Selon l'environnement, on peut obtenir une backtrace si activée au build `-g`.

8.9 std.err — Erreurs structurées

But : centraliser l’information d’erreur (catégorie, message, contexte) et mapper vers des codes de sortie stables.

import std::{err, io}

fn parse_threshold(s:str) -> Result<i64, err::Error> {
  match std.str::to_int(s) {
    Result::Ok(v) => return Result::Ok(v),
    Result::Err(_) => return Result::Err(err::Error::new("parse int échoué")),
  }
}

8.10 std.c — Pont C (complément rapide)

Voir la section FFI pour le guide complet. Ici, le minimum utile.

import std::c

extern "C" { fn puts(msg:*const char) -> i32 }
let cs = c::CString::from_str("Hello C!\n")?
unsafe { _ = puts(cs.as_ptr()) }

8.11 Patrons composés (recettes prêtes à copier)

Lecture robuste avec message explicite

import std::{fs, io}

fn show_file(path:str) -> Result<(), str> {
  if !fs::exists(path) {
    io::eprintln("E2001: fichier introuvable → " + path)
    io::eprintln("Astuce: lancer depuis la racine du projet ou passer un chemin absolu.")
    return Result::Err("absent")
  }
  io::print(fs::read_to_string(path)?)
  return Result::Ok(())
}

Mesure de temps, factorisée

import std::{time, io}

fn timed(label:str, f: fn()->()) -> () {
  let t0 = time::now()
  f()
  io::println(label + ":" + (time::now()-t0).to_string() + " ms")
}

Parsing fiable d’un entier (fichier config)

import std::{fs, str, err}

fn load_threshold(path:str) -> Result<i64, err::Error> {
  let s = fs::read_to_string(path)?
  match str::to_int(str::replace(s, "\n", "")) {
    Result::Ok(v) => return Result::Ok(v),
    Result::Err(_) => return Result::Err(err::Error::new("parse int échoué")),
  }
}

8.12 FAQ débutant

« Pourquoi certaines fonctions retournent un Result ? »

Parce que l’opération peut échouer (fichier manquant, permissions, entrée invalide). Le type Result oblige à y penser et à gérer l’échec, ou à le propager avec ?.

« Quand utiliser println vs eprintln ? »

println pour la sortie « normale » (données destinées à l’utilisateur ou à un pipeline). eprintln pour les diagnostics et erreurs.

« Comment concaténer un nombre et une chaîne ? »

Convertissez d’abord le nombre : "n=" + n.to_string(). VITL ne devine pas le format pour éviter les ambiguïtés.

8.13 Aide-mémoire (quick ref)

import std.{io, fs, str, math, time, cli, vec, debug, err, c}

io::println("Hello")
_ = fs::write_string("out.txt", "ok\n")?
let n = str::len("abc") // 3
let t0 = time::now(); heavy(); io::println((time::now()-t0).to_string()+" ms")

9) Style et bonnes pratiques — ultra-guide débutant (utile aux pros)

Philosophie

Le but n’est pas d’écrire « du code qui marche », mais du code que tout le monde peut comprendre, modifier et tester rapidement. VITL encourage la clarté : immutabilité par défaut, erreurs explicites, modules simples.

9.1 Structure type d’un fichier

  1. module (doit refléter le chemin disque)
  2. imports groupés/par ordre hiérarchique
  3. constantes publiques → privées
  4. types (struct/enum) puis impl
  5. fonctions publiques → privées
  6. tests à la fin
// src/math/geom.vitl
module math.geom
import std.{math, io}

const EPS:f64 = 1e-9

struct Vec2 { x:f64, y:f64 }

impl Vec2 {
  fn norm(&self)->f64 { return (self.x*self.x + self.y*self.y).sqrt() }
}

pub fn dist(a:Vec2, b:Vec2)->f64 { return Vec2{ x:a.x-b.x, y:a.y-b.y }.norm() }

test "distance nulle sur même point" { assert(dist(Vec2{x:0,y:0}, Vec2{x:0,y:0}) < EPS) }

9.2 Nommage (clair et constant)

// Mieux
fn read_user_file(path:str)->Result<String,str> { ... }
// À éviter
fn ruf(p:str)->Result<String,str> { ... }

9.3 Mise en forme (formatter)

9.4 Imports

import std.{io, fs}
import util.{strfmt, path}

9.5 Fonctions : petite surface, retours clairs

// Mauvais : imbriqué
fn load(p:str)->Result<String,str>{
  if std.fs::exists(p){
    return std.fs::read_to_string(p)
  } else {
    return Result::Err("absent")
  }
}
// Mieux : early return + message clair
fn load(p:str)->Result<String,str>{
  if !std.fs::exists(p) { return Result::Err("E2001: absent:"+p) }
  return std.fs::read_to_string(p)
}

9.6 Erreurs : conventions

fn run(path:str)->i32 {
  match app::do_work(path) {
    Result::Ok(_)  => return 0,
    Result::Err(e) => { std.io::eprintln(e); return 3 }
  }
}

9.7 Données : immutabilité, mut avec parcimonie

let mut acc:i64 = 0
for n in 0..=100 { acc = acc + n }

9.8 Chaînes & formatage

9.9 Boucles, conditions, match

match status {
  Status::Ok      => {},
  Status::Err(e)  => std.io::eprintln(e),
}

9.10 Tests (AAA : Arrange, Act, Assert)

test "parse: nombre valide" {
  // Arrange
  let s = "42"
  // Act
  let n = std.str::to_int(s).unwrap()
  // Assert
  assert(n == 42)
}

9.11 Commentaires & documentation

/// Convertit une chaîne en entier signé.
/// Retourne Err si la chaîne n'est pas un entier valide.
fn parse_i64(s:str)->Result<i64,str> { return std.str::to_int(s) }

9.12 Logs & debug

9.13 Performance (sans prématurité)

9.14 FFI & sécurité (rappel de style)

9.15 Exemples « avant / après »

Avant (compact mais obscur)

fn r(p:str)->i32{if!std.fs::exists(p){std.io::eprintln("x");return 3}
std.io::print(std.fs::read_to_string(p).unwrap());return 0}

Après (clair et robuste)

/// Affiche le contenu d'un fichier texte.
/// Codes : 0 ok, 3 I/O manquant.
fn run(path:str)->i32 {
  if !std.fs::exists(path) {
    std.io::eprintln("E2001: fichier introuvable → " + path)
    return 3
  }
  match std.fs::read_to_string(path) {
    Result::Ok(s)  => { std.io::print(s); return 0 },
    Result::Err(e) => { std.io::eprintln(e); return 3 },
  }
}

9.16 Checklist « revue rapide »

Raccourcis utiles (mémo)
let user_name = "Alice"
fn compute_area(w:i32, h:i32) -> i32 { return w*h }
struct Rectangle { width:i32, height:i32 }

10) Conseils par niveau — guide progressif pour tous publics

Comment utiliser cette section

Choisissez votre point de départ, puis avancez palier par palier. Chaque niveau propose : objectifs clairs, mini-projets avec critères de réussite, et erreurs fréquentes. Vous pouvez parcourir rapidement les paragraphes grisés (notes) et revenir aux encadrés pratiques pour consolider.

Débutant — poser des bases solides

À ce stade, l’objectif est d’écrire de petits programmes qui s’exécutent sans surprise. Vous apprendrez à lire/écrire sur la console, manipuler quelques variables, appeler des fonctions de la bibliothèque standard et comprendre les messages d’erreur. Concentrez-vous sur la structure d’un fichier (module → imports → fonctions) et sur l’immutabilité par défaut.

Objectifs d’apprentissage

Mini-projets (avec critères de réussite)

  1. Hello : afficher « Bonjour Vitte Light ». Réussite : programme renvoie 0.
  2. Écho de fichier : lire un chemin argument et afficher le contenu. Réussite : message clair si le fichier n’existe pas (code 3).
  3. Compteur : afficher 0..N (inclus) depuis un argument. Réussite : N négatif → message d’usage (code 2).
// Débutant : compteur 0..=N
module app.counter
import std.{io, cli, str}

fn main()->i32 {
  let a = cli::args()
  if str::len(a) != 2 { io::eprintln("usage: counter <n>"); return 2 }
  let n = str::to_int(a[1])?
  if n < 0 { io::eprintln("n doit être ≥ 0"); return 2 }
  for i in 0..=(n as i32) { io::println(i.to_string()) }
  return 0
}

Erreurs fréquentes & remèdes

Routine « 20 minutes »

1) Écrire 10 lignes qui s’exécutent. 2) Lancer vitl fmt. 3) Casser volontairement un cas (fichier manquant) et lire le message d’erreur. 4) Ajouter un test minimal.

Intermédiaire — construire des outils fiables

Ici, vous structurez vos programmes en modules, vous gérez les erreurs avec Result et l’opérateur ?, et vous commencez à mesurer vos programmes (temps d’exécution, taille des E/S). L’objectif est de livrer des CLI robustes avec des messages d’erreur utiles, des codes de sortie stables et quelques tests.

Objectifs d’apprentissage

Mini-projets

  1. cat++ : concaténer plusieurs fichiers passés en arguments, ignorer ceux manquants avec un avertissement.
  2. todo CLI : gérer une liste (ajout, liste, suppression) dans un fichier texte dans $HOME.
  3. statlog : lire un fichier, compter les lignes/mots/caractères, afficher le temps d’exécution.
// Intermédiaire : "cat++" avec erreurs contextualisées
module app.catpp
import std.{io, fs, cli, str, time}

fn main()->i32 {
  let a = cli::args()
  if str::len(a) < 2 { io::eprintln("usage: catpp <fichiers...>"); return 2 }
  let t0 = time::now()
  let mut shown:i32 = 0
  for i in 1..str::len(a) {
    let p = a[i]
    if !fs::exists(p) { io::eprintln("E2001: introuvable → " + p); continue }
    match fs::read_to_string(p) {
      Result::Ok(s)  => { io::print(s); shown = shown + 1 },
      Result::Err(e) => io::eprintln("E2002: lecture échouée → " + p + " (" + e + ")"),
    }
  }
  io::eprintln("ms=" + (time::now()-t0).to_string())
  return if shown > 0 { 0 } else { 3 }
}

Profil d’erreurs (et parades)

Indicateurs de progression

Vous savez écrire un binaire -O2 avec des erreurs contextualisées, vous avez au moins deux tests par module, et vous pouvez expliquer en 2 phrases la responsabilité de chaque fichier.

Professionnel — livrer, intégrer, maintenir

Le but est de produire des binaires fiables, intégrés à une chaîne CI/CD, avec une surface FFI C minimale et sûre si nécessaire. Vous définissez des conventions d’équipe (codes d’erreurs, structure des modules, checklist de revue), vous générez la documentation à partir des commentaires /// et vous surveillez la qualité via des tests automatiques.

Objectifs d’apprentissage

Mini-projets

  1. Wrapper C : appeler une fonction C (ex. puts) via CString, tests d’intégration.
  2. Plugin .vitbc : charger un bytecode et exposer une API de scripts contrôlée.
  3. Doc & release : générer vitl doc, produire un binaire -O3, SHA256 et changelog.
// Pro : FFI encap + mapping d'erreurs
module app.ffi_safe
import std.{io, c}

extern "C" { fn puts(msg:*const char)->i32 }

fn c_puts_line(s:str)->Result<(),str> {
  let cs = c::CString::from_str(s)?
  let rc = unsafe { puts(cs.as_ptr()) }
  if rc < 0 { return Result::Err("E0701: puts a échoué") }
  return Result::Ok(())
}

fn main()->i32 {
  match c_puts_line("Hello C!\n") {
    Result::Ok(_)  => return 0,
    Result::Err(e) => { io::eprintln(e); return 4 }
  }
}

Checklist « prêt à livrer »

Signaux de maturité

Vous pouvez brancher une lib C sans fuite, diagnostiquer un crash avec un backtrace, et expliquer en 30 secondes comment reproduire un bug (commande + entrée minimale).

Coach / Lead — cadrer et accélérer

Installez des garde-fous simples : gabarit de fichier (module/imports/tests), rubriques de code review (noms, erreurs, tests), matrice des codes d’erreurs, et script de vérification locale (fmt + check + test).

  • Revue = vérifier le pourquoi (doc), le quoi (API claire) et le comment (impl lisible).
  • Favoriser les petites PRs avec mini-projets livrables en < 1 jour.
Filières (adapter l’apprentissage)

Scripting/ops : std.io/std.fs/std.cli, messages d’erreur concrets, packaging binaire.

Données/ETL : lecture par blocs, parsers ligne à ligne, mesure de temps E/S.

Systèmes/embarqué : tailles fixes, conversions explicites, FFI stricte et documentée.

Parcours 30-60-90 jours (indicatif)
  1. 0–30 : 5 mini-CLI + tests basiques, erreurs contextualisées, fmt systématique.
  2. 31–60 : modulariser, mesurer, documenter, ajouter CI locale (script), 1 petit wrapper C.
  3. 61–90 : release binaire -O3, doc auto, matrice d’erreurs stable, plugin/bytecode.
Astuce : gardez un fichier « carnet de bord » avec la commande exacte pour reproduire chaque bug (entrée minimale + sortie attendue). Vous gagnerez des heures.

11) Limites (version Light) — version détaillée, tout public

Pourquoi ces limites ?

Vitte Light est pensé pour couvrir rapidement la majorité des petits outils, scripts et utilitaires sans vous noyer dans la complexité. Pour y parvenir, le langage fait des choix qui privilégient la lisibilité, la prévisibilité et des binaires compacts. En contrepartie, certaines fonctionnalités « lourdes » ne sont pas intégrées nativement. Ce n’est pas une faiblesse mais un périmètre assumé : quand vos besoins dépassent ce périmètre, vous pouvez brancher une bibliothèque externe (FFI C) ou utiliser un outil spécialisé. Cette section vous explique clairement ce qui est non inclus, ce que cela implique concrètement, et comment contourner proprement les cas limites.

11.1 Pas de threads natifs

Vitte Light n’inclut pas de modèle de threads intégré. Cela signifie qu’un même processus VITL n’exécutera pas deux fonctions « en parallèle » sur plusieurs cœurs. En pratique, la plupart des petits outils n’en ont pas besoin : lecture/écriture de fichiers, transformation de texte, génération de rapports ou appels systèmes séquentiels restent instantanés à l’échelle humaine. L’absence de threads évite aussi une grande classe de bugs (verrous oubliés, interblocages, conditions de course) et simplifie énormément le débogage.

Si vous devez paralléliser des traitements lourds (par exemple, compresser un grand nombre de fichiers, calculer des hachages, convertir des images), la stratégie recommandée consiste à lancer plusieurs processus du même binaire et à distribuer le travail entre eux. C’est simple à raisonner : chaque processus a sa propre mémoire, ne partage rien, et l’OS se charge d’exploiter tous les cœurs. Vous pouvez coordonner ces processus via des fichiers temporaires, des pipes, des numéros de tâches, ou un petit superviseur en VITL qui répartit des chemins d’entrée sur N sous-processus. Pour les cas encore plus spécifiques (réseau, GPU, planification fine), appuyez-vous sur une bibliothèque native via FFI C qui expose la parallélisation en interne, tout en gardant une interface VITL minimaliste et sûre.

11.2 Pas de GC complet (usage de Rc/Weak)

Vitte Light ne s’appuie pas sur un ramasse-miettes global. La mémoire est gérée par références comptées (Rc<T>) : lorsqu’il n’existe plus de référence « forte » vers une donnée, elle est libérée immédiatement. C’est facile à comprendre, déboguer et anticiper. La seule vigilance concerne les cycles forts (A pointe vers B et B vers A en « fort ») qui ne peuvent pas se libérer tout seuls. La parade est simple : pour les liens de « retour » (parent → enfant → parent), utilisez une Weak sur un des côtés. Vous évitez ainsi la fuite de mémoire sans devoir installer un GC.

Concrètement, structurez vos données avec un propriétaire clair (l’élément qui vit plus longtemps) et des observateurs qui n’empêchent pas la libération. Par exemple, un « Document » peut posséder en Rc ses « Sections », tandis que chaque « Section » ne conserve qu’un Weak vers son « Document ». Au moment de la suppression, tout se libère de manière déterministe. Si vous manipulez des graphes complexes, faites un petit audit visuel : repérez les boucles A ↔ B et convertissez le lien de retour en Weak. Pour des structures gigantesques et dynamiques où un GC serait utile, déléguez la structure au C (ou à une lib avec GC intégré) derrière un wrapper VITL finement documenté sur l’ownership.

11.3 Génériques partiels

Le système de types couvre les cas fréquents (fonctions et structures paramétrées simples), mais n’offre pas tous les raffinements des langages dédiés à la méta-programmation lourde. En clair : on privilégie des signatures explicites et des conversions claires aux « types magiques ». Pour un outil de 50-300 lignes, c’est un avantage : vous lisez l’API en un coup d’œil et vous évitez les erreurs implicites.

Quand vous sentez que vous forcez le langage (cascade de paramètres de type, abstractions très profondes), c’est souvent un signal pour revenir à des types concrets et des fonctions dédiées aux 2-3 cas qui comptent, ou pour mettre la généricité « exotique » derrière une API C minuscule. Vous gardez une interface VITL stable et vous laissez la sophistication au moteur natif.

11.4 FFI limité à l’ABI C

L’interopérabilité de Vitte Light vise l’ABI C. Cela garantit un pont fiable et portable avec un très vaste écosystème (libc, zlib, SQLite, etc.). En revanche, pour des bibliothèques C++/Rust/Go, vous devrez fournir un shim C : une petite couche qui expose des fonctions C pures (paramètres scalaires, pointeurs, longueurs), et qui s’occupe en interne d’appeler le code C++/Rust/Go. C’est une pratique standard : elle clarifie les responsabilités, stabilise la surface d’échange et simplifie le débogage.

Le contrat à respecter est simple : les chaînes passées au C doivent être des CString valides (UTF-8 sans \0 interne) et leur durée de vie doit couvrir l’appel. Documentez clairement qui alloue et qui libère chaque tampon. Si la lib vous renvoie un pointeur alloué de son côté, prévoyez la fonction de libération correspondante et appelez-la systématiquement. En cas d’erreur, mappez les codes natifs vers des messages clairs côté VITL et convertissez en codes de sortie stables (par exemple, « 4 » pour une erreur FFI).

11.5 Performances et mémoire : à quoi s’attendre

Pour des scripts et utilitaires, Vitte Light offre des temps de démarrage très courts et des binaires légers, avec des performances « assez bonnes » en I/O et en calcul numérique classique. L’absence de threads intégrés ne pénalise pas les tâches séquentielles, et l’absence de GC évite des pauses non déterministes. Les optimisations usuelles suffisent le plus souvent : lire/écrire en blocs plutôt que caractère par caractère, éviter les copies inutiles, réutiliser des tampons, convertir explicitement pour éviter les promotions coûteuses, et compiler en -O2 (ou -O3 si le binaire final le justifie). Si un hot-spot reste déterminant, déportez-le dans une fonction C spécialisée et appelez-la via FFI.

11.6 I/O intensives et très gros fichiers

Pour des fichiers énormes (journaux massifs, dumps), privilégiez le traitement en flux : lire par blocs (ou ligne par ligne) et traiter au fil de l’eau. Évitez de charger tout un fichier en mémoire, sauf si c’est réellement nécessaire. Cette approche garde la consommation mémoire stable et rend votre utilitaire robuste face à des entrées plus volumineuses que prévu. En complément, mesurez le temps (std.time::now()) et affichez une synthèse sobre en sortie d’erreur (durée, octets lus, lignes traitées) pour diagnostiquer facilement les goulots d’étranglement.

11.7 Portabilité et environnement

Vitte Light vise Linux, BSD, macOS et Windows. Quelques différences existent selon l’OS (droits d’exécution, chemins, encodages de console). Restez sur des chemins relatifs maîtrisés, vérifiez exists avant lecture, et affichez des messages d’erreur concrets (chemin, cause). En FFI, tenez compte des conventions d’édition de liens (-L/-l, rpath) et fournissez des instructions claires pour retrouver la bibliothèque au lancement (variable d’environnement, dossier libs/ du projet).

11.8 Quand « sortir » du périmètre Vitte Light

Posez-vous trois questions simples. 1) Le besoin est-il ponctuel et localisé ? Si oui, vous pouvez souvent rester en VITL et, si nécessaire, appeler une ou deux fonctions C ciblées. 2) Le besoin est-il central et persistant (calcul distribué, UI lourde, moteur 3D, GC sophistiqué) ? Dans ce cas, mieux vaut déléguer ce cœur à une lib native éprouvée et ne garder en VITL que la « colle » et le flux. 3) Le besoin touche-t-il l’infrastructure (threads complexes, scheduling, GPU) au point d’absorber tout le projet ? Alors utilisez un langage/outil dédié pour ce sous-système et exposez une interface C propre pour que VITL puisse l’orchestrer.

11.9 En résumé

Vitte Light est un langage d’efficacité pragmatique : rapide à apprendre, rapide à livrer, fluide à maintenir. Les limites énumérées ici ne sont pas des blocages, mais des garde-fous qui vous aident à décider quand rester simple, quand composer avec des processus, et quand brancher une bibliothèque native. En gardant l’interface VITL claire et en documentant vos hypothèses (ownership, erreurs, chemins), vous obtiendrez des outils robustes, portables et faciles à diagnostiquer—exactement ce dont on a besoin au quotidien.


12) Codes de sortie — guide ultra complet, tout public

À quoi servent les codes de sortie ?

Chaque programme Vitte Light (VITL) se termine avec un entier appelé « code de sortie ». Par convention : 0 signifie « succès », et toute valeur non nulle signale une erreur. Les scripts, CI/CD, et autres outils automatiques s’appuient sur ce code pour décider quoi faire ensuite (continuer, réessayer, alerter, etc.). Dans VITL, le code de sortie est simplement la valeur renvoyée par fn main() -> i32.

12.1 Principes de base

12.2 Table des codes (noyau + recommandations)

VITL standardise un petit noyau (0–4). Vous pouvez étendre avec des identifiants supplémentaires si votre outil le nécessite.

Code Nom court Quand l’utiliser Message typique (stderr)
0 EX_OK Exécution normale (pas de message d’erreur)
1 EX_GENERIC Erreur générique / panic erreur: échec interne; réessayez avec -g
2 EX_USAGE Mauvais arguments CLI, aide requise usage: app <fichier> [options]
3 EX_IO Fichiers/chemins/droits I/O E2001: fichier introuvable → data.txt
4 EX_FFI Appel bibliothèque native (C) a échoué E0701: FFI call failed (rc=-1)
5 EX_CONFIG Config manquante/invalide E3001: config.toml invalide (clé 'port')
6 EX_NET Réseau (DNS, socket, TLS) E4002: connexion refusée host:port
7 EX_DATA Données d’entrée invalides E5001: JSON invalide à la ligne 12
8 EX_TIMEOUT Opération expirée E6001: délai dépassé (5s)
9 EX_PERM Permission refusée E6002: accès refusé → /root/out
10 EX_UNSUP Fonction non supportée ici E6003: option --gpu non supportée

Bonnes pratiques : restez dans une petite plage (0–15) et documentez-la. Les environnements POSIX normalisent l’état sur 8 bits (0–255). Des valeurs négatives peuvent être interprétées de façon imprévisible — évitez-les.

12.3 Exemple canonique : mapper les erreurs vers un code

// Style recommandé : une fonction "run" retourne Result, "main" traduit en code.
module app.main
import std.{io, fs, str}

const EX_OK:i32      = 0
const EX_GENERIC:i32 = 1
const EX_USAGE:i32   = 2
const EX_IO:i32      = 3

fn run() -> Result<(), str> {
  let argv = std.cli::args()
  if str::len(argv) < 2 {
    return Result::Err("usage: app <fichier>")
  }
  let path = argv[1]
  if !std.fs::exists(path) {
    return Result::Err("E2001: fichier introuvable → " + path)
  }
  std.io::print(std.fs::read_to_string(path)?) // `?` propage l'erreur I/O
  return Result::Ok(())
}

fn main() -> i32 {
  match run() {
    Result::Ok(()) => { return EX_OK }
    Result::Err(msg) => {
      std.io::eprintln(msg)
      // Heuristique simple : décider du code en fonction du message
      if std.str::starts_with(msg, "usage:") { return EX_USAGE }
      if std.str::starts_with(msg, "E2001:") { return EX_IO }
      return EX_GENERIC
    }
  }
}

12.4 Messages d’erreur : forme et ton

// I/O robuste (extrait)
let path = argv[1]
if !std.fs::exists(path) {
  std.io::eprintln("E2001: fichier introuvable → " + path)
  std.io::eprintln("Astuce: lancez depuis la racine du projet ou passez un chemin absolu.")
  return EX_IO
}

12.5 Conventions CLI usuelles

12.6 Intégration shell, CI/CD et pipelines

# bash
vitl build -O2 -o build/app src/main.vitl && ./build/app data.txt
echo "exit=$?"

# pipelines
set -o pipefail
./produce | ./filter | ./consume
echo "pipeline exit=$?"

12.7 JSON + codes (outil scriptable)

Pour un outil consommé par d’autres programmes, couplez un code avec une sortie JSON courte afin de mêler lisibilité humaine et robustesse machine.

// Exemple : en cas d'erreur, JSON minimal sur stdout, détails sur stderr.
fn main() ->i32 {
  match run() {
    Result::Ok(()) => { std.io::println(r#"{"ok":true}"#); return 0 }
    Result::Err(msg) => {
      std.io::eprintln(msg)
      std.io::println(r#"{"ok":false,"code":2}"#)
      return 2
    }
  }
}

12.8 Tests des codes de sortie

Vérifiez vos codes avec des tests système (scripts) et des tests VITL ciblés.

# test shell (ex.)
./app                       # manque d'arguments
test $? -eq 2 || echo "attendu EX_USAGE"

./app data/missing.txt
test $? -eq 3 || echo "attendu EX_IO"

12.9 Questions fréquentes

12.10 Résumé opérationnel


13) Diagnostics courants — guide ultra complet, tout public

Objectif

Vous aider à trouver, comprendre et corriger rapidement les erreurs de compilation et d’exécution en Vitte Light (VITL). Cette section propose une méthode de triage, des exemples concrets, des gabarits de messages, et des checklists par thème (CLI, fichiers, FFI, etc.).

13.1 Méthode de triage (rapide et efficace)

  1. Lire le message complet (ligne, colonne, module) avant d’essayer quelque chose.
  2. Isoler un MRE (Mini Repro d’Erreur) : réduisez le code au plus petit exemple qui reproduit le problème (souvent 1–2 fichiers).
  3. Compiler en mode debug pour des messages plus riches : vitl build -O0 -g -o build/app src/main.vitl.
  4. Inspection statique avec vitl check src/ (style, imports, incohérences communes).
  5. Lire/ajouter des types aux frontières (I/O, FFI, conversions) pour faire apparaître les incompatibilités.
  6. Tracer ce qui se passe : ajouter std.io::eprintln("dbg: ...") ou std.debug::dump("state=...") si dispo.
  7. Documenter le correctif dans le code (commentaire court) si l’origine était subtile (encodage, chemin relatif, etc.).
Raccourcis utiles

13.2 Anatomie d’un message d’erreur

Un diagnostic VITL indique typiquement (fichier:ligne:colonne), quoi (code, libellé) et piste (conseil). Exemple :

src/app/main.vitl:12:15: E0002: types incompatibles
  aide: caster avec `as` ou convertir avec `.to_string()` selon le contexte

13.3 Erreurs de compilation fréquentes (avec correctifs)

E0001 — Symbole inconnu / import manquant

// Mauvais
module app.main
fn main()->i32 {
  std.io::pritnln("hi")   // faute de frappe + pas d'import
  return 0
}

// Correct
module app.main
import std.io
fn main()->i32 {
  std.io::println("hi")
  return 0
}

E0002 — Types incompatibles (cast/convertir)

// Mauvais
let n:i32 = 3
let x:f64 = 0.5
let y = n + x      // i32 + f64 → erreur

// Correct (cast local)
let n:i32 = 3
let x:f64 = 0.5
let y = (n as f64) + x

// Chaînes
std.io::println("n=" + n)              // E0002
std.io::println("n=" + n.to_string())  // OK

Règle : conversion explicite pour les nombres (as), .to_string() pour concaténer avec du texte.

E0003 — Variable non initialisée

// Mauvais
let mut acc:i32
if cond { acc = 1 }
std.io::println(acc.to_string())

// Correct
let mut acc:i32 = 0
if cond { acc = 1 }
std.io::println(acc.to_string())

E0401 — match non exhaustif

enum Mode { Fast, Slow }

match m {
  Mode::Fast => do_fast(),
  // manque Slow → E0401
}

// Correct
match m {
  Mode::Fast => do_fast(),
  Mode::Slow => do_slow(),
}

E0501 — Conflit de mutabilité

Déclarez let mut si vous modifiez une variable. Évitez les écritures concurrentes dans la même expression.

E0601 — Format chaîne invalide (p.ex. CString)

Pour le FFI, une CString ne doit pas contenir \0 interne. Voir la section FFI ci-dessous.

13.4 Erreurs d’exécution fréquentes (runtime)

E2001 — Fichier introuvable (I/O)

let path = "data/config.txt"
if !std.fs::exists(path) {
  std.io::eprintln("E2001: fichier introuvable → " + path)
  return 3 // EX_IO
}
let txt = std.fs::read_to_string(path)?  // `?` propage l'erreur I/O

E0301 — Dépassement d’index

let v:[i32] = [1,2,3]
let x = v[3]  // panique: hors bornes
// Correct
if i < v.len() { let x = v[i] }

E0201 — Division par zéro

fn div(a:i32, b:i32) -> Result<i32,str> {
  if b == 0 { return Result::Err("division par zéro") }
  return Result::Ok(a / b)
}

E0801 — UTF-8 invalide

En lecture texte, fichiers mal encodés ⇒ préférer lecture binaire + conversion contrôlée ou nettoyer l’entrée.

13.5 FFI (C) — erreurs typiques et correctifs

Checklist sécurité FFI

E1001 — Appel FFI hors unsafe

// Mauvais
extern "C" { fn puts(msg:*const char)->i32 }
fn main()->i32 {
  let s = std.c::CString::from_str("Hi\n")
  puts(s.unwrap().as_ptr()) // E1001
  return 0
}

// Correct
extern "C" { fn puts(msg:*const char)->i32 }
fn main()->i32 {
  let s = std.c::CString::from_str("Hi\n")?
  unsafe { _ = puts(s.as_ptr()) }
  return 0
}

E0601 — CString invalide (caractère nul)

let msg = std.c::CString::from_str("ok\0oops") // E0601
if msg.is_err() { return Result::Err("E0601: CString invalide (\\0 interne)") }

E0701 — FFI: code de retour d’échec

let rc = unsafe { foo_call(...) }
if rc != 0 {
  std.io::eprintln("E0701: FFI call failed (rc="+rc.to_string()+")")
  return 4 // EX_FFI
}

13.6 Quand « rien » ne marche : stratégie pas-à-pas

  1. Confirmez l’environnement (répertoire courant, OS, droits d’accès).
  2. Affichez les arguments au démarrage (std.cli::args()) pour vérifier ce que reçoit votre programme.
  3. Utilisez des chemins absolus pour les ressources (temporairement, pour lever le doute).
  4. Ajoutez des logs ciblés (une ligne par étape importante, sur stderr si possible).
  5. Réduisez à un MRE et remontez progressivement jusqu’à l’état initial.

13.7 Performance ou bug ? (petits tests rapides)

let t0 = std.time::now()
heavy()
std.io::eprintln("dbg: heavy ms=" + (std.time::now()-t0).to_string())

13.8 Gabarits de messages (prêts à coller)

13.9 Par thèmes : checklists de résolution

Arguments CLI
Fichiers & chemins
Chaînes & encodage
Typage
FFI

13.10 Tests de non-régression (verrouiller le fix)

test "lecture fichier: path manquant renvoie EX_IO" {
  // pseudo-test: isolez la fonction qui résout un code depuis un message
  let rc = map_error_to_exit("E2001: fichier introuvable → data.txt")
  assert(rc == 3)
}

13.11 Exemples « messages enrichis »

// I/O robuste
let path = argv[1]
if !std.fs::exists(path) {
  std.io::eprintln("E2001: fichier introuvable → " + path)
  std.io::eprintln("Astuce: lancer depuis la racine du projet ou passer un chemin absolu.")
  return 3
}
// FFI robuste
let msg = std.c::CString::from_str("ok\n")
if msg.is_err() { return Result::Err("E0601: CString invalide (caractère nul)") }
unsafe { _ = puts(msg.unwrap().as_ptr()) }

13.12 Mini-FAQ diagnostics

Résumé — Lisez le message complet · isolez un MRE · vitl check + -O0 -g · ajoutez des types aux frontières · messages enrichis et codes stables · verrouillez le fix par un test.


14) Checklist avant de livrer

Pourquoi cette checklist ?

Elle rassemble tout ce qu’il faut vérifier avant d’expédier un binaire ou un script Vitte Light (VITL). Cochez de haut en bas : structure, build, qualité, erreurs, I/O, FFI, perfs, docs, packaging, CI/CD et post-livraison. Astuce : gardez un Mini Repro d’Erreur (MRE) pour chaque correction critique et un test associé.

14.1 Contrôle express (60 s)

14.2 Structure & modules

14.3 Build & artefacts

14.4 Qualité du code & style

14.5 Gestion des erreurs & UX

14.6 Entrées/Sorties & fichiers

14.7 Mémoire & sécurité

14.8 FFI (C) — si utilisé

14.9 Tests & couverture

14.10 Performance & ressources

14.11 Documentation & exemples

14.12 Packaging & distribution

14.13 CI/CD

14.14 Observabilité & journalisation

14.15 Post-livraison (dernier mètre)

Rappels « prêt à coller »
# Formatage, lint, build, tests (local)
vitl fmt src/ tests/
vitl check src/
vitl build -O0 -g -o build/app src/main.vitl
vitl test
vitl build -O3 -o build/app src/main.vitl

# Exécution simple
./build/app --help
./build/app <args>

FIN DU GUIDE