Error Handling
This chapter specifies error handling semantics in Clef, including the relationship between compile-time error propagation through tooling and runtime error handling in compiled applications.
Overview
Clef takes a fundamentally different approach to error handling than managed F#:
| Aspect | Managed F# | Clef |
|---|---|---|
| Optional values | option<'T> with null representation for None | voption<'T> (ValueOption) with no null |
| Failure handling | Exceptions (raise, try/with) | Result<'T, 'E> with explicit propagation |
| Null values | Permitted for reference types | Not permitted; null-free by construction |
| Runtime type errors | InvalidCastException, NullReferenceException | Cannot occur; prevented by type system |
Design Principle: Errors are values, not control flow. The type system encodes fallibility explicitly, making error handling visible and verifiable at compile time.
The Dual Nature of Error Handling
Clef error handling operates at two distinct levels:
- Application Runtime: How compiled Fidelity framework applications handle errors during execution
- Tooling Integration: How Clef Compiler Service (CCS) propagates errors through the Language Server Protocol to editors like Ionide
These two domains have different requirements and constraints, but must remain coherent.
Application Runtime Error Handling
The Result Type
The Result<'T, 'E> type is the primary mechanism for representing operations that may fail:
type Result<'T, 'E> =
| Ok of 'T
| Error of 'EOperations that can fail return Result values rather than raising exceptions:
// Managed F# style (NOT used in Clef applications)
let divide x y =
if y = 0 then raise (DivideByZeroException())
else x / y
// Clef style
let divide x y : Result<int, DivisionError> =
if y = 0 then Error DivisionByZero
else Ok (x / y)Standard Error Types
Clef defines standard error types for common failure modes:
type ArithmeticError =
| DivisionByZero
| Overflow
| Underflow
type IndexError =
| OutOfBounds of index: int * length: int
type ParseError =
| InvalidFormat of input: string * expected: string
| UnexpectedEnd
type IOError =
| NotFound of path: string
| PermissionDenied of path: string
| DeviceError of code: intNote: The exact set of standard error types is subject to further specification as the standard library matures.
The voption Type
For optional values where absence is not an error, voption<'T> (ValueOption) provides a null-free representation:
type voption<'T> =
| ValueSome of 'T
| ValueNoneThe voption<'T> type has an explicit discriminator with no null representation.
// Looking up a value that may not exist
let tryFind key (map: Map<'K, 'V>) : voption<'V> =
match Map.tryFind key map with
| ValueSome v -> ValueSome v
| ValueNone -> ValueNoneResult Propagation
Clef provides computation expression syntax for Result propagation:
let result {
let! x = tryParseInt "42"
let! y = tryParseInt "17"
return x + y
}This is equivalent to explicit binding:
match tryParseInt "42" with
| Error e -> Error e
| Ok x ->
match tryParseInt "17" with
| Error e -> Error e
| Ok y -> Ok (x + y)Try/With Syntax Compatibility
Clef preserves try/with/finally syntax for compatibility with standard F# tooling:
try
riskyOperation()
with
| :? SomeException as e -> handleError eHowever, the semantics differ:
- In Clef,
try/withmay be used for effect handling (delimited continuations) rather than exception catching - The exact semantics depend on the effect system specification (see Effects)
- Code using
try/withfor exception handling must be migrated to Result-based patterns for native compilation
Tooling Note: Ionide and other editors will parse
try/withexpressions normally. CCS may emit warnings when exception-style patterns are detected, guiding migration to Result-based alternatives.
Null-Freedom
Clef is null-free by construction. The following are compile-time errors:
let x : string = null // ERROR: null literal not available
let y = Unchecked.defaultof<_> // ERROR for reference types in most contextsThis eliminates entire classes of runtime errors:
| Managed F# Runtime Error | Clef |
|---|---|
NullReferenceException | Cannot occur |
InvalidCastException | Cannot occur (static typing) |
ArrayTypeMismatchException | Cannot occur (no covariant arrays) |
Tooling Integration
CCS Error Propagation
Clef Compiler Service (CCS) must propagate errors through the tooling stack in a format compatible with existing F# tooling infrastructure.
Diagnostic Format
CCS diagnostics follow the F# compiler diagnostic format:
filepath(line,col)-(line,col): severity code: messageFor example:
src/Main.fs(12,5)-(12,15): error FS8100: Cannot use 'null' in Clef; use 'ValueNone' for optional valuesError Codes
CCS uses error codes in the FS8xxx range to distinguish native-specific diagnostics:
| Range | Category |
|---|---|
| FS8000-FS8099 | Type system (null-freedom, access kinds) |
| FS8100-FS8199 | Memory management (regions, lifetimes) |
| FS8200-FS8299 | Platform bindings |
| FS8300-FS8399 | Effect system |
| FS8400-FS8499 | Code generation |
LSP Compatibility
CCS implements the Language Server Protocol for editor integration. Key considerations:
- Diagnostic Publishing: Errors are published via
textDocument/publishDiagnosticsin standard LSP format - Code Actions: Quick fixes (e.g., “Replace null with ValueNone”) are provided via
textDocument/codeAction - Hover Information: Type information displays native types
Ionide Integration Model
Ionide currently supports multiple F# compilation targets:
| Target | Integration Point |
|---|---|
| .NET | FSharp.Compiler.Service |
| Fable | Fable.Compiler (JavaScript output) |
| WebSharper | WebSharper.Compiler |
Clef follows this model:
Ionide ←→ LSP ←→ CCS ←→ Firefly Compiler ←→ MLIR/LLVMExtension Points
CCS provides extension points for Ionide integration:
- Project Recognition:
.fidprojfiles identify Clef projects - Target Selection: Ionide can route to CCS when native compilation is detected
- Shared Parsing: Syntax parsing uses standard F# lexer/parser for compatibility
- Semantic Divergence: Type checking and code generation use native semantics
Compatibility Considerations
To maintain compatibility with the broader F# ecosystem:
- Syntax Compatibility: Clef code parses as valid F# syntax
- Type Notation: Types are expressed using standard F# type notation
- Error Format: Diagnostics follow F# compiler conventions
- Incremental Adoption: Projects can mix managed and native targets during migration
Editor Experience
The design-time experience for Clef should be consistent with managed F#:
| Feature | Behavior |
|---|---|
| Syntax highlighting | Standard F# highlighting |
| Error underlining | Red squiggles for errors, yellow for warnings |
| Hover types | Shows native type representations |
| Autocomplete | Suggests native library members |
| Go to definition | Navigates to native library source |
| Quick fixes | Offers native-appropriate fixes |
Error Handling Patterns
Railway-Oriented Programming
Clef encourages railway-oriented programming with Result:
let processOrder orderId =
orderId
|> validateOrderId
|> Result.bind fetchOrder
|> Result.bind validateInventory
|> Result.bind processPayment
|> Result.bind shipOrderError Aggregation
For operations that may produce multiple errors:
type ValidationErrors = ValidationErrors of ValidationError list
let validateAll validators input =
validators
|> List.map (fun v -> v input)
|> List.fold aggregateErrors (Ok input)Partial Success
For operations where partial results are meaningful:
type PartialResult<'T, 'E> =
| Complete of 'T
| Partial of 'T * 'E list
| Failed of 'E listGrammar
result-type := Result < type , type >
voption-type := voption < type >
result-expr :=
Ok expr
Error expr
voption-expr :=
ValueSome expr
ValueNone
result-bind := let! pattern = expr in expr
result-return := return exprDiagnostics
| Code | Severity | Message |
|---|---|---|
| FS8100 | Error | Cannot use ’null’ in Clef; use ‘ValueNone’ for optional values |
| FS8101 | Error | Cannot use ’null’ in Clef; all values must be initialized |
| FS8102 | Warning | Exception-style error handling detected; consider Result-based pattern |
| FS8103 | Error | Type does not support ’null’ in Clef |
| FS8104 | Warning | Unchecked.defaultof<‘T> produces undefined behavior for reference types |
Areas Requiring Further Specification
The following areas require additional design work:
- Effect System Integration: How Result interacts with algebraic effects and delimited continuations
- Async/Concurrent Errors: Error propagation in concurrent and asynchronous contexts
- Interop Boundaries: Error translation at FFI boundaries with C libraries
- Panic vs. Error: Distinction between recoverable errors (Result) and unrecoverable panics
- Stack Traces: Diagnostic information for debugging without managed exception infrastructure
- Tooling PR Strategy: Concrete changes needed for Ionide/FSAC to support CCS
See Also
- Types and Type Constraints - Type system fundamentals
- Special Attributes and Types - Result and voption definitions
- Platform Bindings - Error handling at platform boundaries