Errors and Optionals
This file is part of the Nocter language specification. The specification entry point is README.md.
Fallible Types
Adopted: failure is represented with fallible types, not exceptions.
func open(path: &str): File! {
if failed {
return Error.new("std.io.not_found", "file not found")
}
return file
}
T! is a fallible type. It means the expression or function succeeds with T or fails with the built-in error payload.
T! = fallible T with built-in error
The failure type is not written at each call site. All fallible values use the same failure payload type, error.
Initial payload fields:
code: &str
message: &str
Rules:
erroris compiler built-in type-position syntax, likestr,i32, andnever.erroris not looked up through imports and cannot be redefined as a type declaration.- The spelling
errormay still be used as an ordinary value binding name. For example,catch errorbinds a local value namederror. T!always means successTor failureerror.Errormay be provided bystd/preludeas a normal alias or wrapper forerror.ErrorCodeis a standard-library&stralias, not a compiler-reserved name.ErrorCodeis intentionally open. Standard-library, user, and package code may introduce dotted string codes such as"std.io.not_found","app.config.missing_key", or"package.module.reason".- Standard-library constructors such as
Error.new("std.io.not_found", "...")translate theErrorCodestring into the built-in payload's primitive code representation. - The compiler must not special-case ordinary names such as
Error,ErrorCode,IOError, orResult. - Domain detail is represented in the
errorpayload and standard-library helper APIs, especially through classification code andmessage, not by writing a different failure type in the signature. error.codeanderror.messageare the initial direct user-facing fields for reporting.error.codeis an open dotted string code such as"std.io.not_found"or"app.config.missing_key".error.messageis a human-readable diagnostic message string.- The built-in
errorpayload is copyable, non-owning, and carries borrow-like provenance from its&strfields. Returning or storing anerrorfollows the same escape rules as other aggregates containing borrow-like values. - The ABI layout of
erroris specified in ABI and Layout. error!is not a valid function return type. In a fallible function,return error_valuemeans failure, soerrorcannot be used as the success type without ambiguity. This rule is checked after type aliases and through optional success layers such aserror?!.
Inside a function returning T!, a compatible function body result or return value returns the success value unless the value has type error. return error_value returns the failure value.
func write(file: &+File, text: &str): void! {
if failed {
return Error.new("std.io.broken_pipe", "broken pipe")
}
return
}
Adopted: postfix ? unwraps fallible and optional values for propagation.
let file = File.open(path)?
For T!, expr? evaluates to the success value when expr succeeds. On failure, the current function fails with the same error payload.
For T?, expr? evaluates to the present value when expr is present. On none, the current function returns none through its optional return layer.
Example:
let file = File.open(path)?
This binds file to the successful File value. If File.open(path) fails, the current function fails with that error as if return error_value had been executed.
Rules:
- Postfix
?is not an exception mechanism. - Postfix
?does not perform stack unwinding. - Postfix
?onT!can be used only inside a fallible function. - Postfix
?onT?can be used only when the current function's return layer can carrynone. - Postfix
?does not convertnoneintoerror. - Postfix
?does not converterrorintonone. - Scope-end cleanup and
dropbehavior still run as they would for an explicitreturn. - Error conversion is not needed for propagation because every fallible value fails with
error. throwis not part of the language.
Adopted: postfix ! forcefully unwraps fallible and optional values.
let file = File.open(path)!
let user = maybe_user!
Rules:
- For
T!,expr!evaluates to the success value whenexprsucceeds. - For
T?,expr!evaluates to the present value whenexpris present. - If
expr!sees failure ornone, execution terminates immediately through a trap-like non-recoverable path. expr!does not returnerrorornoneto the caller.expr!has result typeT.expr!is intended for tests, prototypes, and truly unrecoverable assumptions.- Normal code should prefer
?,catch, orotherwise. expr!is not stack unwinding.
Recoverable Failure and Non-Recoverable Termination
Adopted: fallible return, trap, and abort are distinct mechanisms.
return error_value = recoverable failure through T!
trap = non-recoverable program defect or violated runtime check
abort = immediate process termination
Rules:
- In a function returning
T!,return expris a failure return whenexprhas typeerror. - In a function returning
T!,return expris a success return whenexpris assignable toT. Tmust not beerror.- Fallible failure return follows normal
returncleanup for scopes it leaves. traphas typenever.trapis used for program defects, compiler-inserted safety checks, and impossible paths.- Out-of-bounds indexing, integer overflow in normal arithmetic, division by zero, invalid live
boolvalues, invalid enum tags, and explicit unreachable execution all trap. trapdoes not unwind the stack.aborthas typenever.abortterminates the process immediately and does not run Nocter cleanup.panicis not a language feature in v0.- Stack unwinding is not part of v0.
- Build modes must not disable these trap checks; see Safety Checks and Build Modes.
Adopted: catch handles the failure side of a fallible expression.
let file = File.open(path) catch error {
return Error.new("std.io.open_failed", error.message)
}
expr catch error { ... } means:
- Evaluate
expr. - If
exprsucceeds, the wholecatchexpression evaluates to the success value. - If
exprfails, bind the failure value to the catch binding and execute thecatchblock.
Rules:
catchapplies only to fallible values of typeT!.catchdoes not apply to optional valuesT?.- The catch binding has type
error. - The binding name after
catchis an ordinary local name.catch erroris conventional, butcatch erris also valid. - The catch block is evaluated only on failure.
- The catch block must not fall through in the initial design.
- The catch block must leave the current control path with
return,break,continue, a call returningnever, or another terminating construct. - The catch block has no trailing expression result.
catchis not exception handling.catchdoes not perform stack unwinding.catchruns the same scope-end cleanup that the explicit terminating control flow would run.- If a
catchblock terminates by calling aneverfunction, cleanup behavior is determined by thatneverfunction. The compiler does not add implicit unwinding. - The
catchclause belongs to the immediately preceding fallible expression. It is not a general handler after arbitrary expressions.
Postfix ? propagates the original failure.
catch is used for explicit local handling or error replacement.
func read_all(
allocator: &+Allocator,
path: &str,
): String! {
var file = File.open(path) catch error {
return Error.new("std.io.open_failed", error.message)
}
var text = file.read_to_string(allocator) catch error {
return Error.new("std.io.read_failed", error.message)
}
return move text
}
map_error is not part of the initial language design. It may be considered later as an ordinary standard-library API, but the compiler does not special-case that name.
Fallible values are not pattern matched in the initial design.
Rules:
matchdoes not apply toT!.if expr is Patterndoes not apply toT!.is ok(...)and failure patterns are not part of the language.okis not a reserved keyword.- Fallible values are handled with postfix
?andcatch.
Optional Types
Adopted: optional values use the type syntax T?.
T? = optional T
An optional value is either present with a T value or absent.
Inside a function returning T?, a compatible function body result or return value returns a present value and return none returns absence.
func lookup(name: &str): &str? {
if missing {
return none
}
return value
}
Rules:
T?is not spelled as a specialOption<T>type.noneis the optional absent literal.- A compatible function body result or
return valuein aT?function returns the present value. return nonein aT?function returns absence.- Postfix
?onT?propagatesnonethrough the current optional return layer. matchdoes not apply toT?in the initial design.if expr is Patterndoes not apply toT?in the initial design.some(value)is not part of the initial language.someis not a reserved keyword.
Composing Optionals and Fallible Types
Adopted: optional and fallible type constructors may be composed explicitly.
Preferred source spelling:
T?! = fallible optional success
T?! means the computation can fail with error. If it succeeds, the success value is optional: present T or none.
Rules:
T!means a fallible success value.T?means an optional value.- Prefer
T?!in official style. expr?onT?!unwraps only the fallible layer and producesT?.- Applying
?again to thatT?propagatesnonethrough the current optional return layer. - In a function returning
T?!, a compatible function body result orreturn valuereturns success with a presentT. - In a function returning
T?!,return nonereturns success with absence. - In a function returning
T?!,return error_valuereturns failure witherror. Tmust not beerror. Use a wrapper type if anerrorpayload must be carried as successful optional data.- Other mixed forms must use parentheses in v0.
(T!)?means an optional fallible value.
Example:
func env(name: &str): &str?! {
if missing {
return none
}
if invalid_utf8 {
return Error.new("std.process.invalid_encoding", "environment value is not UTF-8")
}
return value
}
Using a fallible optional:
let maybe_config = load_config()?
let config = maybe_config otherwise { return none }
use(config)
Optional Propagation
Adopted: postfix ? propagates optional absence.
When expr has type T?, expr? unwraps the present T. If expr is none, the current function returns none through its optional return layer.
func require_home(): &str? {
let home = lookup("HOME") otherwise { return none }
return home
}
Rules:
- Postfix
?onT?is valid when the current function's return type can carrynone, such asU?or(U?)!. - In a function returning
(U?)!,noneis returned as successful absence, not as failure. - Postfix
?onT?is invalid in a function whose current return layer cannot carrynone. - Absence defaulting and absence-side early exit use
otherwise. otherwisedoes not propagate absence by itself; it selects a fallback block when the optional value isnone.
Optional Otherwise Expressions
Adopted: optional fallback uses otherwise.
let home = lookup("HOME") otherwise { "/tmp" }
let config = find_config(path) otherwise {
return Error.new("app.config.missing", path)
}
load(config)
Rules:
expr otherwise { body }applies only whenexprhas typeT?.- If
expris present, the result is the containedT. - If
exprisnone, the fallback body is evaluated. - The fallback body must produce
T, or it may terminate the current control path withreturn, loop-localbreak/continue, ornever. - The fallback body follows the common body rule: statements first, then an optional result expression.
- The fallback body is evaluated only when needed.
otherwiseis an expression, not a declaration form.otherwisedoes not usesome/nonepatterns.- Evaluating
exprand the fallback body follows normal ownership rules. ??,let ... else, andvar ... elseare not Nocter syntax.
Chained fallback is written by nesting otherwise in the fallback body:
let port = env_int("PORT") otherwise {
config.default_port otherwise { 8080 }
}
Optional and Fallible Pattern Branching
Adopted: is is reserved for enum variants only.
Rules:
if expr is Pattern { ... }applies only to enum values, and the pattern must be written asEnum.variant.T?values do not supportis none,is Type, oris Type(name).T!values do not supportis Error(name),is Type, oris Type(name).T?has noSome/Noneenum variants. The absence value is the keywordnone, usable in expressions such asreturn none.T!has no success/failure enum variants. Failure is the fallible return channel carrying anerrorvalue.
Optional Loops
Rules:
while let,while var,if let, andif varare not Nocter syntax.- Optional values are not automatically iterable.
- Collection iteration helpers may return
T?, but v0 does not introduce a dedicated optional loop syntax.