Language Tour
A quick tour of Hew's syntax and features.
Functions
Functions are declared with fn. The last expression in a block is the return value — no return keyword needed (though you can use it for early exits).
fn add(a: Int, b: Int) -> Int {
a + b
}
fn greet(name: String) {
println(f"Hello, {name}!");
}
fn abs(x: Int) -> Int {
if x < 0 { return -x; }
x
}Variables
let creates immutable bindings; var creates mutable ones. Immutable by default keeps code predictable.
let x = 42;
var counter = 0;
counter = counter + 1; // OK — counter is mutable
// x = 10; // compile error: x is immutableString interpolation
Prefix a string with f to embed expressions inside {} braces. The compiler infers the formatting for each type.
let name = "world";
let n = 42;
println(f"Hello, {name}! The answer is {n}.");Control flow
Hew provides if/else, match, while, and for. All are expressions — they produce values.
let status = if count > 0 { "active" } else { "idle" };match shape {
Circle(r) => println(f"radius {r}"),
Rectangle(w, h) => println(f"{w} x {h}"),
}while running {
process_next();
}
for item in items {
println(item);
}Structs, enums, and traits
Structs hold data; enums model alternatives (with optional payloads); traits define shared behaviour. impl blocks attach methods, and dyn Trait enables dynamic dispatch through vtables.
type Point {
x: f64;
y: f64;
}
enum Shape {
Circle(f64),
Rectangle(f64, f64),
}
trait Greetable {
fn greet(g: Self) -> Int;
}
impl Greetable for Point {
fn greet(p: Point) -> Int { 1 }
}
// Dynamic dispatch via vtable
fn call_greet(obj: dyn Greetable) -> Int {
obj.greet()
}State machines
A machine defines a value type with named states, typed events, and compiler-checked transitions. The compiler verifies that every state handles every event — missing transitions are a compile error, not a runtime surprise.
machine CircuitBreaker {
state Closed { failures: Int; }
state Open;
state HalfOpen;
event Success;
event Failure;
event Timeout;
on Failure: Closed -> Open;
on Timeout: Open -> HalfOpen;
on Success: HalfOpen -> Closed { failures: 0 }
on Failure: HalfOpen -> Open;
default { state }
}Machines are values — store them in actors, pass them to functions, match on their state. The step() method advances the machine by one event, mutating it in place.
var cb = CircuitBreaker::Closed { failures: 0 };
cb.step(Failure); // Closed → Open
cb.step(Timeout); // Open → HalfOpen
cb.step(Success); // HalfOpen → Closed
match cb {
Closed { failures } => println(f"healthy, {failures} failures"),
Open => println("circuit open — failing fast"),
HalfOpen => println("testing..."),
}Inside transitions, state accesses the source state's fields and event accesses event payload data. Guards with when enable data-dependent transitions.
machine TokenBucket {
state Allowing { tokens: Int; }
state Throttled;
event Request;
event Replenish;
on Request: Allowing -> Allowing when state.tokens > 1 {
tokens: state.tokens - 1
}
on Request: Allowing -> Throttled when state.tokens <= 1;
on Replenish: Throttled -> Allowing { tokens: 5 }
default { state }
}See the State Machines page for the full guide — including actor integration, wildcard transitions, and CLI tooling.
Error handling
Hew uses Result<T, E> with the ? operator for error propagation — no exceptions. Functions return Result and use ? to unwrap or propagate errors to the caller.
fn divide(a: Int, b: Int) -> Result<Int, Int> {
if b == 0 { Err(1) } else { Ok(a / b) }
}
fn compute() -> Result<Int, Int> {
let x = divide(10, 2)?; // unwrap or propagate
let y = divide(x, 3)?;
Ok(y)
}Regex
Hew has built-in regex support. Use re"pattern" for regex literals, =~ to test for a match, and !~ to test for a non-match. No imports or external libraries needed.
let email = "user@example.com";
// =~ returns true if the string matches the pattern
if email =~ re"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$" {
println(f"'{email}' is a valid email");
}
// !~ returns true if the string does NOT match
let code = "let x = 42;";
if code !~ re"^//" {
println(f"'{code}' is code, not a comment");
}
// Works in conditions and control flow
let input = "GET /api/users HTTP/1.1";
if input =~ re"^(GET|POST|PUT|DELETE) " {
println("HTTP request line");
}Actors
Actors are the core concurrency primitive. Each actor is an isolated state machine with a private mailbox. It processes one message at a time — no data races by design. Message handlers use receive fn; internal methods use fn.
actor Counter {
let value: Int;
receive fn increment(amount: Int) {
value = value + amount;
println(f"now {value}");
}
receive fn get_count() -> Int {
value
}
}
fn main() {
let c = spawn Counter;
c.increment(5); // fire-and-forget
let n = await c.get_count(); // request-response
println(n);
}Lambda actors
Lightweight, inline actors for simple workers. They capture variables from the enclosing scope automatically.
let factor = 3;
let multiplier = spawn (x: Int) => {
println(x * factor);
};
multiplier <- 7; // prints 21Generators
Generator functions produce a sequence of values lazily using yield. Consume them with for loops or call .next() manually. Actors can expose streaming data with receive gen fn.
gen fn count_up() -> Int {
yield 1;
yield 2;
yield 3;
}
fn main() {
for x in count_up() {
println(x);
}
}actor Fibonacci {
let limit: Int;
receive gen fn sequence() -> Int {
var a = 0;
var b = 1;
while a < limit {
yield a;
let next = a + b;
a = b;
b = next;
}
}
}
fn main() {
let fib = spawn Fibonacci(limit: 100);
for await val in fib.sequence() {
println(val);
}
}Supervisors
Supervisors manage actor lifecycles and automatic restart. Strategies follow Erlang/OTP patterns: one_for_one, one_for_all, rest_for_one.
supervisor AppSupervisor {
child logger: Logger
restart(permanent);
child worker: WorkerActor
restart(transient)
budget(5, 60.seconds)
strategy(one_for_one);
}Wire types
Wire types define network-serializable data with stable field tags. They enforce forward and backward compatibility — field numbers must never be reused. The compiler generates _to_json, _from_json, _to_yaml, and _from_yaml functions for every wire type.
#[json(camelCase)]
#[yaml(snake_case)]
wire type UserEvent {
user_id: u64 @1;
name: String @2;
email: String @3 optional;
role: Role @4;
}
wire enum Role {
Admin;
Member;
Guest;
}
// Generated: UserEvent_to_json, UserEvent_from_json,
// UserEvent_to_yaml, UserEvent_from_yaml
let json = UserEvent_to_json(42, "Alice", "alice@example.com", Role::Member);
// → {"email":"alice@example.com","name":"Alice","role":1,"userId":42}
// Per-field key override:
wire type Event {
internal_ref: String @1 json("ref") yaml("ref");
}