DriftScript
Documentation

Rationale

Why immutable is the default

What it serves, and why mut is a property of a parameter rather than of a type.

Why is immutable the default?

Because four separate things want it at once, and no one of them would be enough on its own: reasoning about a function, reloading code while its state is alive, running tasks that share a value, and reviewing whether a system touched only what it declared.

It is what makes the rest checkable

A compiler that can tell a mutation from a read is what lets a system declare what it writes and be held to it. Mutability that is assumed everywhere makes that declaration unenforceable, so the analysis would be advisory, which is another word for wrong eventually.

// A record, a mutation, and the two things the compiler will not let you skip.
//
// `door` is `mut`, so its fields may be written. `by` is not, so it may only be read. A record
// literal gives every field a value: there is no partial construction.

import { clamp } from "std/math"

data Door {
    open: bool = false
    angle: f32 = 0
}

fn swing(door: mut Door, by: f32) {
    door.angle = math.clamp(door.angle + by, 0, 180deg)
    if door.angle > 45deg {
        door.open = true
    }
}

fn closed() -> Door {
    return Door { open: false, angle: 0 }
}

Why mut is on the parameter

Were a mutable record a different type, every signature accepting either would double, and a consumer's own type would fork the first time somebody needed to read one and write another. On the parameter it stays one type, and the question moves to the place that answers it.

What it costs

A var where you wanted a let, and the annoyance of discovering that at the third line rather than the first. Also a genuine limit: a function that wants to mutate through two parameters has to say so twice, and there is no way to say "and everything reachable from this one".