Ownership · Lesson 11.9

Defer return

Source
See that return keeps its value before deferred code runs, and use that to hand out a value and then advance.
You'll need: Defer, Mutating method

return and defer meet at the end of a function, and the order between them is fixed. return value; works out its value first and keeps it; only then do the deferred statements run. Whatever they change afterwards, the caller still receives the kept value.

That sounds like a detail, but it makes a useful pattern possible: "hand out the current value, then move on" becomes a two-line function, with no temporary variable to hold the old value.

The order at the end of a function

Countdown registers two deferred statements and then returns its local:

func Countdown() -> int32 {
    var remaining: int32 = 3;
    defer PrintLine("    deferred code sees remaining = {}", remaining);
    defer remaining = 0;
    return remaining;
}

Step by step, as the function ends:

flowchart LR
    r["return remaining<br/>works out 3<br/>and keeps it"] --> d1["defer remaining = 0<br/>(registered last, runs first)"]
    d1 --> d2["defer PrintLine(…)<br/>prints remaining = 0"]
    d2 --> c["the caller<br/>receives 3"]
countdown:
    deferred code sees remaining = 0
    returned 3

Two things are worth noticing in that output.

The PrintLine shows 0, not 3. A deferred statement is evaluated when it runs, not when it is registered — so it reads remaining after the reset has happened. And the reset did happen: the local really is 0. It simply makes no difference to the caller, who gets the 3 that return had already kept.

Hand out, then advance

A ticket dispenser shows a number. Taking a ticket gives you that number, and the display moves on to the next:

struct Dispenser {
    next: int32;
}

extend Dispenser {
    // Returns the current ticket, then advances the dispenser.
    func Take(self: &var Dispenser) -> int32 {
        defer self.next += 1;
        return self.next;
    }
}

return self.next keeps the current number; the deferred self.next += 1 then advances the dispenser. Without defer, the same method needs a temporary:

let ticket = self.next;
self.next += 1;
return ticket;

Both are correct. The defer version says what the method is for in its last line — return the current ticket — and puts the bookkeeping on the line before it.

var dispenser = Dispenser { next: 1 };
let first = dispenser.Take();
let second = dispenser.Take();

first is 1, second is 2, and the dispenser now shows 3. The method changes self, so it takes &var Dispenser, as in Mutating method.

StatementWhen it is evaluatedWhat the caller sees
return self.next;first — its value is keptthe kept value
defer self.next += 1;after the return value is keptthe change, on its next call

The program

The whole lesson is one package in the Examples repository. Its comments explain every step.

Src/Main.rux
// `return` and `defer` meet at the end of a function, and the order between them is fixed:
// `return value;` works out its value first and keeps it, and only then do the deferred
// statements run. Whatever they change afterwards, the caller still receives the kept value.
//
// That makes "hand out the current value, then move on" a two-line function. A ticket dispenser
// returns the number it is showing and advances to the next one, without a temporary variable to
// hold the old number.
import Io::PrintLine;

// Both deferred statements run after the return value is kept. In reverse order, so the reset
// runs first and the `PrintLine` then shows that the local really changed. The caller gets 3.
func Countdown() -> int32 {
    var remaining: int32 = 3;
    defer PrintLine("    deferred code sees remaining = {}", remaining);
    defer remaining = 0;
    return remaining;
}

struct Dispenser {
    next: int32;
}

extend Dispenser {
    // Returns the current ticket, then advances the dispenser.
    func Take(self: &var Dispenser) -> int32 {
        defer self.next += 1;
        return self.next;
    }
}

func Main() -> int {
    PrintLine("countdown:");
    let result = Countdown();
    PrintLine("    returned {}", result);

    PrintLine("tickets:");
    var dispenser = Dispenser { next: 1 };
    let first = dispenser.Take();
    let second = dispenser.Take();
    PrintLine("    took {} and {}, now showing {}", first, second, dispenser.next);
    return 0;
}

Run it

cd Examples/Ownership/DeferReturn
rux run
countdown:
    deferred code sees remaining = 0
    returned 3
tickets:
    took 1 and 2, now showing 3

Common mistakes

Expecting a deferred change to reach the caller.
defer remaining = 0; does reset the local, but Countdown() still returns 3. The return value was kept before any deferred statement ran. If the caller must see the change, make it before the return.
Expecting a deferred statement to remember old values.
defer PrintLine("{}", remaining); does not capture remaining at the defer line. It reads the variable when it runs, at the end of the function, after everything else has changed it.
Advancing through a read-only receiver.
With func Take(self: &Dispenser), the deferred self.next += 1; fails with error: cannot modify data through immutable reference '&Dispenser'. Deferred code is checked like any other code in the function: changing self needs &var.

Try it yourself

  1. Swap the two defer lines in Countdown. What does the deferred PrintLine show now, and does the returned value change?
  2. Add a method Peek(self: &Dispenser) -> int32 that returns the next ticket without taking it, and print it between the two Take calls.
  3. Write func Next(counter: &var int32) -> int32 that returns the counter's current value and then adds 1 to it, using defer. Call it twice on a var that starts at 5.

Learn more