Enums
Enums define a type with a fixed set of named variants, each optionally carrying associated data.
Enum declaration
enum Color {
Red,
Green,
Blue
}
enum Shape {
Circle(float),
Rectangle(float, float)
}
Variants without data are simple tags. Variants with data carry positional fields specified in parentheses.
Enum construction
Variants are constructed using dot syntax on the enum name:
const c = Color.Red
const s = Shape.Circle(5.0)
const r = Shape.Rectangle(3.0, 4.0)
Pattern matching on enums
Enum variants are matched using EnumName.Variant(binding) patterns in
match expressions:
match s {
Shape.Circle(radius) -> { log("circle r=${radius}") }
Shape.Rectangle(w, h) -> { log("rect ${w}x${h}") }
}
A match on an enum must be exhaustive: a missing variant is a hard
error, not a warning. Add the missing arm or end with a wildcard
_ -> { … } arm to opt out. if/elif/else chains stay intentionally
partial; opt into exhaustiveness by ending the chain with
unreachable("…").
Bare variant patterns
A variant of an enum declared in the same module may also be matched in
bare, unqualified form. The built-in Result variants Ok and Err are
always available this way:
match s {
Circle(radius) -> { log("circle r=${radius}") }
Rectangle(w, h) -> { log("rect ${w}x${h}") }
}
Two restrictions apply, both hard errors:
- If more than one visible enum declares the same variant name, the bare form is ambiguous and must be qualified.
- A variant of an imported enum must always be qualified as
EnumName.Variant(…). Bare variant patterns resolve against the compiling module's own enum declarations only; folding an imported module's whole variant surface into that namespace would capture ordinary identifiers, so the qualified form is required instead.
A call-shaped pattern whose head is not a visible variant is an ordinary expression-equality pattern: the expression is evaluated and compared to the match subject.
Built-in result enum
Harn provides a built-in generic Result<T, E> enum with two variants:
Result.Ok(value)-- represents a successful resultResult.Err(error)-- represents an error
Shorthand constructor functions Ok(value) and Err(value) are available
as builtins, equivalent to Result.Ok(value) and Result.Err(value).
const ok = Ok(42)
const err = Err("something failed")
const typed_ok: Result<int, string> = ok
// Equivalent long form:
const ok2 = Result.Ok(42)
const err2 = Result.Err("oops")
Result helper functions
| Function | Description |
|---|---|
is_ok(r) | Returns true if r is Result.Ok |
is_err(r) | Returns true if r is Result.Err |
unwrap(r) | Returns the Ok value, throws if r is Err |
unwrap_or(r, default) | Returns the Ok value, or default if r is Err |
unwrap_err(r) | Returns the Err value, throws if r is Ok |
The ? operator (result propagation)
The postfix ? operator unwraps a Result.Ok value or propagates a
Result.Err from the current function. It is a postfix operator with the
same precedence as ., [], and ().
fn divide(a, b) {
if b == 0 {
return Err("division by zero")
}
return Ok(a / b)
}
fn compute(x) {
const result = divide(x, 2)? // unwraps Ok, or returns Err early
return Ok(result + 10)
}
const r1 = compute(20) // Result.Ok(20)
const r2 = compute(0) // would propagate Err from divide
The ? operator requires its operand to be a Result value. Applying ?
to a non-Result value produces a type error at runtime.
Disambiguation: when the parser sees expr?, it distinguishes between the
postfix ? (Result propagation) and the ternary ? : operator by checking
whether the token following ? could start a ternary branch expression.
For legacy expr?[...], optional subscript is used unless the ? is followed
by a valid branch expression and a top-level ternary :. The canonical
optional subscript spelling is expr?.[...]. For example,
repo ? ["--repo", repo] : [] parses as a ternary without extra parentheses.
Pattern matching on result
match result {
Result.Ok(val) -> { log("success: ${val}") }
Result.Err(err) -> { log("error: ${err}") }
}
Try-expression
The try keyword used without a catch block acts as a try-expression.
It evaluates the body and wraps the result in a Result:
- If the body succeeds, returns
Result.Ok(value). - If the body succeeds with an existing
Result, returns thatResultunchanged instead of nesting it asResult.Ok(Result.Ok(...)). - If the body throws an error, returns
Result.Err(error).
const result = try { json_parse(raw_input) }
// result is Result.Ok(parsed_data) or
// Result.Err({error: "json_parse_error", kind, message, line, column})
const checked = try { schema_check(data, schema) }
// checked is schema_check's Result directly, not Result.Ok(Result.Ok(...))
The try-expression is the complement of the ? operator: try enters
Result-land by catching errors, while ? exits Result-land by propagating
errors. Together they form a complete error-handling pipeline:
fn safe_divide(a, b) {
const result = try { a / b }
return result
}
fn compute(x) {
const val = safe_divide(x, 2)? // unwrap Ok or propagate Err
return Ok(val + 10)
}
No catch or finally block is needed for the Result-wrapping form. When
catch or finally follow try, the form is a handled try/catch
expression whose value is the try or catch body's tail value (see
try/catch/finally); only the bare try { ... } form wraps
in Result.
Result in pipelines
The ? operator works naturally in pipelines:
fn fetch_and_parse(url) {
const response = http_get(url)?
const data = json_parse(response)?
return Ok(data)
}