Function Basics

Let's start with how to define and call functions in Coco. The syntax is clean and explicit—you'll always know what a function takes and what it returns.

Defining Functions

fn greet() {
    echo "Hello!";
}

fn greet_person($name string) {
    echo "Hello, {$name}!";
}

fn add($a i32, $b i32): i32 {
    return $a + $b;
}

The pattern is: fn name(parameters): return_type { body }

  • Parameters use $name type syntax
  • Return type comes after the :
  • No return type means the function returns nothing (void)

Calling Functions

greet();                    // Hello!
greet_person("Alice");      // Hello, Alice!
$sum = add(2, 3);           // 5

Nothing surprising here. Arguments are positional by default.

Return Values

Use return to return a value:

fn square($x i32): i32 {
    return $x * $x;
}

The last expression can be returned implicitly if you omit the semicolon:

fn square($x i32): i32 {
    $x * $x
}

Both styles are valid. Use whichever reads better in context.

Multiple Return Values

Coco supports multiple return values using tuples:

fn divide($a i32, $b i32): (i32, i32) {
    $quotient = $a / $b;
    $remainder = $a % $b;
    return ($quotient, $remainder);
}

// Destructure the result
($q, $r) = divide(17, 5);
echo "{$q} remainder {$r}";  // 3 remainder 2

This is useful for functions that naturally produce multiple pieces of data.

Default Parameters

Set defaults with =:

fn greet($name string = "World") {
    echo "Hello, {$name}!";
}

greet();          // Hello, World!
greet("Alice");   // Hello, Alice!

Parameters with defaults must come after required parameters.

Named Arguments

For clarity, you can use named arguments:

fn create_user($name string, $email string, $age i32) {
    // ...
}

// Positional
create_user("Alice", "alice@example.com", 30);

// Named (clearer, especially with many parameters)
create_user(
    name: "Alice",
    email: "alice@example.com",
    age: 30
);

Named arguments can be in any order and are especially helpful when a function has many parameters or several of the same type.

Documentation Comments

Document functions with ///:

/// Calculates the area of a rectangle.
///
/// # Parameters
/// - width: The width in pixels
/// - height: The height in pixels
///
/// # Returns
/// The area in square pixels
fn area($width f64, $height f64): f64 {
    return $width * $height;
}

These comments become part of the generated documentation.

Function Overloading

Coco doesn't have function overloading in the traditional sense. Instead, use:

Different names:

fn print_int($value i32) { ... }
fn print_string($value string) { ... }

Generics:

fn print<T: Display>($value T) {
    echo "{}", $value;
}

Optional parameters:

fn connect($host string, $port i32 = 80, $timeout i32 = 30) {
    // ...
}

This keeps function resolution simple and explicit.

Early Returns

Return early to handle edge cases:

fn find_user($id i32): ?User {
    if $id <= 0 {
        return null;
    }

    $user = database.fetch($id);
    if $user == null {
        return null;
    }

    return $user;
}

Early returns reduce nesting and make the happy path clearer.

Recursion

Functions can call themselves:

fn factorial($n i32): i32 {
    if $n <= 1 {
        return 1;
    }
    return $n * factorial($n - 1);
}

Coco optimizes tail recursion, so tail-recursive functions don't blow the stack:

fn factorial_tail($n i32, $acc i32 = 1): i32 {
    if $n <= 1 {
        return $acc;
    }
    return factorial_tail($n - 1, $n * $acc);  // Tail call
}

Visibility

By default, functions are private to their module. Use pub for public:

// Private - only accessible within this module
fn helper() {
    // ...
}

// Public - accessible from other modules
pub fn process() {
    helper();
    // ...
}

Best Practices

Keep functions small. A function should do one thing. If it's getting long, split it up.

Use descriptive names. calculate_total_price() is better than calc().

Put the most important parameter first. For methods, that's usually the thing being acted on.

Use named arguments for clarity. When calling a function with multiple similar-typed parameters, names prevent mistakes:

// Which is width, which is height?
resize(100, 200);

// Clear
resize(width: 100, height: 200);

Return early for guard clauses. Handle error cases at the top, then write the main logic without deep nesting.

Copyright (c) 2025 Ocean Softworks, Sharkk