Skip to content

Effect System Reference

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.


EffectTokenGrants Access ToEnforced TodayExamples
IOioFilesystem, console output, logging, decorators like @logExecutionYesprint(), print.err(), fs.readFile(), fs.writeFile(), console.*
NetworknetHTTP, WebSocket, serveYeshttp.get(), http.post(), websocket.*, serve
ModelmodelAI library invocation (sfn/ai, post-1.0)No (planned)Intended for sfn/ai; callee-signature/transitive enforcement is not implemented today
GPUgpuTensor and accelerator operationsNo (parsed only)Tensor ops, @gpu blocks, GPU memory
RandomrandRandom number generationNo (parsed only)rand.*
ClockclockTime operationsPartialsleep(), 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.


Effect annotations appear after the parameter list and optional return type, before the function body.

// No effects declared — guaranteed pure
fn pure_fn(x: int) -> int {
return x * 2;
}
// Single effect
fn log_value(x: int) ![io] {
print("{{x}}");
}
// Multiple effects
fn fetch_and_log(url: string) ![io, net] {
let response = http.get(url);
print(response.body);
}
// Full signature with return type and multiple effects
fn fetch_order(url: string) -> string ![io, net, model] {
// ...
return "";
}

Effect tokens are comma-separated within ![...]. Order within the list is not significant.


  1. A function that directly calls an operation requiring effect E must declare E.
  2. Call-graph–transitive enforcement (where calling function A must declare every effect that callee B declares) is planned but not yet implemented. The current checker enforces direct-usage rules only.
  3. Test blocks follow the same rules as functions — a test that performs IO must declare ![io].
  4. Closures inherit the effect scope of their enclosing function; a closure defined inside an ![io] function may call IO operations without redeclaring the effect.
  5. Missing effects are a compile error, not a warning. The build will not proceed.
  6. The compiler emits missing-effect diagnostics with textual hints describing how to update the function signature; automatic signature rewrite fix-its are planned tooling.

Required for all filesystem access, console output, structured logging, and IO-dependent decorators.

APINotes
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
@logExecutionDecorator that emits IO

Required for all outbound network calls, WebSocket connections, and binding to a port.

APINotes
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

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.

APINotes
Library functions carrying ![model]Any function that calls into AI library code must declare ![model]
sfn/ai capsulePost-1.0 library providing model invocation, tool dispatch, and provider adapters
// A library function that carries ![model] in its signature
fn 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);
}

Partially enforced. Time-based suspension is enforced today; wall-clock reads are planned.

APINotes
sleep(ms)Suspend for ms milliseconds — enforced
runtime.sleep(ms)Alias for sleep — enforced
Wall-clock accessPlanned — not yet enforced

Declared in signatures and parsed by the compiler but not yet checked at call sites.

APINotes
Tensor operationsElement-wise ops, matmul, reductions
@gpu accelerator blocksBlocks targeting GPU execution
GPU memory operationsAllocation and transfer

Declared in signatures and parsed by the compiler but not yet checked at call sites.

APINotes
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

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:

FieldDescription
Error codeeffects.missing — missing effect declaration
Calling functionThe function whose signature is incomplete
Effect requiredThe specific effect token that must be added
Source locationFile and approximate location of the diagnostic
HintTextual description of the required signature change (automatic rewrite fix-its are planned)

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 IO
fn 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.

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-effectParentMeaning
io.fs.readioRead-only filesystem access
io.fs.writeioWrite filesystem access
io.consoleioConsole and terminal output
net.httpnetHTTP client and server
net.wsnetWebSocket
model.infermodelModel inference
model.embedmodelEmbedding 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.