Functions
Functions are Kestrel's basic unit of behavior. They take parameters (positional or labeled), return a value (or none), and can be passed around as values themselves.
Definitions
A function is declared with func, a name, parameters, and a return type:
func add(x: Int, y: Int) -> Int {
x + y
}
The body is a block of statements. The last expression in the block is the return value — no return keyword needed unless you're returning early. For one-liners, you can use the expression-bodied form:
func double(x: Int) -> Int = x * 2
A function that returns nothing has no return type:
func greet(name: String) {
println("Hello, \(name)!")
}
Parameters
A parameter has a bind name (used inside the body) and an optional label (used at the call site). The two forms are:
// bind name
func add(x: Int, y: Int) -> Int { x + y } // positional — no labels
// label bind name
func send(to recipient: String, body content: String) { /* ... */ } // labeled
A bare name: Type declares a positional parameter — there's no label, and the call site doesn't write one:
add(3, 4);
A label name: Type declares a labeled parameter. The call site uses the label:
send(to: "alice@example.com", body: "hello");
That's the rule: one name = positional, two names = label then bind name. There's no "label and name are the same" shorthand — if you want both to be to, you write to to: String.
Overloading
Functions can be overloaded by their labels:
struct User { let name: String }
func find(named name: String) -> Optional[User] { /* look up by name */ .None }
func find(id id: Int) -> Optional[User] { /* look up by id */ .None }
find(named: "alice"); // calls the (named:) version
find(id: 42); // calls the (id:) version
The labels are part of the signature, and they're the only thing overloads can differ by (besides arity). Two functions with the same labels that differ only in parameter type are rejected:
<!-- sample: fails E426 -->struct User { let name: String }
func find(by name: String) -> Optional[User] { .None }
func find(by id: Int) -> Optional[User] { .None } // error: duplicate function signature [E426]
Return Types
The arrow -> separates the parameter list from the return type:
struct User { let name: String }
func square(x: Int) -> Int = x * x
func nameOf(user: User) -> String { user.name }
A function that never returns — because it loops forever, calls a panic, or always throws — has the never type, written !:
func crash(message: String) -> ! {
fatalError(message)
}
! is a bottom type. It's compatible with every other type, which is why this is allowed:
let hasPort = true;
let port: Int = if hasPort {
8080
} else {
crash("port required") // never returns, so the if's type is just Int
};
Choosing labels
Labels are how Kestrel call sites stay readable. Pick labels that make the call read naturally:
struct Point { let x: Int }
let home = Point(x: 0);
let office = Point(x: 5);
// Good — reads like English
func move(from start: Point, to end: Point) { /* ... */ }
move(from: home, to: office);
// Less good — call site is just type-shaped, no clarity gained
func move(start: Point, end: Point) { /* ... */ }
move(home, office); // positional; have to remember which is which
When the parameter is the obvious main argument and a label adds nothing, leave it positional:
struct User { let name: String }
let alice = User(name: "Alice");
func describe(user: User) -> String { user.name }
describe(alice);
A heuristic: if your call site reads like prose (send(to: alice, body: ...)), labels are pulling weight. If labels just repeat the type name, positional is cleaner.
Subpages
- Access Modes —
mutatingandconsumingparameters - Methods — functions defined on a type
- Closures — anonymous functions and capture
- Operator Overloading — defining
+,==, etc. on your own types