Skip to content
LogoLogo

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 value MUST produce the success variant
  • throw error MUST 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 error statements
  • 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 all

match 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  rootCause

For 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.

  • defer runs when the containing scope exits.
  • errdefer runs when a Result-returning scope exits through its error variant.

Ordering, async finalization, cancellation, and cleanup-error composition require further normative specification.