Standard Library
The Lake standard library lives under std/ and is written in Lake itself.
You bring symbols into scope with an import line at the top of a file:
+std.io.{ println }
+std.vec.{ vec_new vec_push vec_get vec_len }
The +module.{ names } form imports the listed public (pub) items from a
module. Modules nest with dots (std.crypto.sha256, std.postgres.types).
To compile against the stdlib, point the compiler at the std/ directory.
Either set STD_PATH (or LAKE_PATH) or pass it on the command line:
STD_PATH=/path/to/lake-stdlib/std lakec -O speed main.lake -o out
Module Overview
| Module | Role |
|---|---|
std.io | print / println / eprint / eprintln (plus _buf variants) over stdout/stderr |
std.option | Option[T] enum + is_some / is_none / unwrap_or |
std.result | Result[T E] enum + is_ok / is_err / unwrap_or |
std.vec | Vec[T] growable vector + vec_new / vec_push / vec_get / vec_len |
std.string | String owned, growable byte string |
std.strings | string/buf algorithms: slice, search, trim, int_to_buf, parse_int, line scanning |
std.hashmap | IntMap[V] integer-keyed hash map |
std.bytes | bounded byte access over a buf: at / set, at64 / set64, big-endian helpers, size |
std.math | abs / mod / min / max |
std.process | per-actor allocation (arena_alloc, alloc_or_die) and die |
std.sys | process-global exit(code) |
std.env | argc / argv_at / envp_at |
std.tcp | TCP server primitives: listen / accept / send / recv / close |
std.crypto.sha256 | sha256 digest |
std.crypto.hmac | hmac_sha256 |
std.crypto.pbkdf2 | pbkdf2_sha256 |
std.encoding.base64 | encode / decode |
std.postgres | PostgreSQL client (connect / query / prepare / execute) |
std.experimental.sys | raw Linux syscall numbers and wrappers (per-arch via @cfg) |
The sections below cover the collections you’ll reach for most often, with examples adapted from the stdlib’s own smoke tests.
io
I/O machines are ret-typed. A bare call is fire-and-forget (the scheduler
orders the output); prefix with pin to force sequential, in-order output:
+std.io.{ println println_buf }
main is {
_ -> {
pin println("hello")
pin println_buf(some_buf)
}
}
println takes a str literal; println_buf takes a runtime buf (such
as a file read or a converted integer).
Option and Result
Option[T] models presence/absence; Result[T E] models success/failure.
Both expose unwrap_or plus predicate helpers:
+std.io.{ println }
+std.option.{ Option unwrap_or is_some is_none }
+std.result.{ Result is_ok is_err }
main is {
_ -> {
let s = Option.Some(42)
let n Option[i64] = Option.None
when unwrap_or(s 0) == 42 {
true -> { println("ok some") }
_ -> { println("FAIL some") }
}
when unwrap_or(n 99) == 99 {
true -> { println("ok none") }
_ -> { println("FAIL none") }
}
let r Result[i64 buf] = Result.Ok(7)
when r {
Ok(v) -> { println("ok result") }
Err(_) -> { println("FAIL result") }
}
let e Result[i64 buf] = Result.Err("boom")
when is_err(e) == 1 {
true -> { println("ok is_err") }
_ -> { println("FAIL is_err") }
}
}
}
Note is_some / is_ok etc. return 1 or 0 — compare against 1 (Lake
has no !=; invert with a when == instead).
Vec
Vec[T] is a growable vector. It is value-threaded: vec_push may
reallocate, so it returns a (possibly new) Vec that you must bind and
carry forward:
+std.io.{ println }
+std.vec.{ vec_new vec_push vec_get vec_len }
fill is {
v Vec[i64] i i64 -> ret Vec[i64] {
when i >= 100 {
true -> { ret v }
_ -> { self(vec_push(v i * 7) i + 1) }
}
}
}
main is {
_ -> {
let v Vec[i64] = vec_new()
let v2 Vec[i64] = fill(v 0)
when vec_len(v2) == 100 {
true -> { println("ok") }
_ -> { println("FAIL") }
}
}
}
| Function | Signature | Description |
|---|---|---|
vec_new | () -> Vec[T] | empty vector (annotate the binding) |
vec_push | (Vec[T] T) -> Vec[T] | append; returns the threaded vector |
vec_get | (Vec[T] i64) -> T | element at index |
vec_len | (Vec[T]) -> i64 | element count |
String
String is an owned, growable byte string (as opposed to immutable str
literals). Like Vec, it is value-threaded:
+std.io.{ println println_buf }
+std.string.{ String str_from str_push str_append str_len str_eq str_to_buf }
+std.strings.{ int_to_buf }
main is {
_ -> {
let a = str_from("hello" 5)
let b = str_push(a 33) -- push byte 33 ('!')
pin println_buf(int_to_buf(str_len(b))) -- 6
pin println_buf(str_to_buf(b)) -- hello!
let d = str_append(str_from("foo" 3) str_from("bar" 3))
pin println_buf(str_to_buf(d)) -- foobar
}
}
str_from(s n) copies the first n bytes of literal s. str_to_buf
returns a fresh buf of exactly the live bytes — handy for println_buf.
IntMap
IntMap[V] is an integer-keyed hash map (open addressing, linear probing),
generic over the value type. It is value-threaded — thread the result of
intmap_put:
+std.io.{ println_buf }
+std.hashmap.{ IntMap intmap_new intmap_put intmap_get intmap_has intmap_len }
+std.strings.{ int_to_buf }
fill is {
m IntMap[i64] i i64 -> ret IntMap[i64] {
when i >= 50 {
true -> { ret m }
_ -> { self(intmap_put(m i i * 100) i + 1) }
}
}
}
main is {
_ -> {
let m0 IntMap[i64] = intmap_new()
let m IntMap[i64] = fill(m0 0)
pin println_buf(int_to_buf(intmap_len(m))) -- 50
pin println_buf(int_to_buf(intmap_get(m 7))) -- 700
pin println_buf(int_to_buf(intmap_has(m 123))) -- 0
}
}
| Function | Signature | Description |
|---|---|---|
intmap_new | () -> IntMap[V] | empty map (annotate the binding) |
intmap_put | (IntMap[V] i64 V) -> IntMap[V] | insert/update; thread the result |
intmap_get | (IntMap[V] i64) -> V | value for key (check has first; a miss returns 0) |
intmap_has | (IntMap[V] i64) -> i64 | 1 if present, else 0 |
intmap_len | (IntMap[V]) -> i64 | entry count |
Keys are i64. A fully generic key type (string/buf keys) is not
available yet.
PostgreSQL
std.postgres is a from-scratch PostgreSQL client: it speaks the wire
protocol over a raw socket, authenticates with SCRAM-SHA-256, and uses
binary codecs for typed columns. The surface is connect / query /
prepare / execute / close, returning Conn, Rows, and Stmt
records.
+std.postgres.{ connect close query Rows ip_v4 row_count row_field }
+std.postgres.types.{ dec_int4 }
+std.io.{ println println_buf }
+std.strings.{ int_to_buf }
main is {
_ -> {
let host = ip_v4(127 0 0 1)
let c = connect(host 5432 user db password)
when c.ok {
1 -> {
let rows = query(c "SELECT 1::int4")
when rows.ok {
1 -> {
let f0 = row_field(rows 0 0)
pin println_buf(int_to_buf(dec_int4(f0 0)))
}
_ -> { pin println("query error") }
}
close(c)
}
_ -> { pin println("connect failed") }
}
}
}
The host argument is a 4-octet IPv4 buf built with ip_v4(...). The
user / db / password arguments are buf values. Prepared statements
go through prepare(c name sql) and execute(c stmt args), with parameter
encoders such as enc_int4 from std.postgres.types and args2 / args3
helpers to bundle the arguments. See examples/postgres_smoke in the
stdlib repo for a complete prepared-statement round-trip.
Current Limitations
A few rough edges to keep in mind while writing against today’s stdlib:
- No modulo operator. There is no
%; usestd.math.modor bitwise masking (x & (n - 1)whennis a power of two). - No
!=. Compare with==inside awhenand put the “not equal” case in a wildcard or_arm. - Generic inference sometimes needs a hint. Annotate intermediate
letbindings (let v Vec[i64] = vec_new()) when the right-hand side doesn’t pin down the type parameters. - Hash-map keys are
i64.IntMap[V]covers id-to-value tables; a fully generic-key map is not available yet. - Collections (
Vec,String,IntMap) are value-threaded — always bind and reuse the value a mutating call returns.