Skip to content
Kavachकवच

inout Parameters

Also known as: inout ref out parameter pass by reference mutate argument swap pointer

● Beginner11 minBefore this: FunctionsSpec 3.9

A parameter marked inout lets the function change the caller's variable, and only when it succeeds.

fn bump(inout n: Int) {
    n += 1
}

fn swap(inout a: Int, inout b: Int) {
    const t = a
    a = b
    b = t
}

fn main() {
    let count = 0
    bump(count)
    bump(count)
    let x = 1
    let y = 2
    swap(x, y)
    print("{count} {x} {y}")
}

The program ran and printed:

kavach run inout.kav

2 2 1
inout.kav: Kavach code, then what running it prints

The call looks ordinary, bump(count), and the function changes its own copy; when it returns, the new value goes back into count. The variable must be a let:

fn bump(inout n: Int) {
    n += 1
}

fn main() {
    const count = 0
    bump(count)
    print(count)
}

The compiler refused the program:

kavach run inout-const.kav

error: `n` is `inout`, so `count` must be able to change: declare it with `let`, not `const` (spec 3.9)
inout-const.kav: Kavach code, then what running it prints

When the function fails after changing it

Here withdraw takes the money out first and checks afterwards. The second call fails after balance has already gone down to -80 inside it, and the caller handles the error with catch. What does the caller's balance hold now?

fn withdraw(inout balance: Int, amount: Int) ! String {
    balance -= amount
    if balance < 0 {
        fail "not enough money"
    }
}

fn main() {
    let balance = 50
    withdraw(balance, 30) catch e => print("failed: {e}")
    print(balance)
    withdraw(balance, 100) catch e => print("failed: {e}")
    print(balance)
}

The program ran and printed:

kavach run inout-fail.kav

20failed: not enough money20
inout-fail.kav: Kavach code, then what running it prints

Still 20. The function changes its own copy, and the copy goes back to the caller only when the function succeeds. A failed call leaves the caller's variable exactly as it was: never half-changed.

Fields and list items

Anything the caller could assign to works: an item of a list, or a field of a record held in a let.

type User {
    name: String
    age: Int
}

fn bump(inout n: Int) {
    n += 1
}

fn main() {
    let scores = [70, 80]
    bump(scores[0])
    print(scores)
    let user = User(name: "Asha", age: 31)
    bump(user.age)
    print(user.age)
}

The program ran and printed:

kavach run inout-places.kav

[71, 80]32
inout-places.kav: Kavach code, then what running it prints

Passing it on

An inout parameter is itself a place, so a function can pass it on as inout to another call, a recursive one too. Every change reaches the first caller's variable:

fn add_tax(inout price: Int) {
    price += price / 10
}

fn add_fee(inout price: Int) {
    price += 5
}

fn checkout(inout price: Int) {
    add_tax(price)
    add_fee(price)
}

fn count_down(n: Int, inout total: Int) {
    if n == 0 {
        return
    }
    total += n
    count_down(n - 1, total)
}

fn main() {
    let price = 100
    checkout(price)
    print(price)
    let total = 0
    count_down(4, total)
    print(total)
}

The program ran and printed:

kavach run inout-pass-on.kav

11510
inout-pass-on.kav: Kavach code, then what running it prints

checkout adds 10% tax, then a fee of 5: 100, then 110, then 115. count_down adds 4, 3, 2 and 1 through its own recursive calls.

Not into a callback

A lambda can't change an outside variable, an inout parameter included: it keeps a copy, so a change inside it doesn't reach the outside, and the compiler warns. A callback can be stored and called later, or run in another task, after the function that owns the variable has returned. A call finishes before its caller goes on, so passing the place to a call is safe.

fn add_all(items: [Int], inout total: Int) {
    const _seen = items.map(x => {
        total += x
        x
    })
}

fn add_all_right(items: [Int], inout total: Int) {
    total += items.sum(x => x)
}

fn main() {
    let total = 0
    add_all([1, 2, 3], total)
    print(total)
    add_all_right([1, 2, 3], total)
    print(total)
}

The compiler warned, then the program ran:

kavach run inout-lambda.kav

warning: `total` is a copy inside this lambda: the change doesn't reach the outside. Give the value back instead, or use `Shared` to share it (spec 3.3)06
inout-lambda.kav: Kavach code, then what running it prints
You wantWrite
Add up itemstotal += items.sum(x => x): the callback gives back a value
Change it step by stepa for loop: total += x changes the real variable
Many callbacks or tasks changing one valueShared<T>

Not the same variable twice

Giving one variable to two parameters of the same call would make two names for one value, so it is refused:

fn swap(inout a: Int, inout b: Int) {
    const t = a
    a = b
    b = t
}

fn main() {
    let x = 1
    swap(x, x)
    print(x)
}

The compiler refused the program:

kavach run inout-twice.kav

error: `x` is given as `inout` in this call, so it can't be another argument too (spec 3.9)
inout-twice.kav: Kavach code, then what running it prints

Not as a function value

A function with an inout parameter can only be called by its name: it can't be stored in a variable or passed to another function.

fn bump(inout n: Int) {
    n += 1
}

fn main() {
    const f = bump
    let n = 1
    f(n)
    print(n)
}

The compiler refused the program:

kavach run inout-value.kav

error: `bump` has `inout` parameters, so it can't be a function value (spec 3.9)
inout-value.kav: Kavach code, then what running it prints

Through an interface

An interface method can have inout parameters, and a call through an interface value changes the caller's variable too:

interface Counter {
    fn bump(self, inout n: Int)
}

type Step: Counter {
    size: Int

    fn bump(self, inout n: Int) {
        n += self.size
    }
}

fn main() {
    const counters: [Counter] = [Step(size: 1), Step(size: 10)]
    let total = 0
    for c in counters {
        c.bump(total)
    }
    print(total)
}

The program ran and printed:

kavach run inout-iface.kav

11
inout-iface.kav: Kavach code, then what running it prints

Not in a lambda

A lambda can't take an inout parameter. When a small function must change the caller's variable, give it a name:

fn main() {
    let count = 0
    const bump = (inout n: Int) => {
        n += 1
    }
}

The compiler refused the program:

kavach run lambda-inout.kav

error: a lambda can't have `inout` parameters (spec 3.9): give the function a name, as `fn bump(inout n: Int) { … }`
lambda-inout.kav: Kavach code, then what running it prints

When it panics

A panic is different: it is a bug, not an error, so no caller can catch it. The program stops there, and no variable is looked at again:

fn halve(inout n: Int, by: Int) {
    n = n / by
}

fn main() {
    let n = 10
    halve(n, 2)
    print(n)
    halve(n, 0)
    print(n)
}

The program ran, then stopped with a panic:

kavach run inout-panic.kav

5panic: division by zero
inout-panic.kav: Kavach code, then what running it prints
Go deeper the why, for readers who want it

Nothing is shared while the function runs, so no other part of the program can see half a change. If the function fails, the caller's variable keeps its old value. Giving the same variable twice in one call, as in swap(x, x), is refused: it would be two names for one value.