Philosophie et Sécurité Mémoire
Jakt se positionne comme un langage de programmation système moderne, offrant une sécurité mémoire stricte sans sacrifier les performances d'exécution. En s'appuyant sur le comptage de références automatique (ARC) et un système de types rigoureux, il élimine les vulnérabilités courantes tout en générant du code C++ optimisé en arrière-plan.
Mécanismes de Protection
La sécurité dans Jakt est garantie par plusieurs couches de validation :
- Comptage de références automatique : Gestion du cycle de vie des objets de classe sans intervention manuelle.
- Système de types strict : Détection précoce des incohérences lors de la compilation.
- Vérification des limites : Protection automatique contre les dépassements de capacité pour les tableaux et collections.
- Interdiction des pointeurs nus : Les pointeurs bruts sont bannis en mode standard.
Le langage distingue trois types de pointeurs :
T: Pointeur fort vers une insatnce de classe avec ARC.weak T: Pointeur faible qui se nullifie automatiquement à la destruction de l'objet.raw T: Pointeur brut, exclusivement réservé aux blocsunsafe.
Sécurité Mathématique
Les opérations arithmétiques sont strictement contrôlées. Les débordements d'entiers (signés ou non) déclenchent des erreurs d'exécution. De plus, aucune conversion numérique implicite vers int n'est autorisée ; toutes les conversions doivent être explicitement déclarées.
Configuration de l'Environnement
Le déploiement de la chaîne de compilation nécessite un système Linux, macOS ou Windows (via WSL), avec Clang (version 18 ou supérieure), CMake et Ninja.
# Récupération des sources
git clone https://github.com/SerenityOS/jakt.git
cd jakt
# Initialisation de l'environnement de compilation
cmake -B compilation -GNinja -DCMAKE_CXX_COMPILER=clang++-18
# Génération de l'exécutable
cmake --build compilation
Le processus d'auto-amorçage (bootstrapping) s'effectue en trois phases distinctes, passant d'un générateur C++ initial à un compilateur entièrement auto-hébergé écrit en Jakt.
Pour valider l'installation, exécutez une instruction minimale :
echo 'fn principal() { println("Initialisation réussie") }' > test.jakt
./compilation/jakt test.jakt
./compilation/test
Syntaxe Fondamentale
La structure d'un programme Jakt repose sur des fonctions déclarées avec fn. Les variables sont immuables par défaut et nécessitent le mot-clé mut pour être modifiables.
fn principal() {
let statut_serveur = "actif"
let version_majeure: i64 = 4
mut requetes_traitees = 0
requetes_traitees = requetes_traitees + 15
println("État: {}, Version: {}, Requêtes: {}", statut_serveur, version_majeure, requetes_traitees)
}
Structures de Contrôle
Les flux de contrôle utilisent une syntaxe épurée, supportant les conditions imbriquées et les boucles itératives.
fn principal() {
let charge_cpu = 75
if charge_cpu > 90 {
println("Alerte critique")
} else if charge_cpu > 60 {
println("Charge modérée")
} else {
println("Système idle")
}
mut tentative = 0
while tentative < 3 {
println("Tentative de connexion: {}", tentative)
++tentative
}
for index in 1..5 {
println("Traitement lot: {}", index)
}
}
Collections et Types de Données
Jakt fournit des structures de données intégrées qui garantissent la sécurité mémoire lors de leur manipulation.
fn principal() {
let scores = [85, 92, 78, 95, 88]
let defauts = [0; 5]
println("Total entrées: {}", scores.size())
for score in scores {
println("Score obtenu: {}", score)
}
let annuaire = ["paris": "01", "lyon": "04", "marseille": "05"]
println("Indicatif Lyon: {}", annuaire["lyon"] ?? "N/A")
let acces_autorises = {443, 80, 8080, 443}
println("Port 8080 ouvert? {}", acces_autorises.contains(8080))
}
Structures et Classes
Le langage différencie les types par valeur (struct) et les types par référence (class).
struct Vecteur3D {
x: f64
y: f64
z: f64
public fn magnitude(this) -> f64 {
return sqrt((.x * .x + .y * .y + .z * .z) as f64)
}
}
fn principal() {
let pos = Vecteur3D(x: 1.0, y: 2.0, z: 2.0)
println("Norme: {}", pos.magnitude())
}
class Moteur {
cylindres: i32
public fn new(anon cylindres: i32) -> Self {
return Moteur(cylindres: cylindres)
}
public fn demarrer(this) {
println("Moteur {} cylindres démarré", .cylindres)
}
}
class MoteurTurbo: Moteur {
pression_bar: f64
public fn new(anon cylindres: i32, pression_bar: f64) -> Self {
return MoteurTurbo(cylindres: cylindres, pression_bar: pression_bar)
}
public fn demarrer(this) {
println("Turbo {} bar, {} cylindres démarré", .pression_bar, .cylindres)
}
}
fn principal() {
let v8_turbo = MoteurTurbo(cylindres: 8, pression_bar: 1.5)
v8_turbo.demarrer()
}
Gestion des Erreurs
Les fonctions susceptibles d'échouer sont annotées avec throws. L'appelant doit capturer les exceptions via des blocs try/catch.
fn valider_age(age: i32) throws -> String {
if age < 0 {
throw Error::from_string_literal("Âge invalide")
}
return "Majeur"
}
fn principal() {
try {
let statut = valider_age(25)
println("Statut: {}", statut)
let echec = valider_age(-5)
} catch err {
println("Échec validation: {}", err)
}
}
Fonctionnalités Avancées
Génériques et Adaptateurs (Traits)
Le système de types prend en charge la programmation générique et les interfaces partagées via les trait.
struct Cache<t> {
donnee: T
public fn recuperer(this) -> T {
return .donnee
}
}
trait Exportable {
fn exporter(this) -> String
}
struct Rapport implements(Exportable) {
titre: String
fn exporter(this) -> String {
return .titre
}
}
fn principal() {
let memoire = Cache(donnee: 42)
println("Valeur en cache: {}", memoire.recuperer())
let doc = Rapport(titre: "Bilan Annuel")
println("Export: {}", doc.exporter())
}
</t>
Filtrage par Motifs (Pattern Matching)
L'expression match permet de déstructurer les énumérations de manière exhaustive.
enum ReponseHTTP {
Succes(i32)
Erreur(String)
}
fn traiter(reponse: ReponseHTTP) {
match reponse {
Succes(code) => println("OK: {}", code),
Erreur(msg) => println("KO: {}", msg)
}
}
fn principal() {
traiter(ReponseHTTP::Succes(200))
traiter(ReponseHTTP::Erreur("Timeout"))
}
Interopérabilité et Modules
Jakt permet l'intégration de code C++ natif via des blocs unsafe, facilitant l'accès aux bibliothèques système existantes.
fn obtenir_timestamp() -> i64 {
unsafe {
cpp {
"""
#include <chrono>
return std::chrono::system_clock::now().time_since_epoch().count();
"""
}
}
}
fn principal() {
let temps = obtenir_timestamp()
println("Timestamp système: {}", temps)
}
Optimisation des Performances
Pour maximiser l'efficacité, il est recommandé de privilégier les types par valeur (struct) pour les petites données et d'utiliser les pointeurs faibles (weak) pour briser les cycles de références. Le compilateur supporte également l'évaluation à la compilation (comptime) pour précalculer les constantes.
comptime fn calculer_factorielle(n: i64) -> i64 {
if n <= 1 { return 1 }
return n * calculer_factorielle(n - 1)
}
fn principal() {
let fact_5 = calculer_factorielle(5)
println("Factorielle précalculée: {}", fact_5)
}