Error Handling
Targo follows Go-style error handling. Ordinary failures are returned as values, not thrown as exceptions.
Core Rules
- Prefer
[T, error | null]for operations that can fail. - Use explicit error checks at call sites.
- Reserve
panic()andrecover()for exceptional or unrecoverable situations, not ordinary failures. - Prefer wrapping or returning errors explicitly rather than relying on exception-like control flow.
Common Error-Return Pattern
typescript
const [data, err] = loadConfig(path);
if (err != null) {
return [zero<Result>(), err];
}
return [data, null];Ignoring Values Intentionally
When only one side of the tuple matters, keep the ignored value obvious:
typescript
const [_data, err] = loadConfig(path);
if (err != null) {
return err;
}
return null;typescript
const [value, _err] = maybeReadCached(path);
return value;If the project has a stronger local convention, follow that convention. Otherwise prefer explicit placeholder names like _err or _value.
Presence and Comma-Ok Style Results
Not every tuple return is an error return. Some APIs return a value plus a presence flag:
typescript
const [value, ok] = cache.Get(key);
if (!ok) {
return [zero<Item>(), new Error("missing item")];
}
return [value, null];Treat these as Go-style multi-return APIs, not as JavaScript destructuring sugar.
Error Construction
typescript
import { New } from "errors";
return [zero<Result>(), New("missing config")];typescript
import { Errorf } from "fmt";
return [zero<Result>(), Errorf("invalid config %s: %v", name, err)];Practical Guidance
- Destructure close to the call site when the returned values are used immediately.
- If the result needs to be passed through multiple branches, introduce named locals instead of repeatedly re-calling the function.
- Prefer explicit branch flow over compressing everything into nested conditional expressions.
Common Mistakes
- Treating
[T, error | null]as if it were a Promise result. - Translating
try/catchdirectly instead of redesigning the function contract. - Searching the codebase for "special syntax" before trying ordinary tuple destructuring.
- Hiding ignored values so aggressively that the control flow becomes less clear.
- Mixing error-return tuples with exception-style control flow in the same path.
- Using
panic()for everyday business errors.
Recommended Patterns
Ok-chain for sequential fallible operations
ok() is a builtin that wraps a [T, error | null] result into a chainable pipeline, avoiding nested if-err checks:
typescript
// Instead of nested if-err:
function loadNumber(path: string): [int, error | null] {
return ok(readLine(path))
.flatMap<int>(s => Atoi(s))
.unwrap();
}Chain methods (non-terminal): map, flatMap, mapErr, orElse, tap, tapErr Terminal methods (end the chain): unwrap, must, orDefault, orZero, fold
typescript
// mapErr to wrap errors with context
ok(loadConfig(path))
.mapErr(err => Errorf("config failed: %v", err))
.unwrap();
// orDefault to provide a fallback value
const settings = ok(loadConfig(path))
.map(config => parseSettings(config))
.orDefault(defaultSettings);Error wrapping with context
typescript
import { Errorf } from "fmt";
const [user, err] = findUser(id);
if (err != null) {
return [zero<Response>(), Errorf("findUser(%d): %v", id, err)];
}