跳到正文
cajulang

示例

完整的 Caju 程序,附带各自的输出。复制后保存为 .caju 文件,再用 caju run 运行。

基础

可选类型

标注类型后,编译器能在运行前发现错误;不标注时,类型由编译器推断。

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

函数是值

闭包会捕获外部变量,并一直持有它们。

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

切片与映射

用 append 增长的列表和键值映射,语言内置。

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

安排一个调用在函数结束时执行。这些调用按从后到前的顺序运行。

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

类型与结构

结构体、方法与接口

一个类型只要拥有接口的方法就实现了该接口,无需任何声明。

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

嵌入结构体

Car 获得了 Engine 的字段和方法。

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

类型 switch

找出 any 中存放的类型,并分别处理每种情况。

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

错误

错误即值

函数同时返回结果和错误,没有隐藏的异常。

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 与 recover

在 defer 中调用 recover 可以止住 panic。索引越界同样会引发 panic。

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)

包

使用包

用 caju pkg add 添加依赖,再按仓库路径导入。

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!

发布包

在仓库根目录放一个 caju.toml,再打上带版本号的 git tag,例如 v1.1.0。export 决定了其他项目能使用哪些内容。

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) + "!" }

标准库

JSON

用 std/json 在结构体与 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

文件与文本

用 std/fs 读写文件,用 std/strings 处理文本。

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

哈希与编码

SHA-256、base64、hex 等,无需外部依赖。

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

用标准库写的 Web 服务器

std/http 已经内置。这个程序编译后是一个约 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)
}

测试与代码放在一起

BDD 风格的 _test.caju 文件,用 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

准备好试试了吗?

安装 Caju