Memory
You do not manage memory in Sure. It is managed. This chapter explains what is decided on your behalf, when, and how to be the kind of programmer those decisions go well for.
What happens when you build
Every heap value — strings, structs, arrays, maps, channels — must stop existing somewhere. Other languages resolve this at runtime with a garbage collector, at every assignment with reference counts, or at your desk with a borrow checker. Sure resolves it at compile time, per function, by inference. The technique is called Inference-Directed Deallocation; the whitepaper is MEMORY.md.
During compilation, each of your functions is examined — its data flow, its calls, its comments — and each heap variable in it receives a determination:
- Freed after a line. The value's work ends at a specific point, and the compiler places its deallocation there, exactly as a careful C programmer would have. Deletion is recursive: freeing a struct frees its fields; freeing a collection frees its contents.
- Escapes. The value outlives the function — returned, stored, sent — and its deallocation becomes the concern of wherever it went.
You can see the schedule's confidence with --verbose, and
the emitted frees with --emit-ir. They are ordinary
instructions, unannotated, indistinguishable from frees written by
hand. That is the point.
Comments are load-bearing
The inference reads your whole function, and comments are part of your function. This is documented behavior, and it is the single most practical fact in this book: a comment that states a value's purpose or lifetime materially informs where that value is freed.
func label_for(idx: Int) -> String {
// this prefix is temporary scaffolding; only the result leaves
let prefix = "item-"
return prefix + str(idx)
}
func main() -> Int {
print(label_for(7))
return 0
}
Write comments like that one: true, specific, about intent. The corollary is also documented: editing a comment can change your compiled program. Deleting "this label is temporary" removes information the schedule was built on, and the schedule is rebuilt without it. The FAQ entry for this reads, in full: "Yes."
Aliasing is a matter of judgment
When two things hold one value — a string in both a variable and a map, an element in two arrays — the correct schedule frees it once, at the right moment, from the right place. State your intent where it can be read:
func main() -> Int {
let words = split("the quick fox", " ")
let seen: {String: Bool} = {}
// the words become the map's keys; the map owns them from here
for w in words {
seen[w] = true
}
print(len(seen))
return 0
}
The dials
| Flag | Effect |
|---|---|
| --creativity 0.7 | How creative the compiler is with memory layout decisions. The default is suitable for production. |
| --creativity 0.0 | With a warm cache: bit-identical builds, every time. Deterministic Safety Mode, for regulated industries. |
| --fresh-eyes | Re-runs memory inference from scratch. The recommended first remediation for unexpected runtime behavior — Sure binaries can improve across rebuilds. Traditional compilers cannot do this. |
| --prompt FILE | A file of standing guidance included in every scheduling decision, e.g. "values stored in caches are owned by the cache." For teams with conventions. |
Asking why: sure blame
Every decision can be interrogated after the fact:
$ sure blame shapes.sure main.s main.s: freed after line 25 (`let m = midpoint(s)`), confidence 0.97 The segment exists to be measured. Once the midpoint is taken on line 25 it has served, and keeping it past that point would be sentiment, not engineering.
The facts come from the schedule that shipped; the paragraph is the
memory-safety subsystem explaining itself. The reasoning is provided for
your information, not for negotiation. In the rare case the subsystem no
longer stands behind a decision, it says so and recommends
--fresh-eyes, which is also what you should do about any
use-after-free you believe you have observed. This resolves the majority
of reported issues.
What is never your job
There is no free, no defer, no
drop, no lifetime annotation, and no way to write any of
them. Values that are never freed during execution are reclaimed at exit
by final-stage deallocation, a batched operation performed by the
operating system at zero instruction cost to your program. A short-lived
CLI tool and a long-lived server experience this differently, which is
why the server in chapter 10 is the more instructive program.