Aiming for Jarvis, Creating D.A.N.I.

Tutorial 4: Functions (or, Why we don't copy-paste our logic everywhere)

If you've followed along this far, you've probably noticed that writing everything inside main() gets messy—fast. It’s the programming equivalent of throwing all your spare parts into a single cardboard box and hoping you don't need to find a specific bolt in a hurry. Functions are how we organize that mess into something that resembles a coherent machine. Let's tidy up the workshop.

Defining a function

A function is just a named block of code that does one thing and (ideally) does it well. We use fn to tell the compiler, "Hey, I'm defining something new here, pay attention."

fn greet(name: string) {
    print("Hello, {name}!")
}

fn main() -> int {
    greet("Skink")
    return 0
}

If you don't specify a return type, Skink assumes you're just doing some "side effect" work—like printing—and returning nothing. It's the programming equivalent of shouting into the void.

Returning values: Let's get actual work done

If you want a function to talk back to you, you need to tell it what type of data to send home. Put that type after the parameter list, separated by ->.

fn add(a: int, b: int) -> int {
    return a + b
}

Simple, clean, and—unlike my early attempts at recursive logic for a pathfinding algorithm—won't accidentally lock up your CPU for an hour.

The joy of multiple returns

Sometimes a single return value just isn't enough. Maybe you need to return a result and a status code to tell the caller that everything didn't go completely sideways.

fn divide(a: float, b: float) -> (float, bool) {
    if b == 0.0 {
        return 0.0, false // Oops, someone tried to divide by zero.
    }
    return a / b, true
}

fn main() -> int {
    result, ok := divide(10.0, 2.0)
    if ok {
        print("Result: {result}")
    } else {
        print("Cannot divide by zero")
    }
    return 0
}

Note how we handle that return tuple. It keeps the error checking right where it belongs: near the operation that might actually fail.

Local variables: What happens in the function, stays in the function

Variables declared inside a function are local. They live and die with that function call. If you try to access them from outside, you're going to have a bad time. It’s a good way to keep your memory space tidy, especially when things get complicated and you forget what you named that temporary counter three hours ago.

Exercises

Don't let these gather dust:

  1. The Maximum Check: Write a function max(a: int, b: int) -> int that returns the larger of the two. It's a classic, but essential.
  2. The Great Swap: Write a function swap(a: int, b: int) -> (int, int) that returns the arguments in reverse order (b, a).
  3. Geometry Class: Create two functions: area(w, h) and perimeter(w, h). Call them both from main with some arbitrary values and print the results.

Next

When you're ready to start building more complex things than simple calculations, we'll talk about Structs and methods.