The Crow Programming Language

Function Scope Isolation

Here is the rule, and it is stricter than Python's:

Module variables are invisible inside functions. A function sees its parameters, its own locals, other functions, structs, and imported vocabulary — nothing else. Data enters through parameters and leaves through the return value.

count = 5

fn bad[]
    return count + 1    --- error: 'count' is a module variable, which a
                        --- function does not see — pass it as a parameter

The fix is always the same shape:

1
2
3
4
5
6
count = 5

fn bump[n]
    return n + 1

log[bump[count]]

expected output — from the book, verified by crow test --book

6

A local that happens to reuse a module name shadows nothing — module variables were never visible, so it's simply a fresh binding. No global keyword, no nonlocal, no scope-resolution table to memorize.

Why So Strict?

Two reasons, and they're both load-bearing:

  1. A signature that tells the whole truth. If a function can secretly read ambient state, its behavior depends on things not in its signature — refactoring, testing, and crow check all get harder. Parameters-in, return-out means every dependency is visible at the call site.
  2. Parallelism needs it. Chapter 6's parallel loops work because every read and write in the loop body is visible at the call site. Hidden reads would turn "parallel by default" into "data races by default." Crow chose the scope rule that makes the loop rule sound.

The one sanctioned exception is reactive state in web UIs (view blocks and event handlers, chapter 12) — a managed channel, synchronized by the runtime, not ambient mutation.