စံကား

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

KindWhat 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:

စံကားZK0701

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

၇
ZakaGives
ပ ၏ တန်ဖိုး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:

စံကားZK0715

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:

စံကားZK0714

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 headerIt 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:

စံကားZK0703

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:

စံကားZK0717

Expected error

error[ZK0717]: raw addresses are not allowed here (--no-raw)

Turning the checks down

OptionIn the Explorer's settingsEffect
--mem-check errorOwnership check: Errorthe default: each pointer mistake the compiler proves is an error, and nothing runs
--mem-check warnOwnership check: Warnthe same mistakes are warnings; the program runs, and the VM stops it at a bad access
--mem-check offOwnership check: Offthe compiler reports none of those mistakes
--mem-strictStrict ownership checkalso warns about a pointer that can be ဗလာ
--no-rawForbid raw addressesrefuses 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:

စံကားZK0711

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.