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

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