| name | design_patterns-typed_error_sentinels |
| description | Use when creating error sentinels, defining package-specific error types, checking errors by type rather than value, or encountering Typed, IsTyped, MakeTypedSentinel, WrapWithType, or pkgErrDisamb in code.
|
| triggers | ["error sentinel","IsTyped","MakeTypedSentinel","WrapWithType","pkgErrDisamb","typed error","NewWithType","error type checking"] |
Typed Error Sentinels
Overview
Dodder uses phantom type parameters to create error sentinels with compile-time
namespace isolation. Each package defines its own disambiguation type (an empty
struct), making it impossible for errors from different packages to accidentally
match even if they share the same message string.
Core Types
type Typed[DISAMB any] interface {
error
GetErrorType() DISAMB
}
The DISAMB type parameter is a phantom type — it exists only at compile time
for type discrimination. It is never instantiated at runtime.
Defining Package Errors
Step 1: Declare the Disambiguation Type
type (
pkgErrDisamb struct{}
pkgError = errors.Typed[pkgErrDisamb]
)
Step 2: Create Sentinels
var (
ErrSomethingFailed = errors.NewWithType[pkgErrDisamb]("something failed")
ErrNotReady = errors.NewWithType[pkgErrDisamb]("not ready")
)
Or use the factory that returns both sentinel and checker:
var ErrBadInput, IsBadInput = errors.MakeTypedSentinel[pkgErrDisamb]("bad input")
Step 3: Export a Checker
func IsMyPackageError(err error) bool {
return errors.IsTyped[pkgErrDisamb](err)
}
Checking Errors
By package type (any error from this package)
if errors.IsTyped[pkgErrDisamb](err) {
}
By specific sentinel (standard Go pattern)
switch err {
case ErrSomethingFailed:
case ErrNotReady:
}
Wrapping Errors with Type
Preserve package typing when wrapping an existing error:
func wrapAsPkgError(err error) pkgError {
return errors.WrapWithType[pkgErrDisamb](err)
}
The wrapped error maintains Unwrap() support for standard error chain
traversal.
Built-in Sentinels
Dodder provides common sentinels in alfa/errors/:
| Sentinel | Checker | Purpose |
|---|
errStopIteration | IsStopIteration(err) | Early termination of iteration |
ErrNotFound | errors.Is(err, ErrNotFound{}) | Value not found (carries .Value field) |
How It Differs from Standard Go
| Standard Go | Typed Sentinels |
|---|
var ErrFoo = errors.New("foo") | var ErrFoo = errors.NewWithType[myDisamb]("foo") |
errors.Is(err, ErrFoo) — value comparison | errors.IsTyped[myDisamb](err) — type comparison |
| Same message in two packages could collide | Phantom types provide compile-time isolation |
| No category checking | IsTyped checks any error from a package |
Common Mistakes
| Mistake | Correct Approach |
|---|
Reusing another package's pkgErrDisamb | Each package defines its own phantom type |
Using errors.Is for typed category checks | Use errors.IsTyped[T] for type-based checking |
Forgetting Unwrap() when wrapping | Use WrapWithType which provides Unwrap() automatically |
Exporting pkgErrDisamb | Keep it unexported — export checker functions instead |