9. Functions and methods
9.1 Declaration and calls#
pub func clamp(value: f64, minimum: f64, maximum: f64) -> f64:
if value < minimum: return minimum
if value > maximum: return maximum
return value
func render(scene: Scene*, samples: u32 = 64, denoise: bool = true) -> Image!:
...
let image = try render(&scene, samples = 256, denoise = false)
- Parameter types and every non-
unitresult are explicit. A missing->meansunit. - Arguments are positional or named with
name = value; positional ones come first. A default is a compile-time constant embedded at the call site. Duplicate, unknown, and missing arguments are compile errors. - One scope holds at most one callable with a given name: no overloading. Alternatives get semantic names:
Image.from_file,Image.from_bytes. - No variadic Base functions in this revision (§20). Calls to variadic C functions are §17.2.
- Recursion is allowed; running out of stack is a trap, never a crash.
inline funcis a hint. An implementation without an inliner ignores it.- A function that never returns is declared
-> never;extern func abort() -> neverbinds C's_Noreturn.
Why = for named arguments. : already means "has this type" everywhere in the language. Using it for "takes this value" would put two relations behind one symbol at exactly the two places a reader confuses them.
9.2 Parameters and the calling convention#
A T[N] parameter is a value copy. A T[] parameter is a lent view. A T* parameter is a pointer. There is no inout; pass a pointer. Every Base function whose signature is C-representable (§17.6) uses the target C calling convention whether or not it is exported, so its address may be handed to C. Sub-word parameters (bool, i8, u8, i16, u16, c.char, c.short) use the ABI's sub-word rules, which differ between AAPCS64 and Apple arm64; the backend handles them.
9.3 Multiple results#
func divide(value: i64, divisor: i64) -> (i64, i64):
return (value // divisor, value % divisor)
let (quotient, remainder) = divide(17, 5)
Tuples replace C's out-parameters for the common case. Public data with names should be a struct.
9.4 Function values#
let operation: func(i64, i64) -> i64 = add
let result = operation(2, 3)
A function value is a C function pointer (§5.6). A non-fallible function converts to the corresponding fallible function type; nothing else converts.
9.5 Methods#
struct Point:
pub let x: f64
pub let y: f64
func distance_to(self, other: Point) -> f64:
let dx = self.x - other.x
let dy = self.y - other.y
return math.sqrt(dx * dx + dy * dy)
func origin() -> Point:
return Point(0.0, 0.0)
struct Cursor:
pub var position: usize
mutating func advance(self, amount: usize):
self.position += amount
- A function declared inside a type with an explicit first
selfparameter is a method;point.distance_to(other)passespointasself. A function withoutselfis called through the type:Point.origin(). There is nostatickeyword. mutatingmarks a method that assignsvarfields or replacesself. Its receiver at the call site must be avar, a mutable pointer, or a mutable span element.- A non-
mutatingmethod receivesselfasconst Self*; amutatingmethod receivesselfasSelf*. This is deterministic so that exported headers are stable. Becauseselfaliases the receiver, a callee that mutates the receiver through another pointer changes whatself.xreads mid-method.self = valuein a mutating method stores through the pointer. - A method is callable on a value, a
var, or a pointer;p.advance(3)onp: Cursor*needs no dereference. Calling on an rvalue materialises a temporary. value.memberwithout()is always a field. There are no computed properties.- A method named
initis the initialiser (§10.1).
Why self is a pointer. In full Luce a non-mutating method receives a copy, which is safe under reference counting and invisible to the caller. In Base a copy of a large struct on every method call is a cost C programmers would notice, and a struct method in C3, Zig, and Odin takes a pointer. Making the convention deterministic, rather than "by value if small", is what lets the generated C header say const Point* and mean it.
9.6 Closures#
There are none. A capture-free lambda (x) => x + 1 converts to a function pointer. A callback that needs state is written the way C writes it: a function pointer beside a void* or T* context.
Why. A closure that captures by reference needs its environment to outlive the frame, and without reference counting there is no owner for that environment. Every manual-memory language that offers closures either restricts them to non-escaping, or hands the programmer an allocator at closure creation. Both are plausible later; neither belongs in the first revision.
9.7 The entry point#
pub func main(arguments: str[]) -> i32:
print("Hello")
return 0
main takes str[] or cstr[] and returns i32 or i32!. The startup shim builds arguments from argc and argv; with str[] it validates each argument as UTF-8 and traps invalid_utf8 on failure, and a program that must accept arbitrary bytes declares cstr[]. A returned error is reported and becomes exit status 1.