Expression-Oriented Control Flow
Use conditions, switches, blocks, and loops as values
VibeLang makes common control-flow forms usable as expressions. New syntax follows Zig where TypeScript does not already provide a suitable form.
If Expressions
An if can produce a value:
const access = if (user.isAdmin) "all" else "limited"Each reachable branch must produce a compatible type. A block returns its final expression:
const price = if (customer.isMember) {
const discount = calculateDiscount(customer)
subtotal * (1 - discount)
} else {
subtotal
}Statement-form TypeScript if remains valid.
Switch Expressions
A switch can return a value and can be checked for exhaustiveness:
const message = switch (state.kind) {
case "loading":
"Loading…"
case "ready":
state.value.title
case "failed":
state.error.message
}The clauses use ordinary TypeScript syntax. In expression position, the selected case's final expression becomes the switch value, so expression switches do not fall through. Statement-position switches keep TypeScript's existing break and fallthrough behavior.
Blocks and Labeled Breaks
A labeled block can compute a value from an early exit:
const result = parse: {
if (input.length === 0) break :parse ParseResult.empty()
if (!isValid(input)) break :parse ParseResult.invalid(input)
break :parse ParseResult.valid(input)
}The label makes the destination explicit and allows break to carry a value.
Loop Expressions
Loops can produce a value when a labeled break succeeds:
const firstEven = search: for (const value of values) {
if (value % 2 === 0) break :search value
} else -1The else expression runs when the loop completes without breaking to its value label.
Runtime-sized loops remain ordinary runtime loops. Comptime-known loops may be evaluated or unrolled when used from comptime code.
Throw Statements
VibeLang keeps JavaScript statement-form throw; it does not add a throw expression:
function readPort(config: Config): Result<number, InvalidConfig> {
return if (config.port !== undefined) {
config.port
} else {
throw new InvalidConfig("port")
}
}Inside a Result-returning function, the compiler lowers the Error to the Result error variant.
Declarations in Conditions
VibeLang plans to adopt declarations in conditionals from TC39 where appropriate:
if (const user = cache.get(id); user !== null) {
render(user)
}The binding stays scoped to the conditional construct.
defer and errdefer
Cleanup belongs next to acquisition:
function write(
path: string,
contents: string,
): Result<void, FileError> {
const fs = FileSystem.context()
const file = fs.open(path, "write").unwrap()
defer file.close()
file.write(contents).unwrap()
}defer runs when the current scope exits, regardless of the path. errdefer runs only when the scope exits with a Result error:
const database = Database.context()
const transaction = database.begin().unwrap()
errdefer transaction.rollback()
transaction.commit().unwrap()The exact interaction with defects, async suspension, and disposal protocols will be finalized in the normative specification.