Examples

Complete Caju programs, with the output each one prints. Copy one, save it to a .caju file and run it with caju run.

The basics

Optional types

With types, the compiler catches errors before running. Without them, types are inferred.

types.caju
fn average(total: num, n: int) -> num {
return total / n
}
let sum = 0
let count: int = 0
for i = 1 to 4 {
sum = sum + i * 1.5
count = count + 1
}
print("average:", average(sum, count))
# count = "four" error: int does not accept str
$ caju run types.caju
average: 3.75

Functions are values

A closure captures outer variables and keeps them.

closures.caju
fn counter() -> fn() -> int {
n := 0
return fn() -> int {
n++
return n
}
}
a := counter()
b := counter()
print(a(), a(), a(), b())
$ caju run closures.caju
1 2 3 1

Slices and maps

Lists that grow with append and key-value maps, built into the language.

maps.caju
stock := map[str]int{"banana": 12, "cashew": 30}
stock["mango"] = 5
stock["banana"] -= 2
delete(stock, "mango")
if qty, ok := stock["cashew"]; ok {
print("cashews in stock:", qty)
}
print(stock)
grades := []int{7, 9, 4}
grades = append(grades, 10)
print(grades, len(grades))
$ caju run maps.caju
cashews in stock: 30
map["banana":10 "cashew":30]
[7 9 4 10] 4

defer

Schedule a call for when the function returns. Calls run from last to first.

defer.caju
fn chores() {
defer print("3. clear the table")
defer print("2. wash the dishes")
print("1. have dinner")
}
chores()
$ caju run defer.caju
1. have dinner
2. wash the dishes
3. clear the table

Types and structures

Structs, methods and interfaces

A type satisfies an interface just by having its methods, with nothing to declare.

shapes.caju
interface Shape { area() -> num }
struct Rect { w: num, h: num }
struct Circle { r: num }
fn (r: Rect) area() -> num { return r.w * r.h }
fn (c: Circle) area() -> num { return 3.14159 * c.r * c.r }
shapes := []Shape{Rect{2, 3}, Circle{1}}
for _, s := range shapes { print(s.area()) }
$ caju run shapes.caju
6
3.14159

Embedded structs

Car gets Engine's fields and methods.

embed.caju
struct Engine { power: int }
fn (e: Engine) roar() -> str { return "vroom " + str(e.power) }
fn (e: *Engine) boost() { e.power *= 2 }
struct Car {
Engine
model: str
}
car := Car{Engine{90}, "beetle"}
car.boost()
print(car.model, car.power, car.roar())
$ caju run embed.caju
beetle 180 vroom 180

Type switch

Find out the type stored in an any and handle each case.

typeswitch.caju
fn describe(x: any) -> str {
switch v := x.(type) {
case int: return "integer " + str(v)
case num: return "number " + str(v)
case str: return "text with " + str(len(v)) + " bytes"
case nil: return "nil"
default: return "something else"
}
}
for _, x := range []any{7, 2.5, "caju", nil, true} {
print(describe(x))
}
$ caju run typeswitch.caju
integer 7
number 2.5
text with 4 bytes
nil
something else

Errors

Errors are values

The function returns the result and an error, with no hidden exceptions.

divide.caju
fn divide(a: int, b: int) -> (int, error) {
if b == 0 { return 0, errors.New("division by zero") }
return a / b, nil
}
for _, d := range []int{5, 0} {
q, err := divide(10, d)
if err != nil {
print("error:", err)
continue
}
print("10 /", d, "=", q)
}
$ caju run divide.caju
10 / 5 = 2
error: division by zero

panic and recover

recover, inside a defer, stops the panic. An index out of range also panics.

recover.caju
fn safe(f: fn()) -> (err: error) {
defer fn() {
if r := recover(); r != nil { err = errors.New("recovered: " + str(r)) }
}()
f()
return nil
}
print(safe(fn() { panic("something went wrong") }))
print(safe(fn() {
list := []int{1, 2, 3}
i := 5
print(list[i])
}))
$ caju run recover.caju
recovered: something went wrong
recovered: Index 5 out of range (length 3)

Packages

Using a package

Add the dependency with caju pkg add and import it by its repository path.

app.caju
import "github.com/acme/strutil"
print(strutil.shout("caju"))
$ caju pkg add github.com/acme/strutil
downloading github.com/acme/strutil@1.1.0
added github.com/acme/strutil@1.1.0; 1 packages ok
$ caju run app.caju
CAJU!

Publishing a package

A caju.toml at the repository root and a git tag with the version, such as v1.1.0. export defines what other projects can use.

strutil.caju
# caju.toml
# [package]
# name = "github.com/acme/strutil"
# version = "1.1.0"
import "std/strings"
export fn shout(s: str) -> str { return strings.to_upper(s) + "!" }

Standard library

JSON

Convert structs to JSON and JSON to structs with std/json.

json.caju
import "std/json"
struct Fruit {
name: str
price: num
tags: []str
}
text, _ := json.marshal(Fruit{"cashew", 2.5, []str{"tropical", "sweet"}})
print(text)
f := Fruit{}
err := json.unmarshal("{\"name\": \"mango\", \"price\": 3}", &f)
print(f.name, f.price, err)
$ caju run json.caju
{"name":"cashew","price":2.5,"tags":["tropical","sweet"]}
mango 3 nil

Files and text

std/fs to read and write files; std/strings to work with text.

files.caju
import "std/fs"
import "std/strings"
fs.write_file("notes.txt", "cashew\nmango\nguava\n", 0o644)
text, err := fs.read_file("notes.txt")
if err != nil {
print("error:", err)
} else {
lines := strings.split(strings.trim_space(text), "\n")
print(len(lines), "lines:", strings.join(lines, ", "))
}
$ caju run files.caju
3 lines: cashew, mango, guava

Hashing and encoding

SHA-256, base64, hex and more, with no external dependencies.

crypto.caju
import "std/crypto"
import "std/base64"
print(crypto.sha256_hex("caju"))
print(base64.encode("caju"))
$ caju run crypto.caju
247b3a76c41fb983655aeeb0955751d31eac8e76a817b2508bc4e297f09aad90
Y2FqdQ==

A web server with the standard library

std/http comes built in. This program compiles to an executable of about 100 KB.

hello-server.caju
import "std/http"
import "std/os"
http.handle("/", fn(req: *http.Request, res: *http.Response) {
res.write("hello world")
})
print("listening on http://localhost:3200/")
err := http.listen_and_serve("0.0.0.0", 3200)
if err != nil {
os.eprintln(err.Error())
os.exit(1)
}

Tests next to the code

BDD-style _test.caju files, run with caju test.

strings_test.caju
import "std/strings"
import { describe, it, expect } from "std/testing"
describe("strings.split", fn() {
it("cuts the text at every separator", fn() {
expect(strings.split("a,b", ",")).to_equal([]str{"a", "b"})
})
})
$ caju test strings_test.caju
ok strings_test.caju (1 tests)
1 passed, 0 failed, 0 skipped in 1 files

Ready to try it?

Install Caju