08Pointers and the heap
Ownership and borrowing
Each heap block has one owner; lend it with တန်ဖိုးအငှား, and the compiler finds the pointer mistakes before the program runs.
Each block from the heap should be given back once, and no pointer should use it after that. Zaka keeps track of who is in charge of each block: one pointer owns it, and the others borrow it. With that, the compiler finds the pointer mistakes before the program runs, and points at the line at fault.
Three kinds of pointer
| Kind | What it is |
|---|---|
ကိန်းပြည့် လိပ်စာ | an owner: it holds a block of the heap, and it alone gives the block back |
ငှားထားသော ကိန်းပြည့် လိပ်စာ | a borrow: it points at a variable, or at a block that another pointer owns |
ကိန်းပြည့် လိပ်စာအကြမ်း | a raw address: the compiler does not follow it, but the VM still checks each use |
ငှားထားသော ("lent") stands once, at the front of the kind, and marks the outer level
alone. ငှားထားသော ကိန်းပြည့် လိပ်စာ ၏ လိပ်စာ borrows a variable that owns a block:
through it, a function can look at the owner and lend its block, or take the block out of
the variable and even give it back. The inner levels of a pointer kind are never borrows,
because a borrow never sits in a heap block or behind another pointer. ငှားထားသော in
another place, or in front of a raw address, is an error (ZK0713).
One owner each
Setting one owner from another moves the block: the new variable owns it, and the old
one holds ဗလာ from then on.
Expected output
ပ တွင် ဘာမှ မကျန်ပါ
၇
Reading through the old owner after the move is a mistake, and the compiler reports it:
Expected error
error[ZK0701]: "ပ" was moved to another owner and now holds ဗလာ
Comparing the old owner with ဗလာ is the one read still allowed after a move: it is
how a program asks whether the block has gone. Numbers, characters, မှန် and မမှန်,
borrows and raw addresses are duplicated, not moved. Owners, and lists that hold owners,
move.
Lending a block
To let another pointer use a block while the owner keeps it, lend the block with
တန်ဖိုးအငှား ("the value, on loan"):
Expected output
၇
| Zaka | Gives |
|---|---|
ပ ၏ တန်ဖိုး | the value in the block: a read |
ပ ၏ တန်ဖိုးအငှား | a borrow of the block ပ owns; ပ keeps it |
x ၏ လိပ်စာ | a borrow of the variable x |
ပ + ၁, ပ - ၁ | a borrow of a place inside the block |
A borrow reads and writes through the block as often as the program needs, and several borrows of one block can live at once. A borrow never gives the block back: that is the owner's task, after the last use of each borrow.
The compiler wants to know which one is meant. An owner written where a borrow is wanted would move the block, so it is refused, and the message names the fix:
Expected error
error[ZK0715]: "ပ" owns its block and would move here; write "ပ ၏ တန်ဖိုးအငှား" to lend it
The reverse is refused too. A borrow never becomes an owner, because what it points at has an owner of its own:
Expected error
error[ZK0714]: a borrowed address cannot be given where an owner is wanted
What a function header promises
A function's header tells what it does with each pointer passed in:
| In the header | It promises |
|---|---|
parameter ငှားထားသော ကိန်းပြည့် လိပ်စာ ပ | "I look at the block, and can write to it. The caller keeps it." |
parameter ကိန်းပြည့် လိပ်စာ ပ | "Hand the block to me. I give it back, or pass it on." |
result ကိန်းပြည့် လိပ်စာ ရလဒ် | "Here is a new block. The caller owns it now." |
result ငှားထားသော ကိန်းပြည့် လိပ်စာ ရလဒ် | "Look there: inside a block the caller lent me, or at a top-level variable." |
ပေါင်း adds up a block it borrows, and ရှင်း takes a block and gives it back:
Expected output
၆
The first call lends the block with ကိန်းများ ၏ တန်ဖိုးအငှား, so ကိန်းများ keeps it.
The second hands the block over: after it, ကိန်းများ holds ဗလာ, and ရှင်း has given
the block back. For these two forms, the call itself tells whether the variable keeps its
block.
A call given the address of the variable, ပ ၏ လိပ်စာ, is different. Through a
ငှားထားသော ကိန်းပြည့် လိပ်စာ ၏ လိပ်စာ parameter, the function can change ပ itself, as
on changing the caller's variables. It can even take the block out of
ပ and give it back:
Expected output
ပ တွင် ဘာမှ မကျန်ပါ
After such a call, compare ပ with ဗလာ before reading through it.
A new block from a function
A function whose result is an owner hands a new block to its caller. The caller owns it from then on, and gives it back:
Expected output
၅
The address of one of the function's own variables cannot leave like this: see how long an address lasts.
A borrow from a function
A function can return a borrow as well. The borrow must point into a block that the caller lent through a borrow parameter, or at a top-level variable, so the caller knows what the result borrows:
Expected output
၉
၁
အကြီး borrows from ကိန်းများ, just as the argument did, so ကိန်းများ gives the
block back after the last use of အကြီး. In C, a function that moves the caller's
pointer takes a pointer to that pointer. In Zaka it returns the new borrow instead.
Borrows live in variables
A borrow lives in a local variable, a parameter, or a top-level variable that the top-level code sets. It cannot be kept in a heap block, written through a pointer, or put in a top-level variable from inside a function, since what it points at could end first:
Expected error
error[ZK0703]: a borrowed address cannot be stored in a heap block, through a pointer, or in a top-level variable from a function
After the call, ဝှက်ထား would point at a variable that is gone. Taking the address of a
borrow is refused as well (ZK0705), so each borrow is reached through its own name.
Raw addresses
Some programs hand out heap room in a fashion of their own. An arena takes one large
block and hands out pieces of it. The pieces are never given back one at a time: the
whole arena goes back at once. No owner or borrow can describe a piece, so the pieces are
raw addresses, written with လိပ်စာအကြမ်း:
Expected output
၄၂
၇
၂
The arena itself, အခင်း, is a plain owner, and the compiler checks it like each other
owner. The conversion inside ယူ is where the program takes over from the compiler. The
VM still checks each read and write through က and ခ: a piece used past the end
of the arena, or after the arena is given back, stops the program. Each raw kind is
spelled with အကြမ်း, so a search for that word finds each place the compiler does not
check.
A lesson can forbid raw addresses with zakac run --no-raw:
Expected error
error[ZK0717]: raw addresses are not allowed here (--no-raw)
Turning the checks down
| Option | In the Explorer's settings | Effect |
|---|---|---|
--mem-check error | Ownership check: Error | the default: each pointer mistake the compiler proves is an error, and nothing runs |
--mem-check warn | Ownership check: Warn | the same mistakes are warnings; the program runs, and the VM stops it at a bad access |
--mem-check off | Ownership check: Off | the compiler reports none of those mistakes |
--mem-strict | Strict ownership check | also warns about a pointer that can be ဗလာ |
--no-raw | Forbid raw addresses | refuses each raw kind (ZK0717) |
warn and off change the codes from ZK0701 to ZK0712 alone. The kind rules of this
page, ZK0705 and ZK0713 to ZK0717, are errors in each mode. A move leaves ဗလာ behind in
each mode too, so a program does the same thing whichever mode compiles it.
The strict check warns where a pointer can be ဗလာ, such as a new block never compared
with ဗလာ, since the heap can run out:
Expected warning
warning[ZK0711]: "ပ" can be ဗလာ here; compare it with ဗလာ first
The page on pointer mistakes shows each mistake twice: as the compiler
reports it, and as the VM stops it under warn.
Hands on
- Call
လဲလှယ်from changing the caller's variables with one block lent twice:ပ ၏ တန်ဖိုးအငှား၊ ပ ၏ တန်ဖိုးအငှား. Does it compile, and what does it do? - Write a function that takes an owner and a count, doubles each box of the block, and returns the block as its result. Which variable gives the block back?
- Change
ယူso that each piece starts at a multiple of 8 octets.
Next: Pointer mistakes.