# 9. Ownership and memory

> Chapter 9: how Alice uses the .NET garbage collector, why temporary arrays cost time, and how ownership, own, borrow, scope and @[Stack] make hot code allocate nothing.

Canonical: https://alice-lang.org/documentation/learn/ownership/

[Documentation index](https://alice-lang.org/documentation/llms.txt) · [Complete book](https://alice-lang.org/llms-full.txt)

Up to now you have created arrays, lists and objects without thinking about memory. That is fine for most code: .NET has a **garbage collector** (GC) that finds objects nobody uses any more and frees them. But the collector is not free. In a loop that runs millions of times, a temporary array per iteration means millions of allocations and frequent collections.

This chapter explains how Alice removes most of that cost without making you manage memory by hand. The idea is **ownership**, borrowed from Rust. In Alice it is inferred, and you only write it down when you want the compiler to check it.

## 9.1 What allocation costs

Every `Array(T).new` and every `Class.new` asks the GC for memory. Allocating is fast. The cost comes later: the collector must stop or slow the program to find dead objects, and allocated memory pushes useful data out of the CPU caches.

You can measure what a piece of code allocates with a .NET counter, `GC.GetAllocatedBytesForCurrentThread`. Here is a method that smooths a series of numbers. It makes a temporary array on every call:

```alice
def smooth(xs : Array(Float64)) : Float64
  tmp = Array(Float64).new(xs.size, 0.0)
  for i in 1...xs.size - 1
    tmp[i] = (xs[i - 1] + xs[i] + xs[i + 1]) / 3.0
  end
  s = 0.0
  for v in tmp
    s += v
  end
  s
end

data = Array(Float64).new(1000) { |i| i.to_f }
smooth(data)

before = GC.GetAllocatedBytesForCurrentThread
100.times { smooth(data) }
after = GC.GetAllocatedBytesForCurrentThread
puts "allocated per call: #{(after - before) / 100} bytes"
```

```text title="Output"
allocated per call: 0 bytes
```

The array holds 1000 numbers of 8 bytes, so you might expect about 8 KB per call. The answer is 0. What happened?

## 9.2 Owned arrays

The compiler noticed three facts about `tmp`:

1. It is a local array of plain numbers.
2. The method only indexes it, reads its size and loops over it.
3. Nothing stores it anywhere, returns it, or hands it to code that could keep it.

So `tmp` is the only reference to its memory. We say the method **owns** it. An owned array does not need the GC. Alice takes its buffer from a per-thread pool and gives it back where the method stops using it. The first call fills the pool; every later call reuses the same buffer.

This is inferred for you. `alice explain` shows which methods got it:

```sh
alice explain smooth.al
```

```text
╭────────┬────────────────────────────────────────────────────────╮
│ method │ optimizations                                          │
├────────┼────────────────────────────────────────────────────────┤
│ Main   │ none                                                   │
│ smooth │ parallel loop                                          │
│        │ owned array: pooled buffer, no GC allocation once warm │
╰────────┴────────────────────────────────────────────────────────╯
```

"Parallel loop" is a different optimization, the subject of [chapter 11](https://alice-lang.org/documentation/learn/parallelism.md): the compiler also proved that the iterations of the first loop are independent.

### Which arrays can be owned

An array stays owned while it is only:

- indexed, sized or iterated;
- lent to a parameter that does not keep it;
- moved to another owned local;
- returned to the caller, who then owns it.

It stops being owned the moment it may **escape**: stored in a field, captured by a block that outlives the call, put in a collection, or passed to .NET code that might keep it. Then the GC takes care of it, as usual. Your program is correct either way. Ownership only changes how fast it is.

## 9.3 Saying it out loud: `own`

Inference is silent. If you later change the code so that the array escapes, the program still works, just slower, and nobody notices. When an array matters for speed, write `own` and the compiler turns the inference into a checked contract:

```alice fail="AL0207"
class Recorder
  property last : Array(Float64) = Array(Float64).new(0, 0.0)
end

def measure(n : Int32, rec : Recorder) : Float64
  own samples = Array(Float64).new(n, 1.0)
  rec.last = samples
  samples.sum
end
```

```text title="Error"
error[AL0207]: owned value `samples` escapes
```

The error points at the promise you made. Either keep the array local, or remove `own` because sharing it was the point.

### Moving

Owned arrays can be **moved**: assigning one owned local to another hands over the buffer. The old name may not be used afterwards, because it no longer refers to anything you own:

```alice fail="AL0208"
def demo : Int32
  own a = Array(Int32).new(4, 0)
  own b = a
  a[0]
end
```

```text title="Error"
error[AL0208]: use after move: `a` was moved into another owned local
```

Moving is what makes a classic dynamic-programming loop allocation-free. Each row is computed from the previous one; then the new row becomes the previous one, and the old buffer goes back to the pool for the next row:

```alice
def edit_distance(a : String, b : String) : Int32
  own prev = Array(Int32).new(b.size + 1, 0)
  for j in 0..b.size
    prev[j] = j
  end
  for i in 1..a.size
    own row = Array(Int32).new(b.size + 1, 0)
    row[0] = i
    for j in 1..b.size
      cost = a[i - 1] == b[j - 1] ? 0 : 1
      row[j] = Math.Min(Math.Min(row[j - 1] + 1, prev[j] + 1), prev[j - 1] + cost)
    end
    prev = row
  end
  prev[b.size]
end

puts edit_distance("kitten", "sitting")
edit_distance("kitten", "sitting")
before = GC.GetAllocatedBytesForCurrentThread
100.times { edit_distance("kitten", "sitting") }
puts "bytes per call: #{(GC.GetAllocatedBytesForCurrentThread - before) / 100}"
```

```text title="Output"
3
bytes per call: 0
```

`prev = row` moves `row` into `prev`. The buffer `prev` held before is released at that moment, and the next `Array(Int32).new` picks it up again. The program computes seven rows and allocates one buffer pair, once.

## 9.4 Parameters: `own` and `borrow`

Methods can state what they do with an array argument.

`borrow` promises that the method only looks at the array and does not keep it. The caller's array stays owned across the call:

```alice
def total(borrow xs : Array(Int32)) : Int64
  s = 0_i64
  for v in xs
    s += v
  end
  s
end

def demo : Int64
  own counts = Array(Int32).new(5, 2)
  total(counts) + counts.size
end

puts demo
```

```text title="Output"
15
```

`own` on a parameter means the caller hands the array over. The method may then update it in place: rebinding an owned array to a `map` of itself overwrites the buffer instead of making a new one.

```alice
def scale(own xs : Array(Float64), k : Float64) : Array(Float64)
  xs = xs.map { |v| v * k }
  xs
end

def demo : Float64
  own a = Array(Float64).new(3, 1.5)
  b = scale(a, 2.0)
  b.sum
end

puts demo
```

```text title="Output"
9.0
```

After `scale(a, 2.0)`, `a` has moved into `scale`, and reading it is `AL0208`, just as with a local move. A borrowed parameter that the method tries to keep is `AL0213`.

### Two names for one array

Ownership also catches a classic bug. This method writes into `dst` while reading `src`. If both are the same array, it reads values it has already overwritten:

```alice fail="AL0217"
def add_one_into(dst : Array(Int32), src : Array(Int32))
  for i in 0...src.size
    dst[i] = src[i] + 1
  end
end

a = [1, 2, 3]
add_one_into(a, a)
```

```text title="Error"
error[AL0217]: conflicting borrow: `a` is passed as both `dst` and `src` of add_one_into
```

Pass `a.dup` as the source if that is what you meant.

## 9.5 Lifetimes: `scope ... end`

Normally an owned array goes back to the pool when the method stops using it. A `scope` block makes that point explicit. Every array created inside must be owned, and its buffer is released at `end`. Variables declared inside are not visible after it.

```alice
def run(n : Int32) : Int32
  s = 0
  scope
    tmp = Array(Int32).new(n, 1)
    for v in tmp
      s += v
    end
  end
  s
end

puts run(10)
```

```text title="Output"
10
```

Use `scope` in long methods where a big temporary is needed only in the middle, so its buffer is free again for what follows.

## 9.6 Small values on the stack

Small arrays that never escape, such as `[r, g, b]` inside a method, do not even go to the pool. Alice puts them in a buffer on the **stack**, the memory of the running method, which costs nothing to free.

For your own small structs, `@[Stack]` asks for the same thing as a rule: a value of that type may only live on the stack. It compiles to a .NET `ref struct`. The compiler then refuses anything that would move it to the heap, such as capturing it in a block:

```alice fail="AL0218"
@[Stack]
struct Window
  getter lo : Int32
  getter hi : Int32

  def initialize(@lo : Int32, @hi : Int32)
  end
end

w = Window.new(1, 5)
f = ->() { w.lo }
puts f.call
```

```text title="Error"
error[AL0218]: stack value `w` of @[Stack] type Window cannot be captured by a block or lambda
```

## 9.7 What stays with the GC

Ownership is about arrays of plain values: numbers, booleans, characters and structs of them. Objects of classes, strings, lists and hashes stay with the .NET GC, which is good at them. Alice still avoids many hidden allocations there: block pipelines become loops ([chapter 4](https://alice-lang.org/documentation/learn/collections.md)), and identical immutable objects built from constants are shared.

When you need control over many small tree nodes, the planned **arena** types will allocate them in large slabs. They are not part of the language yet; [Appendix E](https://alice-lang.org/documentation/learn/coming-in-1-0.md) describes them.

## Project: hourly sales without garbage

The shop wants sales per hour for each day of the year, and the busiest hour. Each day's numbers are needed only while that day is processed, so the hourly array is owned, and its buffer is reused for all 365 days.

```alice
def busiest_hour(borrow cups : Array(Int32)) : Int32
  best = 0
  for h in 1...cups.size
    best = h if cups[h] > cups[best]
  end
  best
end

def year_report(days : Int32) : Array(Int32)
  peaks = Array(Int32).new(24, 0)
  for day in 0...days
    own hourly = Array(Int32).new(24, 0)
    for h in 8...18
      hourly[h] = (day * 7 + h * 13) % 50
    end
    peaks[busiest_hour(hourly)] += 1
  end
  peaks
end

peaks = year_report(365)
best = busiest_hour(peaks)
puts "most often the busiest hour: #{best}:00 (#{peaks[best]} days)"
```

```text title="Output"
most often the busiest hour: 14:00 (80 days)
```

`hourly` is owned, so the 365 arrays are one pooled buffer. `peaks` is returned to the caller, which is allowed: returning moves ownership out. `busiest_hour` borrows both, and says so.

:::caution\[Common mistakes]

- **Storing an `own` array.** `error[AL0207]`. Keep it local, or drop `own` if it really must be shared.
- **Using a moved array.** `error[AL0208]`. After `b = a` between owned locals, or after passing `a` to an `own` parameter, use `b` or reassign `a`.
- **Keeping a `borrow` parameter.** `error[AL0213]`. A borrowed array may be read and lent, not stored or returned.
- **Expecting ownership for objects.** Classes, strings, lists and hashes are managed by the GC. Ownership applies to arrays of plain values.
- **Optimizing without measuring.** Use `alice explain` and the allocation counter before rewriting code.
  :::

## Exercises

1. Change `smooth` in 9.1 so that it returns `tmp` instead of the sum. Is `tmp` still owned? Who owns it now?
2. Why does the compiler reject `own a = ...` followed by `@@cache = a` but accept the same code without `own`?
3. Write `prefix_max(borrow xs : Array(Int32)) : Array(Int32)` that returns, at each position, the largest value seen so far. Print it for `[3, 1, 4, 1, 5]`.

<details>
<summary>Answers</summary>

1. Yes. Returning is a move: the caller now owns the array, and it may keep it or pass it on.
2. Without `own`, the compiler simply sees that the array escapes and lets the GC manage it. With `own`, you promised that it would not escape, and the store breaks that promise: `AL0207`.
3. The result is a new array that the method returns, and `xs` is only read:

   ```alice
   def prefix_max(borrow xs : Array(Int32)) : Array(Int32)
     out = Array(Int32).new(xs.size, 0)
     best = Int32::MIN
     for i in 0...xs.size
       best = xs[i] if xs[i] > best
       out[i] = best
     end
     out
   end

   puts prefix_max([3, 1, 4, 1, 5])
   ```

   ```text title="Output"
   [3, 3, 4, 4, 5]
   ```

</details>

## Summary

- The .NET GC manages memory. Alice keeps hot code from needing it.
- A local array of plain values that never escapes is **owned**: its buffer is pooled and reused, so warm calls allocate 0 bytes.
- `own` makes ownership a contract (`AL0207` escape, `AL0208` use after move). `borrow` parameters promise not to keep an array (`AL0213`). `own` parameters take it over and may update it in place.
- Passing one array as two parameters that one of them writes is `AL0217`.
- `scope ... end` releases owned arrays at `end`. Small local arrays and `@[Stack]` structs live on the stack (`AL0218`).
- `alice explain` and `GC.GetAllocatedBytesForCurrentThread` show what you got.

[Chapter 10](https://alice-lang.org/documentation/learn/safety.md) collects the other mistakes the compiler refuses to let through.

## Continue reading

- Previous: [8. Errors](https://alice-lang.org/documentation/learn/errors.md)
- Next: [10. Safety as compile errors](https://alice-lang.org/documentation/learn/safety.md)
