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 1The 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)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
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
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
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
| You want | Write |
|---|---|
| Add up items | total += items.sum(x => x): the callback gives back a value |
| Change it step by step | a for loop: total += x changes the real variable |
| Many callbacks or tasks changing one value | Shared<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)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)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
11Not 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) { … }`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
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.