Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

ModuleRole
std.ioprint / println / eprint / eprintln (plus _buf variants) over stdout/stderr
std.optionOption[T] enum + is_some / is_none / unwrap_or
std.resultResult[T E] enum + is_ok / is_err / unwrap_or
std.vecVec[T] growable vector + vec_new / vec_push / vec_get / vec_len
std.stringString owned, growable byte string
std.stringsstring/buf algorithms: slice, search, trim, int_to_buf, parse_int, line scanning
std.hashmapIntMap[V] integer-keyed hash map
std.bytesbounded byte access over a buf: at / set, at64 / set64, big-endian helpers, size
std.mathabs / mod / min / max
std.processper-actor allocation (arena_alloc, alloc_or_die) and die
std.sysprocess-global exit(code)
std.envargc / argv_at / envp_at
std.tcpTCP server primitives: listen / accept / send / recv / close
std.crypto.sha256sha256 digest
std.crypto.hmachmac_sha256
std.crypto.pbkdf2pbkdf2_sha256
std.encoding.base64encode / decode
std.postgresPostgreSQL client (connect / query / prepare / execute)
std.experimental.sysraw 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") }
    }
  }
}
FunctionSignatureDescription
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) -> Telement at index
vec_len(Vec[T]) -> i64element 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
  }
}
FunctionSignatureDescription
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) -> Vvalue for key (check has first; a miss returns 0)
intmap_has(IntMap[V] i64) -> i641 if present, else 0
intmap_len(IntMap[V]) -> i64entry 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 %; use std.math.mod or bitwise masking (x & (n - 1) when n is a power of two).
  • No !=. Compare with == inside a when and put the “not equal” case in a wildcard or _ arm.
  • Generic inference sometimes needs a hint. Annotate intermediate let bindings (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.