Failure Semantics
Normative model for Result values and nominal Error classes
Status: the Result return model, ordinary Error subclasses, compiler lifting, and Promise restrictions are locked. The complete convenience-method set and declaration encoding are direction.
Result Model
A function that can complete with a recoverable error MUST return:
Result<A, E>A is the success value and E is an Error subtype or union of Error subtypes. A fallible async function MUST return:
Promise<Result<A, E>>Failure MUST be represented in the ordinary return value. VibeLang MUST NOT encode E as an erased throws annotation or a second Promise type parameter.
Error Classes
Any named class extending Error MUST be usable as a nominal recoverable error:
class NotFound extends Error {
constructor(readonly id: string) {
super(`Not found: ${id}`)
}
}Authors MUST NOT need a TaggedError("Name") factory, _tag declaration, or separate error-declaration syntax. The compiler MUST provide stable nominal identity, matching metadata, serialization evidence, and cross-realm transport metadata while preserving ordinary Error behavior.
Compiler Lifting
Inside a function inferred or declared to return Result<A, E>:
return valueMUST produce the success variantthrow errorMUST produce the error variant and exit the function- returning an existing compatible Result MUST preserve it without nesting
Authors MUST NOT need to write Result.ok(...) or Result.err(...). Those constructors MUST NOT be part of the ordinary VibeLang authoring API.
A function with no fallible path MUST continue to return its ordinary success type rather than an unnecessary Result.
A .vibe function with a reachable recoverable Error exit MUST return or infer a Result. An explicit non-Result return annotation on such a function MUST be a compile error. Reflect.panic(...) is the separate defect exit and does not change the return type.
Inference and Public Contracts
When a function body has no explicit return annotation, the compiler MUST infer A from successful returns and E from:
- reachable
throw errorstatements - propagated
Result.unwrap()calls - existing Results returned from the function
- foreign exception or rejection boundaries
Public, abstract, ambient, and declaration-only contracts MUST express fallibility directly with Result<A, E> or Promise<Result<A, E>>.
VibeLang MUST NOT add a throws clause, !T marker, prefix try expression, postfix catch expression, or special panic-catch grammar.
Propagation
Result.unwrap() MUST yield the success value or propagate the error variant from the enclosing Result-returning function:
function displayName(id: string) {
const user = findUser(id).unwrap()
return user.displayName
}The compiler MUST include the unwrapped Result's error type in the enclosing function's inferred E. The emitted error path MUST return the enclosing error variant rather than throw a recoverable JavaScript exception.
At an explicit non-Result boundary, unsafe extraction MAY throw at runtime and MUST be visually distinct in API naming or diagnostics.
Matching and Transformation
Result values MUST provide compiler-aware operations equivalent in purpose to:
isOk isError match map mapError andThen recover
tap tapError unwrap unwrapOr allmatch MUST require both success and error branches. Transformations MUST preserve or correctly combine the Result error type.
A Result MUST be treated as a must-use value. A source program MUST NOT silently discard a Result without returning, matching, transforming, inspecting, or unwrapping it.
Error Prototype
Every Error instance MUST provide compiler-aware quality-of-life methods:
is matches match matchPartial rootCauseFor a statically known error union, error.match({...}) MUST be exhaustive and MUST narrow each handler argument to its nominal Error subclass:
error.match({
NotFound: (error) => User.guest(error.id),
Timeout: (error) => retryAfter(error.ms),
})Handler selection MUST use compiler-stable nominal identity, not a forgeable user _tag or minifier-sensitive constructor name in compiled artifacts.
Foreign Exceptions
Calling TypeScript, JavaScript, or another foreign implementation that may throw or reject MUST produce a Result in VibeLang. An unknown thrown value MUST be wrapped in UnhandledException, which is an Error subclass retaining the original cause.
A trusted adapter MAY map the boundary to a more precise Error subtype. JSDoc and declaration metadata MAY provide that trusted contract, but MUST NOT cause an actual foreign throw to escape the VibeLang Result boundary.
Reflect.panic is reserved for compiler/runtime invariants and MUST bypass recoverable Result handling.
Promise Semantics
Authored .vibe code MUST consume Promise instances only through await.
Calls to Promise instance .then(), .catch(), or .finally() MUST be compile errors. Imported TypeScript and JavaScript modules retain ordinary Promise behavior internally.
Awaiting Promise<Result<A, E>> MUST produce Result<A, E>. await MUST NOT automatically unwrap, recover, or discard the Result.
Promise rejection at a VibeLang boundary MUST become an error Result, normally UnhandledException, rather than an untyped rejection escaping the declared return contract.
JavaScript try/catch
Ordinary statement-form JavaScript try/catch remains valid where JavaScript interoperability requires it. It is not the typed recovery surface for VibeLang Results, and its presence MUST NOT change a function's Result contract implicitly.
Cleanup
VibeLang MUST support defer and errdefer.
deferruns when the containing scope exits.errdeferruns when a Result-returning scope exits through its error variant.
Ordering, async finalization, cancellation, and cleanup-error composition require further normative specification.