Functions
Functions are named pieces of a program that take input and give back a result. They are how you break a program into small parts that each do one job, can be read on their own and can be used again.
Declaring and calling
A function is declared as func name(parameters): result type: and its body ends with end. Every parameter is written as name: type, and ret hands the result back to the caller. A function that gives nothing back simply leaves out the result type.
func line total(price: int, quantity: int): int:
ret price * quantity
end
func show line(name: str, amount: int):
println name + ":", amount
end
func main:
tea := line total(45, 2)
cake := line total(120, 1)
show line("tea", tea)
show line("cake", cake)
show line("total", tea + cake)
endThe order of functions in a file does not matter: main may call a function that is declared further down. A call always needs its parentheses, even when there are no arguments.
Returning early
ret leaves the function at once, wherever it stands. That lets you deal with the bad cases first, each with its own ret, and keep the normal path at the bottom without deep nesting. In a function with no result type, a bare ret just leaves.
func check username(name: str): str:
if len(name) < 3:
ret "too short"
end
if len(name) > 12:
ret "too long"
end
if name == "admin":
ret "reserved"
end
ret "ok"
end
func greet(name: str):
if check username(name) != "ok":
println "cannot greet", name
ret
end
println "welcome,", name
end
func main:
println check username("al")
println check username("administrator")
println check username("admin")
greet("mina")
greet("x")
endA function with a result type must reach a ret on every path. If it can fall off its end, the compiler stops with E071 and asks for a final ret.
Default values
A parameter with a default value is optional at the call. Put such parameters after the ones without a default.
func welcome(name: str, greeting: str = "Hello"):
println greeting + ",", name
end
func area(width: int, height: int = 1): int:
ret width * height
end
func main:
welcome("Mina")
welcome("Mina", "Good day")
println area(4), area(4, 5)
endA default value must be a constant or built from globals; it cannot refer to another parameter, so func box(w: int, h: int = w) fails with "unknown identifier". Write an overload instead (see below). Salam has no named arguments: the arguments always go in the order of the parameters.
Inferring the result type
Leave the result type out and Salam works it out from the ret statements. This is handy for short helpers; for a function others will call, writing the type out makes it easier to read.
func average(a: f64, b: f64):
ret (a + b) / 2.0
end
func larger(a: int, b: int):
if a > b:
ret a
end
ret b
end
func main:
println average(3.0, 4.0), larger(8, 3)
endReturning several values
A function returns one value. When you need to give back more than one, put them in a small struct and return that. The caller reads the parts by name, which is clearer than remembering an order. Structs have a lesson of their own; here all you need is that the fields are marked pub so that code outside the struct can read them.
struct Clock:
pub hours: int
pub minutes: int
end
func split minutes(total: int): Clock:
ret Clock { hours = total / 60, minutes = total % 60 }
end
func main:
trip := split minutes(135)
println trip.hours, "h", trip.minutes, "min"
println split minutes(45)
endRecursion
A function may call itself. Each call gets its own copy of the parameters, and a recursive function always needs a base case, a condition where it returns without calling itself again. Here that is n < 2.
func fibonacci(n: int): int:
if n < 2:
ret n
end
ret fibonacci(n - 1) + fibonacci(n - 2)
end
func main:
repeat 10 in i:
print fibonacci(i), ""
end
println ""
endThis version of fibonacci is easy to read but slow: every call makes two more, so the work roughly doubles with each step of n. For large inputs, a loop that carries the last two values is far faster. Very deep recursion can also run out of stack, so prefer a loop when the depth grows with the size of the input.
Overloading
Several functions may share a name as long as their parameters differ, either in type or in number. Salam picks the right one from the arguments at compile time.
func double(number: int): int:
ret number * 2
end
func double(text: str): str:
ret text + text
end
func double(text: str, separator: str): str:
ret text + separator + text
end
func main:
println double(21)
println double("ha")
println double("ha", "-")
endMulti-word names
A function name can be several words, so that a call reads like a sentence. Every word is part of the name, so none of them may be a keyword: func print line or func each item is rejected because of print and each.
func is weekend(day: int): bool:
ret day == 6 or day == 7
end
func main:
println is weekend(6), is weekend(2)
endReference parameters
Parameters are copied by default, so changing one inside the function is invisible outside it. Write &: after the parameter name and the function works on the caller's own variable and can change it. The variable you pass should be declared mut. A reference parameter also saves copying a large struct on every call.
func increase(number &: int):
number = number + 1
end
func no_effect(number: int): int:
ret number + 1
end
func main:
mut score := 10
increase(score)
increase(score)
println score, no_effect(score), score
endPrefer returning a value when you can: score = next score(score) shows what changes right at the call. Use &: when a function updates something in place, such as a struct or a Vector.
Functions as values
A function can also be handed to another function. The bare name of a named function, without parentheses, is its address in memory, typed i64; that is the form C-style callback registries take. To pass a function to a parameter with a function type such as func (int) int, wrap it in a short anonymous function. The next lesson covers both.
main and the exit code
main is where the program starts, and what it returns is the program's exit code. Falling off its end, or a bare ret, exits with 0, which means success. ret 1 (or any other number) reports a failure to the shell or the script that started the program. Error messages go through printerrln, so they do not mix with the normal output.
func enough stock(stock: int, ordered: int): bool:
if ordered > stock:
printerrln "not enough stock:", ordered, "ordered,", stock, "left"
ret false
end
ret true
end
func main:
defer println "order log closed"
if not enough stock(12, 5):
ret 1
end
println "order accepted"
endWith 20 ordered and only 12 in stock, the program would print the message to the error stream and exit with 1. The deferred line still runs either way. Calling os.Exit inside main is an error (E109), because it would skip main's defer statements, while ret runs them. The defer lesson explains those.
Rules of thumb
- Give each function one job and a name that says what it does.
- Handle the bad cases first, each with an early
ret. - Return a struct to give back several values.
- Make sure every path of a function with a result type ends in
ret. - Report failure from
mainwithret 1, not withos.Exit.