# 10. Safety as compile errors

> Chapter 10: the mistakes Alice refuses to compile: nil, reassignment, missing cases, data races, use after send, impure pure code, ambient authority in safe code, and untyped code in strict mode.

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

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

You have met several compile errors already: a `nil` used without a check, a `val` reassigned, a `case` missing a variant, an owned array that escapes. They share one idea. Each turns a bug that would appear at runtime, maybe months later, into a message you read before the program even starts.

This chapter collects them and adds the most important one, the **data race**. You will see the error each check prints and how to fix it.

## 10.1 The checks you know

| Mistake                                         | Error              | Chapter                                                                                                                              |
| ----------------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
| using a `T?` without checking for `nil`         | `AL0310`           | [2](https://alice-lang.org/documentation/learn/guessing-game.md), [3](https://alice-lang.org/documentation/learn/common-concepts.md) |
| `x = nil` with no type                          | `AL0204`           | [3](https://alice-lang.org/documentation/learn/common-concepts.md)                                                                   |
| assigning to a `val`                            | `AL0203`           | [3](https://alice-lang.org/documentation/learn/common-concepts.md)                                                                   |
| a field no constructor sets                     | `AL0221`           | [6](https://alice-lang.org/documentation/learn/types.md)                                                                             |
| calling a private method                        | `AL0313`           | [6](https://alice-lang.org/documentation/learn/types.md)                                                                             |
| a `case` that misses a variant                  | `AL0201`           | [7](https://alice-lang.org/documentation/learn/sum-types.md)                                                                         |
| an owned array escapes, or is used after a move | `AL0207`, `AL0208` | [9](https://alice-lang.org/documentation/learn/ownership.md)                                                                         |

None of these checks costs anything when the program runs. They happen once, at compile time.

## 10.2 Data races

A program that does several things at once can be much faster. It can also have the worst kind of bug. Two threads read and write the same variable, and the result depends on timing. Such a bug may show up once in a thousand runs, and never on the developer's machine.

Here is the classic example: adding numbers from many threads into one variable. `Par.For(1000) { |i| ... }` runs the block for `i` from 0 to 999, spread over all CPU cores:

```alice fail="AL0202"
total = 0
Par.For(1000) { |i| total += i }
puts total
```

```text title="Error"
error[AL0202]: data race: spawned task writes captured variable 'total'
```

Why is this a race? `total += i` reads `total`, adds, and writes back. If two cores read the same old value at the same moment, one of the two additions is lost. In C# or Java this compiles and usually prints the right answer, but not always. In Alice it does not compile.

### Fixing a race

The compiler's hint lists the tools. Each one makes the sharing explicit:

```alice
total = Atomic.new(0)
Par.For(1000) { |i| total.add(i) }
puts total.get

names = Mutex(List(String)).new(List(String).new)
Par.For(4) { |i| names.lock { |list| list.Add("worker #{i}") } }
puts names.lock { |list| list.Count }

squares = Array(Int64).new(10, 0_i64)
Par.For(10) { |i| squares[i] = i.to_i64 * i }
puts squares.sum
```

```text title="Output"
499500
4
285
```

- `Atomic` is a number that many threads can update safely.
- `Mutex(T)` wraps a value. The only way to reach it is `lock { |value| ... }`, which lets one thread in at a time. The lock and the data it protects cannot be separated.
- Writing `squares[i]` at the iteration's own index is allowed: each iteration touches a different element, so there is no conflict.

The best fix is often to share nothing at all. A task can **return** its result, and the caller waits for it:

```alice
t = spawn { (1..100).sum }
puts t.wait
```

```text title="Output"
5050
```

### Races hide in methods too

The checker follows calls. A method that changes an object's field is a write, even when it does not look like one at the call site:

```alice fail="AL0202"
class Tally
  getter n : Int32 = 0

  def bump
    @n += 1
  end
end

tally = Tally.new
Par.For(10) { |i| tally.bump }
```

```text title="Error"
error[AL0202]: data race: `bump` mutates a shared Tally
```

It also catches the opposite case: the main program changing data that a running task still reads.

```alice fail="AL0202"
prices = [250, 300, 350]
t = spawn { puts prices.sum }
prices[0] = 0
t.wait
```

```text title="Error"
error[AL0202]: data race: writing `prices` while a spawned task may still read it
```

Move the write after `t.wait`, and the program compiles.

### Use after send

Sending an array through a channel gives it to the receiving thread. Using it afterwards would share it again, so the compiler forbids that too:

```alice fail="AL0219"
ch = Channel(Array(Int32)).new(1)
batch = [1, 2, 3]
ch.send(batch)
batch[0] = 9
```

```text title="Error"
error[AL0219]: `batch` was sent to another thread and belongs to the receiver now
```

Send `batch.dup` if you need to keep working on your copy.

## 10.3 Saying what a method may do

The checks so far are about values. Two modifiers let you make promises about a method's **effects**, and the compiler proves them.

### `pure def`: no side effects

A **pure** method only computes its result from its arguments. It prints nothing, writes no file, and changes no state it did not create. Pure methods are easy to test, safe to run in parallel, and safe to cache. Alice infers purity for every method; `pure` makes it a contract: Purity is what the compiler can prove: arithmetic, `Math`, and methods that are themselves pure. Some library calls, such as `String#upcase`, are not on the proven list yet, so a `pure def` that calls them is `AL0210` too.

```alice fail="AL0210"
pure def tax(cents : Int32) : Int32
  puts "computing tax"
  cents * 7 / 100
end
```

```text title="Error"
error[AL0210]: `tax` is not pure
```

### `safe def`: no ambient authority

Imagine your shop accepts discount rules from partners, or a plugin computes prices. You want that code to compute, and nothing else: no reading files, no network, no environment variables. `safe def` checks exactly that, for the method and everything it calls:

```alice fail="AL0212"
safe def discount(total : Int64) : Int64
  percent = env("DISCOUNT").to_i
  total - total * percent / 100
end
```

```text title="Error"
error[AL0212]: `discount` is not safe: it calls `env`
```

The fix is to pass in what the method needs. A safe method may use what it is given, but cannot reach out for more:

```alice
safe def discount(total : Int64, percent : Int32) : Int64
  total - total * percent / 100
end

puts discount(1000_i64, 15)
```

```text title="Output"
850
```

This style is called **capability security**: authority comes only through arguments. `safe` is a compile-time check, not a sandbox. It does not limit memory or time.

## 10.4 Strict mode: no guessing

Alice infers types. When it cannot, for example for some operators on .NET types, it falls back to `dynamic`: the operation is resolved when the program runs. That keeps quick scripts working, but a `dynamic` value has no compile-time checks and is slower.

**Strict mode** turns every such fallback into an error. Turn it on for a project in `alice.toml`:

```toml
[build]
strict = true
```

or for one command with `--strict`. Here the `*` between a .NET `Vector2` and a `Float32` cannot be typed statically:

```alice fail="AL0220" args="--strict"
v = System.Numerics.Vector2.new(1.0_f32, 2.0_f32)
w = v * 2.0_f32
puts w
```

```text title="Error"
error[AL0220]: strict: `*` on `Vector2` and `Float32` has no static type
```

Without `--strict`, the same program runs and prints `<2, 4>`. Under strict mode, call the typed .NET method instead:

```alice args="--strict"
v = System.Numerics.Vector2.new(1.0_f32, 2.0_f32)
w = System.Numerics.Vector2.Multiply(v, 2.0_f32)
puts w
```

```text title="Output"
<2, 4>
```

A parameter you declare as `Any` on purpose stays allowed: strict mode only rejects dynamic typing you did not ask for. Libraries and long-lived services should use strict mode.

## Project: a safe, pure pricing core

The tea shop's pricing rules are the part of the program that must never be wrong, and that partners may extend. Make them `safe` and `pure`, and keep the I/O outside:

```alice
safe pure def line_cents(cents_each : Int32, cups : Int32) : Int64
  cents_each.to_i64 * cups
end

safe pure def apply_discount(total : Int64, percent : Int32) : Int64
  return total if percent <= 0
  total - total * Math.Min(percent, 50) / 100
end

safe def order_total(prices : Array(Int32), cups : Array(Int32), percent : Int32) : Int64
  total = 0_i64
  for i in 0...prices.size
    total += line_cents(prices[i], cups[i])
  end
  apply_discount(total, percent)
end

puts order_total([250, 350], [2, 1], 10)
puts order_total([250, 350], [2, 1], 90)
```

```text title="Output"
765
425
```

Modifiers combine: `safe pure def`. The discount is capped at 50 percent inside the pure core, so a bad input cannot make a price negative. [Chapter 13](https://alice-lang.org/documentation/learn/proofs.md) proves that claim for every input.

:::caution\[Common mistakes]

- **Updating a captured variable in `Par.For` or `spawn`.** `error[AL0202]`. Use `Atomic`, `Mutex(T)`, a channel, or return the value from the task.
- **Calling a mutating method on a shared object from parallel code.** Also `AL0202`. Give each task its own object, or guard it with `Mutex(T)`.
- **Using a value after sending it.** `error[AL0219]`. Send a copy.
- **Expecting `safe` to sandbox time or memory.** It controls authority, not resources.
- **Reading strict errors as type errors.** `AL0220` means "could not be typed statically". Annotate, call a typed method, or write `Any` on purpose.
  :::

## Exercises

1. Fix this program so that it compiles and prints the number of even numbers below 1000: `count = 0` then `Par.For(1000) { |i| count += 1 if i.even? }`.
2. Is `def area(w : Int32, h : Int32) : Int32 = w * h` pure? Add `pure` and check with `alice check`.
3. Write `safe def greeting(name : String, hour : Int32) : String` that returns `"Good morning, NAME"` before 12 and `"Good afternoon, NAME"` otherwise. Why does the hour come in as a parameter instead of being read from the clock?

<details>
<summary>Answers</summary>

1. Answer:

   ```alice
   count = Atomic.new(0)
   Par.For(1000) { |i| count.add(1) if i.even? }
   puts count.get
   ```

   ```text title="Output"
   500
   ```

2. Yes: it only computes a number from its arguments.

   ```alice
   pure def area(w : Int32, h : Int32) : Int32 = w * h

   puts area(3, 4)
   ```

   ```text title="Output"
   12
   ```

3. Reading the clock is ambient authority (`now` is not on the safe list), so a safe method cannot do it. Taking the hour as a parameter also makes the method testable for any hour.

   ```alice
   safe def greeting(name : String, hour : Int32) : String
     hour < 12 ? "Good morning, #{name}" : "Good afternoon, #{name}"
   end

   puts greeting("Alice", 9)
   puts greeting("Alice", 15)
   ```

   ```text title="Output"
   Good morning, Alice
   Good afternoon, Alice
   ```

</details>

## Summary

- Alice turns whole families of runtime bugs into compile errors with codes and hints.
- `AL0202` rejects data races: writes to captured variables, mutating calls on shared objects, writes while a task still reads. `Atomic`, `Mutex(T)`, channels and returned values are the fixes.
- `AL0219` rejects using a value after sending it to another thread.
- `pure def` (`AL0210`) promises no side effects. `safe def` (`AL0212`) promises no ambient authority.
- Strict mode (`[build] strict = true`, `--strict`) turns every untyped expression into `AL0220`.

With races ruled out, running code in parallel stops being dangerous. [Chapter 11](https://alice-lang.org/documentation/learn/parallelism.md) does that.

## Continue reading

- Previous: [9. Ownership and memory](https://alice-lang.org/documentation/learn/ownership.md)
- Next: [11. Parallelism without threads](https://alice-lang.org/documentation/learn/parallelism.md)
