Aller au contenu principal

Fonctionnalités du langage

Hew est conçu pour des services résilients et performants. Cette page sépare les fonctionnalités v0.9.3 de la direction plus large du langage.

Modèle d'acteurs

L'exemple de salon de discussion conserve le nombre de membres et de messages dans un acteur. Seules ses méthodes receive modifient cet état, et chaque appel renvoie un résultat typé.

chat_room.hew
actor ChatRoom {
    var members: i64 = 0;
    var message_count: i64 = 0;
 
    receive fn join(user_id: i64) -> i64 {
        members = members + 1;
        members
    }
 
    receive fn post_message(content: string) -> i64 {
        message_count = message_count + 1;
        message_count
    }
}

Modèle mémoire

Les valeurs possédées ordinaires sont libérées récursivement. Les ressources affines gardent une seule responsabilité de nettoyage : #[resource] fournit le nettoyage de fin de portée, tandis que #[linear] exige une action de consommation explicite sur chaque chemin.

Arbres de supervision

L'exemple déclare trois enfants sous un superviseur one_for_one, avec un budget de cinq redémarrages par minute et des politiques permanentes ou transitoires pour chaque enfant.

service_cluster.hew
// A supervisor declares its restart strategy, its restart budget
// (`intensity: N within <window>`), and its children. Each child names the
// actor type and its init args; a per-child `restart:` policy decides whether
// a crash is restarted (permanent), restarted only on failure (transient),
// or never (temporary).
actor DatabasePool {
    let pool_size: i64;
    receive fn query() -> i64 {
        pool_size
    }
}
 
actor CacheLayer {
    let capacity: i64;
    receive fn get() -> i64 {
        capacity
    }
}
 
actor ApiHandler {
    let port: i64;
    receive fn handle() {}
}
 
supervisor ServiceCluster {
    strategy: one_for_one;
    intensity: 5 within 60s;
 
    child db: DatabasePool { pool_size: 16 } restart: permanent;
    child cache: CacheLayer { capacity: 1024 } restart: permanent;
    child api: ApiHandler { port: 8080 } restart: transient;
}

Concurrence structurée

Le lot lance trois tâches indépendantes avec fork, puis récupère leurs résultats avec await. Le scope délimite ce travail enfant avant le retour du lot.

batch.hew
// Structured concurrency: a `scope` block opens a region that joins all
// child work before it returns. Start independent jobs with `fork`, then
// collect each result with `await`.
fn process_batch(a: i64, b: i64, c: i64) -> i64 {
    var total = 0;
    scope {
        let ra = fork run_job(a);
        let rb = fork run_job(b);
        let rc = fork run_job(c);
        let first = await ra;
        let second = await rb;
        let third = await rc;
        total = first + second + third;
    };
    total
}
 
fn run_job(job: i64) -> i64 {
    job * 2
}

Motifs de concurrence

L’exemple met deux appels d’acteur en concurrence avec select et un délai after, puis utilise await fork (...) pour lancer deux appels indépendants et collecter leurs réponses.

patterns.hew
actor Fetcher {
    receive fn fetch(key: i64) -> i64 {
        key
    }
}
 
actor Store {
    receive fn get_users() -> i64 {
        0
    }
    receive fn get_posts() -> i64 {
        0
    }
}
 
// Wait for the first responder, with a timeout fallback. Each arm races an
// actor ask; the first to reply wins and the rest are cancelled.
fn race_fetch(primary: Fetcher, fallback: Fetcher, key: i64) -> i64 {
    select {
        resp from primary.fetch(key) => resp handle e { -1 },
        resp from fallback.fetch(key) => resp handle e { -1 },
        after 500ms => -1,
    }
}
 
// Run independent calls concurrently and collect every result.
fn parallel_fetch(db: Store) -> (i64, i64) {
    let (users, posts) = await fork (db.get_users(), db.get_posts());
    (users.expect("store stopped"), posts.expect("store stopped"))
}
 
fn main() {
    let primary = spawn Fetcher;
    let fallback = spawn Fetcher;
    println(race_fetch(primary, fallback, 42));
    let db = spawn Store;
    let (users, posts) = parallel_fetch(db);
    println(f"{users} {posts}");
}

Machines à états

