言語機能
Hewは耐障害性の高いサービス向けに設計されています。このページでは現在のv0.9.3機能セットと言語の方向性を分けて説明します。
アクターモデル
チャットルームの例では、メンバー数とメッセージ数を1つのアクターが所有します。その状態を変更するのはreceiveメソッドだけで、各呼び出しは型付きの結果を返します。
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
}
}メモリモデル
通常の所有値は再帰的に解放されます。アフィンリソースのクリーンアップ責任は1つだけです。#[resource]はスコープ終了時の安全なクリーンアップを行い、#[linear]はすべての経路で明示的な消費操作を必要とします。
監視ツリー
例では3つの子をone_for_oneスーパーバイザーの下に宣言し、1分あたり5回の再起動予算と、子ごとのpermanentまたはtransientポリシーを設定します。
// 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;
}構造化並行性
バッチはforkで3つの独立した処理を開始し、awaitで結果を集めます。scopeは、バッチが戻る前に子タスクの完了を待ちます。
// 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
}並行パターン
例では2つのアクター呼び出しをselectとafterタイムアウトで競わせ、その後await fork (...)で2つの独立した呼び出しを開始して両方の応答を集めます。
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}");
}ステートマシン
接続マシンはイベント、状態、ガード付き遷移、既定の動作を1つの表で宣言します。stepは現在の状態とイベントに対して宣言された遷移だけを適用します。
// 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
}失敗を型で扱うアクター呼び出し
このローカルアクターの例では、各リクエストが型付きの結果を返し、呼び出し側が処理または伝播する呼び出し境界を示します。
// 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"),
}
}ワイヤーコントラクト
プロファイルスキーマは各フィールドに安定した数値タグを割り当てます。任意のverifiedフィールドは古い相手や新しい相手が省略でき、互換性チェッカーがタグの変更を追跡します。
// 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;
}型システム
Hewの型システムはアクターの隔離とケイパビリティチェック境界を中心に構築されています。コンパイラはコンパイル時にSend/Frozenチェック、網羅性、アクターの隔離を強制します。
単相化によるジェネリクス
コンパイラは、プログラムが使用する具体型ごとにジェネリック型と関数を特殊化します。
Send + Frozenケイパビリティトレイト
Sendはアクター境界を越えられる値を示します。Frozenは安全に共有できる、深く不変な値を示します。
双方向型推論
期待される型がラムダやジェネリック呼び出しへ伝わるため、注釈を減らしながら推論結果を型チェッカーで確認できます。
網羅性チェック付きパターンマッチ
match式はコンパイル時に完全性がチェックされます。コンパイラはすべての可能なケースが処理されることを保証します。
externブロックによるC FFI
externブロックはCシンボルとHew側のシグネチャを記述します。FFIは既定でunsafeであり、ABI、ポインター、ライフタイムの正しさは呼び出し側の責任です。
Option<T>とResult<T, E>
値の不在と回復可能な失敗は明示的な値として表され、パターンマッチで処理するか?で伝播します。
アダプター付きIteratorトレイト
map、filter、foldなどのIteratorアダプターは、シーケンス上の遅延変換を組み合わせます。
コンパイル
Hewは今日からネイティブマシンコードにコンパイルします。hew buildコマンドはRustフロントエンドとRust LLVMバックエンドを駆動し、各ステージでより多くのチェッカー保証が保持されます。