The Anatomy of a Function
The general shape of a Zero function:
fun name(param1: Type1, param2: Type2) -> ReturnType {
// body
return value
}
Pieces:
fun— the keyword that introduces a function.name— the function's name.(param1: Type1, ...)— parameter list. Every parameter has an explicit type.-> ReturnType— the return type.{ ... }— the body, a block of statements.return value— exits the function withvalue.
A small concrete example:
fun double(value: i32) -> i32 {
return value * 2
}
That's the entire function. It takes one i32, returns another, and does no I/O. From inside the body, value is a let-style binding — you can use it like any other local.
Calling Functions
Calls look exactly like you'd expect:
let result = double(21)
The argument has to match the parameter type. The result is bound to result, which the compiler infers to be i32 because double returns i32.
A worked example tying a helper and a main together — click Run to see it in action:
You should get math works\n on stdout.
pub and Visibility
By default, a function declared in a file is private to that file (or module — visibility rules tighten up as projects grow). To expose a function outside its module, prefix it with pub:
pub fun greet() -> String {
return "hello\n"
}
Without pub, code in other modules can't call greet. The runtime needs to call main from outside any user-defined module, which is why main is always pub.
The default-private rule is a good one to lean into. Mark only what you intend to be an interface; the rest stays inside the module.
Return Types
Every function declares its return type after ->. Common return types:
fun answer() -> i32 { return 42 }
fun ok() -> bool { return true }
fun label() -> String { return "ready\n" }
fun nothing() -> Void { }
Void is the return type for a function that does its work through side effects rather than producing a value. A Void function doesn't need an explicit return — falling off the end of the body is enough.
fun log(world: World, message: String) -> Void raises {
check world.out.write(message)
}
Function Calls That Discard a Value
If a function returns a value and you don't care about it, you still have to acknowledge the return. The idiom is to bind it with let:
ignored is a binding the rest of the function never reads. The convention of using the name ignored (or _) signals that the discard is intentional. This is more friction than silently dropping a return value, which is the point: in a language where agents are reading and generating code, an unread value is often a bug worth surfacing.
The Role of raises
A function that might fail declares it on the signature. We saw this on main:
pub fun main(world: World) -> Void raises {
check world.out.write("hello\n")
}
The raises clause can be bare (any error) or specific:
fun validate(ok: Bool) -> i32 raises { InvalidInput } {
if ok == false {
raise InvalidInput
}
return 42
}
raises { InvalidInput } means "this function can fail with InvalidInput, and nothing else." Callers must use check (or a more elaborate handling form) to propagate or handle the error.
Raises and Check goes into this in depth, including what happens with multiple error types and how check interacts with the caller's raises clause.
Generic Functions
When you want a function to work over more than one type, declare type parameters in angle brackets:
fun makePair<T, U>(left: T, right: U) -> Pair<T, U> {
return Pair { left: left, right: right }
}
T and U are type parameters — the caller decides what they are. Calling makePair(40, 2_u8) gives you a Pair<i32, u8>. See Generics for the full story, including generic shapes and constraints.
Where Functions Live
In a small program, you write functions directly in your .0 file. In a package, you spread functions across files under src/ and the compiler resolves cross-file references for you. The fundamentals stay the same — fun, parameters, return type, body — regardless of where the function physically lives.
Style Notes
A few conventions you'll see in the official examples:
- Lowercase function names, words run together (
makePair) or separated by camelCase. The standard library leans camelCase. - One return value per function. If you need to return multiple things, build a small
shapefor them — that's clearer than returning a tuple-of-pair-of-tuples. Voidfunctions only docheckcalls; functions that compute a value avoid I/O when they can. That separation is partly cultural and partly enforced — a pure computation function doesn't takeworldand so literally cannot do I/O.
The last point is worth dwelling on. Because I/O lives behind the World capability and World is passed in explicitly, a function's signature tells you whether it might do I/O. Functions whose signatures don't mention World are pure with respect to the outside world. That's a property agents (and humans) can rely on without reading the body.
Next: If/Else
You've seen if show up in passing — the next doc covers if/else expressions in detail, including how they interact with bindings and what's missing on purpose (no truthy coercion, no ternary).
Frequently Asked Questions
How do you declare a function in Zero?
Use fun: fun name(param: Type) -> ReturnType { body }. Add pub in front to make the function visible outside its module. Add raises after the return type if the function can fail. For example: pub fun double(value: i32) -> i32 { return value * 2 }.
What does the pub keyword do?
pub keyword do?pub marks a declaration as public — visible to code outside its current module. Without pub, a function is private to the file (or package) it's declared in. The conventional entry point pub fun main has to be public so the runtime can find and call it.
How do you return a value from a function in Zero?
Write return value inside the function body. The expression has to match the declared return type. A function with return type Void returns nothing and doesn't need an explicit return statement — falling off the end is fine.
Can Zero functions take multiple parameters?
Yes. List them in parentheses separated by commas, each with its name and type: fun add(a: i32, b: i32) -> i32 { return a + b }. Each parameter is a let-style binding in the function body. Zero requires explicit types on parameters — there's no parameter-type inference at function declarations.
What does raises mean on a function signature?
raises mean on a function signature?raises declares that the function can fail. A bare raises allows any error type; raises { InvalidInput } restricts it to a specific named error. Callers must use check (or another fallibility construct) to acknowledge the possibility of failure — they can't silently ignore it.