Function
A function gives a piece of work a name. You write the work once, and then any part of the program can run it by calling that name — with different values each time. Main has been a function since the very first lesson; this lesson writes others and calls them from Main.
Functions are how a program stays readable as it grows. CelsiusToFahrenheit(100.0) says what it does, while 100.0 * 9.0 / 5.0 + 32.0 makes you work it out.
The shape of a function
Every function has the same parts: func, a name, the parameters in parentheses, -> and the type of the result, then a body in braces that hands the result back with return:
func Add(left: int, right: int) -> int {
return left + right;
}
| Part | In Add | Meaning |
|---|---|---|
| Name | Add | How callers refer to it — PascalCase, like Main |
| Parameters | left: int, right: int | The values the caller must supply, each typed |
| Result type | -> int | The type of the value the function gives back |
| Body | { return left + right; } | The work, ending in return |
Calling a function
A call is the name followed by one argument per parameter, in the same order. Each argument becomes the value of its parameter for that one run of the body, and the value after return replaces the call:
flowchart LR
call["Add(2, 3)"] --> bind["left = 2<br/>right = 3"]
bind --> body["return left + right;"]
body --> result["the call is<br/>worth 5"]The result type is whatever the work produces. Here two float64 values go in and one float64 comes out:
func Average(first: float64, second: float64) -> float64 {
return (first + second) / 2.0;
}
Arguments must match the parameter types exactly — nothing is converted for you. Square(2.5) is refused because Square takes an int and 2.5 is a float64.
Parameters cannot change
Inside the body a parameter behaves like a let binding: you can read it, but not assign to it. A function that needs a running value declares a local var of its own:
func SumUpTo(last: int) -> int {
var total = 0;
for i in 1..=last {
total += i;
}
return total;
}
Each call gets its own fresh total, so calling SumUpTo twice never mixes the two sums.
A call is a value
Anywhere a value of the result type fits, a call fits too. It can be bound to a name, or passed straight into another call:
let side = Add(4, 5);
PrintLine("Add(Square(3), Square(4)) {}", Add(Square(3), Square(4)));
The inner calls run first: Square(3) is 9, Square(4) is 16, and only then does Add run, with 9 and 16 as its arguments.
The program
The whole lesson is one package in the Examples repository. Its comments explain every step.
// A function gives a piece of work a name, so it can be written once and run
// from as many places as need it. `Main` has been one since the first lesson;
// this lesson writes others and calls them from `Main`.
//
// The shape is always the same: `func`, a name, the parameters in parentheses,
// `->` and the type of the result, then a body that hands the result back with
// `return`.
import Io::PrintLine;
// Every parameter is written `name: type`, separated by commas. A call must
// supply one argument for each, in the same order.
func Add(left: int, right: int) -> int {
return left + right;
}
func Square(value: int) -> int {
return value * value;
}
// The result type is whatever the work produces. Here two `float64` values go
// in and one `float64` comes out.
func Average(first: float64, second: float64) -> float64 {
return (first + second) / 2.0;
}
func CelsiusToFahrenheit(celsius: float64) -> float64 {
return celsius * 9.0 / 5.0 + 32.0;
}
// Parameters are immutable inside the body, exactly like `let` bindings, so a
// running value needs a local `var` of its own.
func SumUpTo(last: int) -> int {
var total = 0;
for i in 1..=last {
total += i;
}
return total;
}
func Main() -> int {
PrintLine("Add(2, 3) {}", Add(2, 3));
PrintLine("Square(7) {}", Square(7));
PrintLine("Average(4.0, 7.0) {}", Average(4.0, 7.0));
PrintLine("CelsiusToFahrenheit(100.0) {}", CelsiusToFahrenheit(100.0));
PrintLine("SumUpTo(10) {}", SumUpTo(10));
// A call is an ordinary value. It can be bound to a name...
let side = Add(4, 5);
PrintLine("side {}", side);
// ...or passed straight into another call. The inner call runs first.
PrintLine("Square(Add(1, 2)) {}", Square(Add(1, 2)));
PrintLine("Add(Square(3), Square(4)) {}", Add(Square(3), Square(4)));
// Arguments must match the parameter types. `Square(2.5)` is rejected,
// because `2.5` is a `float64` and `Square` takes an `int` — the value is
// not converted for you. And assigning to a parameter inside its function
// is an error:
//
// func Bump(n: int) -> int { n = n + 1; return n; }
// error: cannot modify parameter 'n'
return 0;
}
Run it
cd Examples/Functions/Function
rux run
Add(2, 3) 5
Square(7) 49
Average(4.0, 7.0) 5.5
CelsiusToFahrenheit(100.0) 212.0
SumUpTo(10) 55
side 9
Square(Add(1, 2)) 9
Add(Square(3), Square(4)) 25
Common mistakes
Square(2.5) fails with error: argument 1 to 'Square' has type 'float64', but parameter 'value' requires 'int'. Pass a value of the parameter's type, or convert it with as as in Convert.Add(2) fails with error: call to 'Add' expects 2 arguments, but 1 was provided. Every parameter needs an argument — until Default argument shows how to make one optional.func Bump(n: int) -> int { n = n + 1; return n; } fails with error: cannot modify parameter 'n'. Copy the parameter into a local var and change that instead, as SumUpTo does with total.Names are case-sensitive:
square(2) fails with error: name 'square' is not defined in this scope, and the compiler suggests did you mean 'Square'?.Try it yourself
- Write
Cube(value: int) -> intthat returnsvalue * Square(value), and printCube(3). - Write
FahrenheitToCelsiusand check that it turns212.0back into100.0. - Write
Product(last: int) -> intthat multiplies the numbers from 1 tolast, following the shape ofSumUpTo. - Call
Square(2.5)andAdd(2)and read both messages.
Learn more
- Functions and Function declaration in the Rux Reference
- The Main entry point — the function every program starts in
- Return — leaving a function early, and functions with no result