Effect System Reference
Overview
Section titled “Overview”The effect system is a compile-time capability mechanism. Every function declares what side effects it may perform via ![effect, ...] annotations on its signature, and the compiler currently enforces these declarations based on direct usage of effectful operations (e.g., filesystem, printing, HTTP). A function that directly performs any operation requiring effect E must declare E; failing to do so is a compile error, not a warning. Call-graph–transitive enforcement based on callee signatures is planned but not yet implemented. This makes direct side effects explicit, auditable, and verifiable without runtime overhead.
Canonical Effects
Section titled “Canonical Effects”| Effect | Token | Grants Access To | Enforced Today | Examples |
|---|---|---|---|---|
| IO | io | Filesystem, console output, logging, decorators like @logExecution | Yes | print(), print.err(), fs.readFile(), fs.writeFile(), console.* |
| Network | net | HTTP, WebSocket, serve | Yes | http.get(), http.post(), websocket.*, serve |
| Model | model | AI library invocation (sfn/ai, post-1.0) | No (planned) | Intended for sfn/ai; callee-signature/transitive enforcement is not implemented today |
| GPU | gpu | Tensor and accelerator operations | No (parsed only) | Tensor ops, @gpu blocks, GPU memory |
| Random | rand | Random number generation | No (parsed only) | rand.* |
| Clock | clock | Time operations | Partial | sleep(), runtime.sleep() |
The tokens unsafe, read, and mut are also recognized by the parser for FFI and ownership annotations but are not part of the capability-surface set enforced by the effect checker today.
Syntax
Section titled “Syntax”Effect annotations appear after the parameter list and optional return type, before the function body.
// No effects declared — guaranteed purefn pure_fn(x: int) -> int { return x * 2;}
// Single effectfn log_value(x: int) ![io] { print("{{x}}");}
// Multiple effectsfn fetch_and_log(url: string) ![io, net] { let response = http.get(url); print(response.body);}
// Full signature with return type and multiple effectsfn fetch_order(url: string) -> string ![io, net, model] { // ... return "";}Effect tokens are comma-separated within ![...]. Order within the list is not significant.
Enforcement Rules
Section titled “Enforcement Rules”- A function that directly calls an operation requiring effect
Emust declareE. - Call-graph–transitive enforcement (where calling function
Amust declare every effect that calleeBdeclares) is planned but not yet implemented. The current checker enforces direct-usage rules only. - Test blocks follow the same rules as functions — a test that performs IO must declare
![io]. - Closures inherit the effect scope of their enclosing function; a closure defined inside an
![io]function may call IO operations without redeclaring the effect. - Missing effects are a compile error, not a warning. The build will not proceed.
- The compiler emits missing-effect diagnostics with textual hints describing how to update the function signature; automatic signature rewrite fix-its are planned tooling.
API Surface Per Effect
Section titled “API Surface Per Effect”io effect
Section titled “io effect”Required for all filesystem access, console output, structured logging, and IO-dependent decorators.
| API | Notes |
|---|---|
print(value) | Write to stdout |
print.err(value) | Write to stderr |
print.warn(value) | Write warning to stderr |
fs.readFile(path) | Read file contents |
fs.writeFile(path, content) | Write file contents |
fs.appendFile(path, content) | Append to a file |
fs.exists(path) | Check file existence |
fs.writeLines(path, lines) | Write an array of lines |
log.info(), log.warn(), log.error(), log.debug() | Structured logging via sfn/log |
console.info(), console.log(), console.error() | Legacy console aliases |
@logExecution | Decorator that emits IO |
net effect
Section titled “net effect”Required for all outbound network calls, WebSocket connections, and binding to a port.
| API | Notes |
|---|---|
http.get(url) | HTTP GET request |
http.post(url, body) | HTTP POST request |
websocket.connect(url) | Open a WebSocket connection |
websocket.send(conn, msg) | Send a message on a connection |
websocket.recv(conn) | Receive a message from a connection |
serve(handler) | Bind and serve an HTTP handler |
model effect
Section titled “model effect”Required for any function that calls a library function (such as one from the
planned sfn/ai capsule) whose signature declares ![model]. Today ![model]
acts as a capability gate for direct use of model-capable APIs; full
call-graph–transitive enforcement based on callee signatures is planned but
not yet implemented. The model, prompt, tool, and pipeline block
keywords have been removed from the language; the ![model] effect remains
as the capability gate.
| API | Notes |
|---|---|
Library functions carrying ![model] | Any function that calls into AI library code must declare ![model] |
sfn/ai capsule | Post-1.0 library providing model invocation, tool dispatch, and provider adapters |
// A library function that carries ![model] in its signaturefn ai_call(model_name: string, input: string) -> string ![model] { // implemented in sfn/ai capsule (post-1.0) return "";}
// Any caller must also declare ![model]fn summarize(text: string) -> string ![model] { return ai_call("summarizer", text);}clock effect
Section titled “clock effect”Partially enforced. Time-based suspension is enforced today; wall-clock reads are planned.
| API | Notes |
|---|---|
sleep(ms) | Suspend for ms milliseconds — enforced |
runtime.sleep(ms) | Alias for sleep — enforced |
| Wall-clock access | Planned — not yet enforced |
gpu effect (parsed, not yet enforced)
Section titled “gpu effect (parsed, not yet enforced)”Declared in signatures and parsed by the compiler but not yet checked at call sites.
| API | Notes |
|---|---|
| Tensor operations | Element-wise ops, matmul, reductions |
@gpu accelerator blocks | Blocks targeting GPU execution |
| GPU memory operations | Allocation and transfer |
rand effect (parsed, not yet enforced)
Section titled “rand effect (parsed, not yet enforced)”Declared in signatures and parsed by the compiler but not yet checked at call sites.
| API | Notes |
|---|---|
rand.int() | Random integer |
rand.float() | Random float in [0, 1) |
rand.choice(list) | Random element from a list |
rand.shuffle(list) | Shuffle a list in place |
Diagnostic Format
Section titled “Diagnostic Format”When a required effect is missing, the compiler emits an error with a textual hint:
error[effects.missing]: function `process` uses `![net]` operation but does not declare net --> src/main.sfn:12:5 | = hint: add `net` to the effect list of `process`Diagnostic fields:
| Field | Description |
|---|---|
| Error code | effects.missing — missing effect declaration |
| Calling function | The function whose signature is incomplete |
| Effect required | The specific effect token that must be added |
| Source location | File and approximate location of the diagnostic |
| Hint | Textual description of the required signature change (automatic rewrite fix-its are planned) |
Effect Minimization
Section titled “Effect Minimization”Declare the narrowest set of effects possible. Functions that mix pure computation with effectful operations are harder to test and reason about. The recommended pattern is to push effects toward the boundary of the program (entry points, request handlers, CLI commands) and keep domain logic pure.
// Preferred: pure logic separated from IOfn compute_total(items: Item[]) -> float { return array_reduce(items, 0.0, fn(acc: float, i: Item) -> float { return acc + i.price; });}
fn print_total(items: Item[]) ![io] { let total = compute_total(items); print("{{total}}");}Pure functions with no effect annotations:
- Are eligible for memoization and inlining by the optimizer.
- Can be called from any context, including tests, without capability grants.
- Signal clearly to readers that no side effects occur.
Future: Hierarchical Effects
Section titled “Future: Hierarchical Effects”This feature is planned and not yet active.
In a future release, effects will compose hierarchically. The top-level tokens (io, net, etc.) will become namespaces for fine-grained sub-effects:
| Planned Sub-effect | Parent | Meaning |
|---|---|---|
io.fs.read | io | Read-only filesystem access |
io.fs.write | io | Write filesystem access |
io.console | io | Console and terminal output |
net.http | net | HTTP client and server |
net.ws | net | WebSocket |
model.infer | model | Model inference |
model.embed | model | Embedding generation |
Until hierarchical effects ship, all sub-effects collapse to their parent token. A function requiring io.fs.read today must declare io. The syntax ![io.fs.read] is reserved for the future and will be rejected by the current compiler.
Cross-References
Section titled “Cross-References”- Tutorial: The Effect System
- Language Specification: §7 Effect System
- Capsule capability grants: Capsules & Packages