Memory & Results
This is usually the first page where cnegative starts to feel more low-level.
That is normal.
Do not try to learn everything here at once. Focus on two ideas:
result Tis for operations that might fail- pointers are for explicit memory access
If you are a beginner, learn result first. Leave pointers second.
Part 1: result T
result T means:
“This tries to produce a
T, but it might fail.”
Example:
fn:result int divide(a:int, b:int) {
if b == 0 {
return err;
}
return ok a / b;
}That function returns:
ok ...when it workederrwhen it failed
Reading the result
Check .ok first:
fn:int main() {
let r:result int = divide(10, 2);
if r.ok {
println(r.value);
}
return 0;
}Important rule:
.okcan always be read.valuecan only be read after proving the result is ok
guarded result access
Using r.value without a proven-ok check reports E3024.
Another valid proof pattern
This also works:
fn:int main() {
let r:result int = divide(10, 2);
if r.ok == false {
return 1;
}
println(r.value);
return 0;
}The compiler now understands both:
if r.ok { ... }if r.ok == false { return err; }orreturn 1;style checks before later.value- immutable bool aliases like
let ready:bool = r.ok; if ready { ... } - simple composed guards like
if r.ok && cond { ... }andif blocked || cond { return err; } - reassigning
r = ok value;makesr.valueusable again, while reassigning a mutable result clears earlier proofs - guarded
ifexpressions likeif r.ok { r.value[0] } else { 0 } - guarded loops like
while r.ok { return r.value[0]; }
try for unwrap-or-return
When you are already inside a result ... function, try is the shorter pattern:
fn:result int plus_one(a:int, b:int) {
try value = divide(a, b);
return ok (value + 1);
}Simple meaning:
- call something that returns
result T - if it failed, return
err - if it worked, keep the inner
Tas a normal local binding
main can now use this too because main may return result int or result u8:
fn:result int main() {
try value = divide(20, 5);
return ok value;
}Beginner mental model
Think of result T as:
- success path
- failure path
And you must check which path you are on before reading the value.
Part 2: pointers
Pointers let you refer to memory explicitly.
Start with a normal value:
let mut value:int = 10;Get its address:
let p:ptr int = addr value;Important current rule:
addris for real mutable storageaddron an immutableletbinding fails withE3035addron a module constant fails withE3036
Read or write through the pointer:
deref p = 11;You can also use .value on a pointer:
p.value = 12;Heap allocation
Use alloc when you want heap memory:
let heap:ptr int = alloc int;
heap.value = 42;
free heap;This is the basic rule:
alloccreates heap memoryfreereleases heap memory
free is explicit
If you allocate heap memory yourself, you are responsible for freeing it.
Zone allocation
zone is the temporary-memory form:
fn:int main() {
let mut total:int = 0;
zone {
let value:ptr int = zalloc int;
deref value = 7;
total = deref value;
}
return total;
}Simple meaning:
zone { ... }creates a temporary allocation scopezalloc Tallocates inside that scope- everything allocated with
zallocis released automatically when the zone ends
This is different from heap allocation:
allocis normal heap memory and still needsfreezallocis temporary zone memory and must not be freed manually
Current checked zone rules:
zallocoutside azonereportsE3041- returning a zone-owned value reports
E3042 freeon zone memory reportsE3043- assigning a zone-owned value into outer storage reports
E3044 - passing a zone-owned value into an ordinary function parameter reports
E3045
This feature is meant to stay explicit and simple. It is not hidden lifetime inference.
One small stdlib note
Not every heap-backed thing is released with raw free.
Some stdlib modules manage their own heap-owned objects and give you a module-level release function instead. Right now the clearest examples are:
std.bytes.release(buffer)std.lines.release(buffer)std.text.release(builder)
Use raw free for:
- heap pointers from
alloc - owned strings returned by the runtime
Do not use raw free for:
slice Tvaluesresult Twrappers- zone-owned pointers from
zalloc - stdlib-owned buffer or builder objects
Those now get clearer diagnostics:
E3037:freecannot release aslicevalueE3038:freecannot release aresultwrapper directlyE3043:freecannot release zone-owned memory
Use module release(...) functions for:
- stdlib-owned buffer or builder objects
Beginner rule of thumb
If you are unsure:
- use ordinary values first
- use
resultwhenever failure is possible - only use pointers when you actually need explicit memory access
- use
zoneonly for clearly temporary pointer data
That will keep your first programs much easier to reason about.
Runtime memory codes
The tracked allocator now uses dedicated runtime memory codes too:
R4001: allocation failedR4002:reallocon unmanaged pointerR4003:freeon unmanaged pointerR4004: leak summary at shutdownR4005: individual leaked allocation detailR4006: allocation size overflowR4008:reallocfailed and preserved the original pointerR4009:reallocsize overflowR4010: double free detectedR4011:freeon an interior pointer instead of the allocation startR4013: use-after-free detected inside the allocator quarantine windowR4016: buffer overflow detected by allocator guard checksR4017: buffer underflow detected by allocator guard checks
Small combined example
fn:result int divide(a:int, b:int) {
if b == 0 {
return err;
}
return ok a / b;
}
fn:int main() {
let value:result int = divide(20, 5);
if value.ok == false {
return 1;
}
let heap:ptr int = alloc int;
heap.value = value.value;
print(heap.value);
free heap;
return 0;
}Next step
Continue to Strings & Ownership.