Skip to main content

Function Declarations

Functions are the primary building blocks for organizing code in Sector9. This page covers the basic func syntax, parameters, return types, body forms, and where verification contracts attach.

Basic Syntax

A function declaration uses the func keyword followed by a name, parameters, return type, and body:

func name(param1 : Type1, param2 : Type2) : ReturnType {
// body
}

The simplest function takes parameters and returns a value:

func add(x : Nat, y : Nat) : Nat {
x + y
}

Functions with no parameters use empty parentheses:

func zero() : Nat {
0
}

Parameters

Typed Parameters

Every parameter requires a type annotation. Multiple parameters are separated by commas:

func greet(name : Text, age : Nat) : Text {
// use name and age
}

Single Parameter

A function with one parameter:

func double(n : Int) : Int {
n * 2
}

No Parameters

Functions with no parameters still require parentheses:

func getTimestamp() : Nat {
0 // placeholder
}

Return Types

Explicit Return Types

The return type follows the parameter list after a colon:

func isEven(n : Nat) : Bool {
n % 2 == 0
}

Unit Return Type

Functions that return no meaningful value use () (unit):

func logMessage(msg : Text) : () {
// side effect only
}

Async Return Types

Public actor methods return async types to support message passing:

public func getData() : async Nat {
42
}

See async functions and await for actor-message control flow.

Body Forms

Sector9 supports two body forms: block bodies and expression bodies.

Block Body

The block body uses curly braces and can contain multiple statements. The last expression is the return value:

func compute(x : Nat, y : Nat) : Nat {
let sum = x + y;
let doubled = sum * 2;
doubled // returned
}

Expression Body

For simple functions, use = followed by a single expression:

func square(n : Nat) : Nat = n * n;

This is equivalent to:

func square(n : Nat) : Nat {
n * n
}

Expression bodies work well for short computations:

func isPositive(x : Int) : Bool = x > 0;
func triple(n : Int) : Int = n * 3;
func maxOf(a : Int, b : Int) : Int = if (a >= b) { a } else { b };

Visibility Modifiers

Public Functions

public makes a function accessible from outside the module or actor:

module {
public func exported() : Nat { 42 }
}

In actors, public functions become callable methods:

persistent actor {
public func greet() : async Text {
"Hello"
}
}

Private Functions

Functions without public are private by default:

persistent actor {
// Private - only callable within this actor
func helper(x : Nat) : Nat {
x + 1
}

public func useHelper(n : Nat) : async Nat {
helper(n)
}
}

Use private explicitly for clarity:

private func internal(x : Nat) : Nat {
x * 2
}

Functions in Different Contexts

Module Functions

Functions in modules are local by default:

module Math {
public func add(a : Nat, b : Nat) : Nat {
a + b
}

// Private helper
func clamp(x : Nat, max : Nat) : Nat {
if (x > max) { max } else { x }
}
}

Actor Methods

Public functions in actors become shared methods that return async types:

persistent actor Counter {
var count : Nat = 0;

public func increment() : async Nat
modifies count
ensures result == count;
{
count += 1;
count
}
}

Actor methods exposed as calls return async types. State-changing public methods also need accurate modifies clauses, and callers that depend on state preconditions should get entry_requires as well as internal requires.

Query Functions

Use query for fast actor methods that are intended to be read APIs:

persistent actor {
var balance : Nat = 0;

public query func getBalance() : async Nat
reads balance
ensures result == balance;
{
balance
}
}

Prefer keeping query functions read-only. If a query body writes actor fields, current verification requires an explicit modifies clause; do not rely on query execution for durable consensus state changes.

Verification Contracts

Functions can include verification contracts in the signature. See Contracts for full details.

Preconditions

requires specifies what must be true when the function is called:

func divide(a : Int, b : Int) : Int
requires b != 0;
{
a / b
}

Postconditions

ensures specifies what must be true when the function returns:

func abs(x : Int) : Int
ensures result >= 0;
{
if (x >= 0) { x } else { -x }
}

Effect Annotations

reads and modifies declare which actor fields the function accesses:

persistent actor {
var total : Nat = 0;

public func add(amount : Nat) : async Nat
modifies total
ensures total == old(total) + amount;
{
total += amount;
total
}
}

Contract Placement

Contracts appear after the return type annotation but before the function body:

func name(params) : ReturnType
requires precondition1;
requires precondition2;
ensures postcondition1;
ensures postcondition2;
{
// body
}

For actor methods with effects:

public func update(x : Nat) : async ()
reads field1
modifies field2
entry_requires x > 0;
requires x > 0;
ensures field2 == old(field2) + x;
{
// body
}

Common Patterns

Returning Early with Conditionals

The last expression is the return value:

func classify(n : Int) : Text {
if (n < 0) {
"negative"
} else if (n == 0) {
"zero"
} else {
"positive"
}
}

Helper Functions

Use private functions to break up complex logic:

persistent actor {
var balance : Nat = 0;

private func validate(amount : Nat) : Bool {
amount > 0 and amount <= 10000
}

public func deposit(amount : Nat) : async Bool
modifies balance
entry_requires amount <= 10000;
requires amount <= 10000;
{
if (validate(amount)) {
balance += amount;
true
} else {
false
}
}
}

Chaining Function Calls

module {
public func step1(x : Nat) : Nat { x + 1 }
public func step2(x : Nat) : Nat { x * 2 }
public func step3(x : Nat) : Nat { x - 1 }

public func pipeline(x : Nat) : Nat {
step3(step2(step1(x)))
}
}

Common Mistakes

Missing Async for Actor Methods

persistent actor {
// WRONG - public actor methods need async
public func bad() : Nat {
42
}

// CORRECT
public func good() : async Nat {
42
}
}

Shared Functions Outside Actors

// WRONG - shared functions must be in actors
shared func badShared() : async () {
// rejected
}

// CORRECT - inside an actor
persistent actor {
public func goodShared() : async () {
// OK
}
}

Contracts Inside Body

// WRONG - contracts go in the signature
func bad(x : Nat) : Nat {
requires x > 0; // syntax error
x
}

// CORRECT
func good(x : Nat) : Nat
requires x > 0
{
x
}

Missing Modifies Clause

persistent actor {
var x : Nat = 0;

// WRONG - modifying x without declaring it
public func bad() : async () {
x := 1; // verification error
}

// CORRECT
public func good() : async ()
modifies x
{
x := 1;
}
}

Summary

  • Functions use func keyword with name, parameters, return type, and body
  • Block bodies use { } and return the last expression
  • Expression bodies use = expression for simple functions
  • public makes functions accessible outside the module or actor
  • Actor methods return async types and can include modifies/reads clauses
  • Contracts (requires, ensures) appear between the signature and body
  • Private helper functions organize complex logic