La machine de connexion déclare ses événements, états, transitions gardées et comportement par défaut dans une table. step n'applique qu'une transition déclarée pour l'état et l'événement courants.

connection.hew
// A connection lifecycle as a compiler-checked state machine.
// Events are declared up front; transitions route with `=>` and a body
// that constructs the target state. Every (state, event) pair must be
// covered — `default { state }` makes the rest a no-op stay.
machine Connection {
    events {
        Connect { host: string; }
        Disconnect;
        HeartbeatTimeout;
    }
 
    state Idle;
    state Connecting { host: string; }
    state Connected { host: string; }
    state Disconnected { retries: i64; }
 
    on Connect { host }: Idle => Connecting { host: host }
    on Connect { host }: Disconnected => Connecting when state.retries < 5 { host: host }
    on Disconnect: Connected => Disconnected { retries: 0 }
    on HeartbeatTimeout: Connected => Disconnected { retries: 0 }
 
    default { state }
}
 
fn main() {
    var conn = Connection.Idle;
    _ = conn.step(Connection.Event.Connect { host: "10.0.0.1" });
    println(conn.state_name()); // Connecting
}

Appels d'acteurs avec erreurs typées

Cet exemple d’acteur local montre la frontière d’appel avec erreurs typées : chaque requête renvoie un résultat que l’appelant traite ou propage.

actor_calls.hew
// Actor calls make failure part of the method surface. Every call yields a
// Result, so callers handle a stopped actor or expired request deadline alongside
// the successful value. The error branch below has the typed
// ActorError<Never, Never> carried by this method.
actor Worker {
    let id: i64;
    receive fn process(task: i64) -> i64 {
        task + id
    }
}
 
fn dispatch(task: i64) -> i64 fails ActorError<Never, Never> {
    let w = spawn Worker { id: 1 };
    // `?` on the binding unwraps Ok and propagates a typed ActorError<Never, Never>
    // on the failure branch.
    let answer = (w.process(task))?;
    answer
}
 
fn main() {
    match dispatch(7) {
        .Ok(v) => println(f"result = {v}"),
        .Err(_) => println("dispatch failed"),
    }
}

Contrats Wire

Le schéma de profil donne un numéro stable à chaque champ. Le champ optionnel verified peut être omis par des pairs plus anciens ou plus récents, tandis que le vérificateur de compatibilité suit les changements de numéros.

user.hew
// Wire types carry explicit field tags (@1, @2, ...) for forward- and
// backward-compatible binary encoding. Optional fields may be omitted by
// older or newer peers without breaking the schema.
#[wire]
type UserProfile {
    name: string @1;
    email: string @2;
    age: i32 @3;
    verified: Option<bool> @4 optional;
}

Système de types

Le système de types de Hew est construit autour de l'isolation par acteurs et de frontières vérifiées par capacités. Le compilateur applique les vérifications Send/Frozen, l'exhaustivité et l'isolation des acteurs à la compilation.

Génériques avec monomorphisation

Le compilateur spécialise les types et fonctions génériques pour chaque type concret utilisé par le programme.

Traits de capacité Send + Frozen

Send marque les valeurs qui peuvent franchir la frontière d’un acteur. Frozen marque les valeurs profondément immuables qui peuvent être partagées sans risque.

Inférence de types bidirectionnelle

Les types attendus circulent vers les lambdas et les appels génériques, ce qui réduit les annotations tout en laissant le type déduit visible au vérificateur.

Correspondance de motifs avec exhaustivité

Les expressions match sont vérifiées pour leur complétude à la compilation. Le compilateur s'assure que chaque cas possible est traité.

FFI C avec des blocs extern

Les blocs extern décrivent les symboles C et leurs signatures Hew. La FFI est unsafe par défaut : l'ABI, les pointeurs et les durées de vie restent sous la responsabilité de l'appelant.

Option<T> et Result<T, E>

L'absence et les échecs récupérables sont des valeurs explicites, traitées par correspondance de motifs ou propagées avec ?.

Trait Iterator avec adaptateurs

Les adaptateurs comme map, filter et fold composent des transformations paresseuses sur les séquences.

Compilation

Hew compile déjà en code machine natif. La commande hew build pilote le frontend Rust et le backend Rust LLVM, avec davantage de garanties conservées à chaque étape.

.hew → Frontend → Vérification des types → MIR → LLVM IR → Binaire natif