Skip to content

Effects

A function without a uses clause is guaranteed pure by the compiler. This is not a coding convention and not the result of a review — it is a consequence of analysing effects across the call graph. A function that declares nothing has an empty capability set, and the empty set can never contain the effect set of a function it calls.

Effect What it grants In the uses IO blanket?
IO Console output, stdin Yes
Fs Filesystem Yes
Clock Clock and sleep Yes
Args Command line Yes
Exit Process termination Yes
Net DNS, sockets, TLS No
Rand Randomness No
Proc Child processes, environment No
Conc Tasks, channels, scheduling No

uses IO is a blanket alias: at parse time it expands to {IO, Fs, Clock, Args, Exit}. It exists for backward compatibility with older code. It does not cover Net, Rand, Proc or Conc — those four must be declared explicitly.

Writing uses Bogus is a parse error, and the compiler lists the exact set of valid effects.

fn read_config(path: str) -> str uses Fs {
return read_file(path)
}
fn elapsed_ms() -> int uses Clock {
return clock_ms()
}
fn program_args() -> list[str] uses Args {
return args()
}
fn pure_compute(n: int) -> int {
# No uses clause -> pure by default
let mut s: int = 0
let mut i: int = 0
while i < n {
s = s + i
i = i + 1
}
return s
}

pure_compute cannot print, cannot read a file, cannot read the clock and cannot touch the network — and that is checked, not promised.

What the compiler says when you get it wrong

Section titled “What the compiler says when you get it wrong”

When a declaration is missing, the compiler does not merely point at the offending line. It names the call chain that carried the effect in:

error: function 'load_config' calls 'read_file' which requires effect 'Fs'
not declared (declared: (none - pure); missing: Fs)

And if you declare pure but call something effectful:

error: function 'f' is declared 'pure' but calls 'println' which requires
effect 'IO' (declared pure but callee chain is not pure)

pure and uses are mutually exclusive at parse time. pure is not decoration: it is a claim that the checker verifies across the call graph and names the offending edge when the claim is false.

Terminal window
hlc --audit examples/effects_demo.hls

prints, for every function, the capabilities it declared next to the ones the compiler computed:

function declared computed
------------------------------ ------------------------------ ------
pure_compute (none - pure) (none)
read_config Fs Fs
elapsed_ms Clock Clock
program_args Args Args
main IO, Fs, Clock, Args, Exit IO, Fs, Clock, Args, Exit

The empty computed column for pure_compute is the argument in one line: the compiler walked the call graph and found no effect at all.

Note that main’s declared column already shows the expanded blanket — IO, Fs, Clock, Args, Exit — because the expansion happens at parse time.

From Stage 111 an effect also has a value form, Cap[E] — a capability token that can be bound, passed, returned and stored in a struct field. It can only be issued by main or by a function that already declares that effect.

Question Where to look
Can this function print? Is IO in the computed column?
Can it touch the clock? Is Clock in computed?
Can it open a socket? Is Net in computed — even five call layers down?
Can it spawn a task? Is Conc in computed?

The full list of builtins per effect is in the quick reference.