# slang in one page > slang is a statically typed language for servers, with Go-like > simplicity. It compiles to C, so programs are fast and small. It has green > threads, channels, a garbage collector, generics, and no exceptions, nulls, > closures or interfaces. This page is enough to write correct slang. Every > code block below compiles and runs as shown; the test suite checks that. Commands: `slangc main.sl --run` (compile and run), `slangc main.sl -o app` (build), `slangc new app` (new project), `slangc test` (run `*_test.sl`), `slangc get` (fetch pinned packages). Errors read `file.sl:12: error: ...`, list the first error of every function in one compile, and often end with `(did you mean 'x'?)`; `--json` gives one JSON object per error. Package APIs: `slangc doc http` lists a package's signatures with their comments, `slangc doc builder.Str` shows one item in full, `slangc doc http header` lists just the items matching "header" (also `/packages/.md` and `/api.json` on the docs site). HTTP services: use the zokor framework and its own `llms-small.txt`. ## Basics ```slang // Top-level statements are the program; functions may come in any order. let n = 10; // int (64-bit); every `let` can be reassigned let pi = 3.14; // float let name = "slang"; // str (UTF-8) let ok = true; // bool n = n + 1; n += 1; // compound assignment let half = n / 2; // int / int is integer division let small = n as i32; // narrowing needs `as` // time.mono() is a `duration` (ns), never an int: `d as int` for a count println("${name} has ${n} and ${half}"); // interpolation: any expression println("also " + name + " " + n); // + converts scalars to str fn add(a: int, b: int) -> int { return a + b; } fn sign(x: int) -> str { if x < 0 { return "negative"; } else if x == 0 { return "zero"; } return "positive"; } for i in 0..3 { print(i); } // 0 1 2 (exclusive); 1..=3 is inclusive println(""); let k = 0; while k < 2 { k += 1; } println(add(small, 1) + k); println(sign(-5)); let code = 404; let label = switch code { case 200, 201 { "ok" } case 404 { "missing" } default { "other" } }; println(label); ``` Semicolons end statements. No `var`, `const`, `null` or `nil`. Fixed-width ints: `i8..i64`, `u8..u64`; `f32`. Casts go through `as`. ## Lists, maps, bytes ```slang let xs = [3, 1, 2]; // [int] let names: [str] = []; // an empty literal needs a type push(xs, 4); let last = pop(xs); xs[0] = 9; // bounds-checked for x in xs { print(x); } println(" len=${len(xs)} last=${last}"); let part = xs[0..2]; // slice let ages: map[str]int = {"ada": 36}; ages["alan"] = 41; // insert or overwrite if has(ages, "ada") { println(ages["ada"]); } del(ages, "alan"); for key, v in ages { println(key + "=" + to_str(v)); } // insertion order let raw = b"hi"; // bytes: binary-safe println(len(raw)); println(inspect(part)); // [9, 1]; println only takes scalars push(names, "x"); ``` A missing map key is a runtime error: check with `has` first. Build large strings with `import "builder";` (`builder.new_str().write(..).finish()`), not `+` in a loop, which is quadratic. ## Structs, methods, enums, generics ```slang struct Point { x: int, y: int } // value: assignment copies impl Point { fn sum(self: Point) -> int { return self.x + self.y; } } gc struct Account { // heap object: shared by reference id: int, owner: str, tags: [str], } enum Status { Pending, Paid, Shipped } struct Box[T] { v: T } fn first[T](xs: [T]) -> T { return xs[0]; } // T is inferred at the call let p = Point { x: 1, y: 2 }; // every field, exactly once println(p.sum()); let a = Account { id: 1, owner: "ada", tags: [] }; let alias = a; alias.owner = "grace"; // a.owner is now "grace" too println(a.owner); let s = Status.Paid; println(s); // Paid switch s { case Status.Pending { println("wait"); } case Status.Paid, Status.Shipped { println("done"); } } println(Box { v: 41 }.v + 1); println(first(["a", "b"])); ``` `pub` exports a function, type, method or constant from its package. A `switch` on an enum must cover every variant or have `default`. ## Errors: opt, result, guard let, ?? ```slang fn find(ids: [int], want: int) -> opt[int] { for i in 0..len(ids) { if ids[i] == want { return some(i); } } return none; } fn parse_port(s: str) -> result[int, str] { let r = to_int(s); guard let port = r else let e = err_of(r) { return err("PORT: " + e); } if port < 1 || port > 65535 { return err("PORT out of range: " + to_str(port)); } return ok(port); } let idx = find([4, 5, 6], 5) ?? -1; // ?? gives a default on none/err println(idx); guard let port = parse_port("8080") else { println("bad port"); exit(2); // guard's else must leave the scope } println(port); let bad = parse_port("x"); if let p = bad { // if let: handle both, carry on println(p); } else let e = err_of(bad) { println(e); // PORT: not a base-10 integer } ``` Absent data is `opt[T]`; bad data is `result[T, str]` with a reason; a failing network or file operation is `result[T, fault]` (timeout, reset, closed, refused, io). There are no exceptions. `assert(cond, msg)` and `panic(msg)` end the current task with the message and location. A guard's `else` must leave the scope (`return`, `break`, `continue`, `exit`, `panic`, or a function of yours that always exits); `if let` is for when both outcomes carry on, and each of its bindings lives only in its branch. A bare `none`, `err(..)`, `[]` or `{}` takes its type from where it goes: an annotated binding, a parameter, a return type, a field, or the list or map it sits in (`let xs: [opt[int]] = [some(1), none];`). ## Concurrency ```slang fn worker(jobs: chan[int], results: chan[int]) { while true { let j = chan_recv(jobs); // opt[int]: none once closed guard let n = j else { return; } chan_send(results, n * n); } } fn square(n: int) -> int { return n * n; } let jobs: chan[int] = make_chan(16); let results: chan[int] = make_chan(16); for w in 0..4 { spawn worker(jobs, results); } for n in 1..=5 { chan_send(jobs, n); } chan_close(jobs); let total = 0; for i in 0..5 { total += chan_recv(results) ?? 0; } println(total); // 55 let h = spawn square(7); // join[int] let r = join_wait(h); // result[int, str]; err if it panicked println(r ?? -1); let m = make_mutex(); mutex_lock(m); total += 1; // no defer: unlock on every path mutex_unlock(m); ``` `spawn f(args)` runs `f` on a green thread; arguments are evaluated first and nothing is captured. Blocking calls (`chan_recv`, `net.*`, `time.sleep`) park the task, not the OS thread, so there is no async/await. `select { case let v = chan_recv(a) { .. } case chan_send(b, x) { .. } default { .. } }` waits on several channels. ## Functions as values ```slang fn double(x: int) -> int { return x * 2; } fn apply(f: fn(int) -> int, x: int) -> int { return f(x); } let ops: map[str]fn(int) -> int = {"double": double}; println(apply(double, 4)); println(ops["double"](5)); ``` A function value names a top-level function. There are no closures and no lambdas: pass what a function needs as parameters. A method cannot be a function value; wrap it in a plain function. ## Packages, JSON, environment ```slang import "json"; import "proc"; gc struct User { name: str, age: i32, email: opt[str], // missing or null in JSON is none } let u = User { name: "ada", age: 36, email: none }; let text = json.encode(u); println(text); let r: result[User, str] = json.decode("{\"name\":\"bo\",\"age\":7}"); guard let back = r else let e = err_of(r) { println("decode: " + e); // errors name the field exit(1); } println(back.name); let home = proc.getenv("HOME") ?? "/"; println(len(home) > 0); ``` A package is a directory: its `.sl` files share one namespace. `import "geometry";` finds `./geometry/`, then a standard package, then a `pkg` pin in `slang.project` (`pkg name git tag v1.2.0`, then `slangc get`). JSON works on `struct` and `gc struct`; the decode target comes from the annotation, and a `[T]` of a plain `struct` decodes into one buffer, not an object per item. Package authors can add repeatable `exclude ` lines to their own `slang.project` to omit non-runtime directories from consumers' package caches; a package must not import a directory it excludes. Standard packages: `time net json proc fs os io log crypto sql pg redis regex strings unicode encoding compress math decimal rand builder byteutil flags sort uuid http http2 httpc template`. uuid http http2 httpc websocket`. uuid http http2 httpc csv`. `decimal` is exact money: `let r = decimal.parse("19.99")` (result), then `decimal.add/sub/mul(a, b)` (opt: none past 38 digits), `decimal.div(a, b, scale, decimal.Rounding.HalfEven)`, `decimal.format(d)`, `decimal.split(d, n)`. Compare with `decimal.eq`/`cmp` (structs have no `==`). JSON writes a Decimal field as an exact string; `pg.arg_decimal`/`pg.get_decimal` for numeric. `strings` is byte-based (protocol text); `unicode` is for text people type: `unicode.valid(s)`, `unicode.count(s)` (characters), `unicode.truncate(s, n)`, `unicode.nfc(s)` before storing or comparing, `unicode.equal_fold(a, b)` for case-insensitive matches (`"STRASSE"` equals `"straße"`). Transforms repair invalid UTF-8 to U+FFFD. `uuid.v7()` (time-ordered, for keys) and `uuid.v4()` return `result[str, str]`; an id is a canonical lowercase `str`, `uuid.parse` canonicalizes any spelling. `nats`: `let nc = nats.connect(url, deadline)`, `nats.publish(nc, subj, data)`, `nats.subscribe(nc, subj)` then `nats.next_msg(sub, deadline)` (result of opt), `nats.request(nc, subj, data, deadline)`, `nats.drain(nc, deadline)`; reconnects and resubscribes by itself. `s3`: `let c = s3.new_client(endpoint, region, key, secret)` (or `s3.from_env(proc.getenv)`), `s3.put_object(c, bucket, key, data, ctype, dl)`, `s3.get_object`, `s3.list_objects(c, bucket, prefix, "/", next, 1000, dl)`, `s3.put_large` (multipart), `s3.presign_get(c, bucket, key, seconds)`. `template`: `let r = template.parse(name, src)` (Go syntax: `{{ .f }}`, `if`, `range $i, $x := .xs`, `with`, `define`, pipelines), then `template.render(t, data)` (result; data is any `json.encode` value). HTML is escaped by context at parse time (text, attributes, URLs, scripts, styles); `parse_text` for email. A missing field is an error. `websocket`: `let ws = websocket.dial(url, deadline)` (ws:// or wss://; `dial_with` for `options()`: protocols, headers, `ping_every`, `compress`), `websocket.send_text/send_binary(ws, x, deadline)`, `websocket.recv(ws, deadline)` (result of opt Message; `m.kind` is `websocket.Kind.Text/Binary/Close`), `websocket.close(ws, 1000, "", deadline)`. Pings are answered automatically. `encoding.base32_encode/decode` (RFC 4648; decode takes either case, padded or not), `base32hex_*`, and `crockford_encode/decode` for codes people type. `csv.parse(text)` (result of `[[str]]`), `csv.to_maps(rows)` by header, streaming with `csv.new_reader(o)`, `csv.feed(rd, chunk)`, `csv.next(rd)` (none: feed more), `csv.end(rd)`; `csv.format(rows)` writes CRLF, quoted as needed. `crypto`: `seal(key, data, aad)`/`open` (AES-256-GCM, random nonce; result), `hkdf_sha256(ikm, salt, info, n)`, `constant_time_eq(a, b)`, `ed25519_sign/verify`, `ecdsa_p256_sign_sha256/verify_sha256` (raw r||s), `rsa_sign_sha256(pem, msg)`; verifiers return `result[bool, str]`. `math` takes any numeric type and returns that type (`math.abs(x)` of an `i32` is an `i32`): `math.sqrt`, `math.pi`, `math.round_to(x, 2)`, and for money `math.checked_add(a, b)` / `math.checked_mul(a, b)`, an `opt` that is `none` on overflow. `math/stats` (mean, median, stddev, quantile, correlation, regression, streaming `running()`/`digest()`, distributions) and `math/numtheory` (mod_pow, is_prime, factorize) are written over it. `rand` is seeded and non-cryptographic, for tests that replay: `let r = rand.new(42); rand.below(r, 10); rand.between(r, 1, 6); rand.shuffle(r, xs); rand.backoff(r, attempt, base, cap)`. Secrets use `crypto.rand`. The standalone `argon` package provides bounded Argon2id password hashing with standard PHC strings; its native crypto calls require OpenSSL 3.2+. ## Calendar time: timez ```slang import "timez"; let r = timez.utc(2026, 10, 13, 9, 30, 15, 0); // result[Time, str]; err names the bad field guard let t = r else let e = err_of(r) { println(e); exit(1); } println(t.rfc3339()); // 2026-10-13T09:30:15Z println(t.http_date()); // Tue, 13 Oct 2026 09:30:15 GMT println(t.date().add_months(4).iso()); // 2027-02-13 (end-of-month clamps) println(t.format("%a %d %b %Y %H:%M")); // Tue 13 Oct 2026 09:30 println(timez.format_duration(timez.parse_duration("90m") ?? 0)); // 1h30m0s let p = timez.parse_rfc3339("2026-10-13T11:30:15+02:00"); println((p ?? t).equal(t)); // true: offsets are applied ``` `Time` is an instant (UTC), an RFC 3339 string in `json.encode`/`decode` (`pg.arg_time(t)`, `pg.get_time(rows, r, c)` for `timestamptz`); `Date` a calendar day; durations are int nanoseconds (`timez.MINUTE`, `"7d"` parses). Zones: `timez.load_zone("Europe/Berlin")`, `t.in_zone(z).format("%H:%M %Z")`, `timez.zoned(z, y, m, d, h, mi, s, timez.Disambiguation.COMPATIBLE)`. Tests: pass a `timez.fixed_clock(t)` and `advance` it. Cron: `timez.parse_cron("0 9 * * MON-FRI")` then `.next_after(t, zone)`. ## Tests Tests live next to the code in `*_test.sl`, as `fn test_*()` that fail through `assert` or `panic`. They can reach private functions. `slangc test` runs them and prints only failures and a count (`-v` lists every test); `--run name` filters. ```slang fn clamp(v: int, lo: int, hi: int) -> int { if v < lo { return lo; } if v > hi { return hi; } return v; } fn test_clamp() { assert(clamp(15, 0, 10) == 10, "got " + to_str(clamp(15, 0, 10))); assert(clamp(-1, 0, 10) == 0); } test_clamp(); println("ok"); ``` ## Mistakes that cost a retry - `println(list)`: use `println(inspect(list))`. - `let xs = [];` or `let m = {};`: annotate, `let xs: [int] = [];`. - `m[k]` on a missing key panics: check `has(m, k)`. - A closure or lambda: write a top-level function and pass its state in. - `return` between `mutex_lock` and `mutex_unlock`: unlock first. - A `guard` whose `else` falls through is rejected: leave the scope, or use `if let v = x { .. } else { .. }` when both paths continue. - `let v = chan_recv(c);` is `opt[T]`: unwrap with `guard let` or `??`. - Mixing `i32` and `int`: widening is implicit; narrowing needs `as`. - A plain `struct` holding itself through `opt` or `result` is rejected (both are held by value): make it a `gc struct Node { next: opt[Node] }`. ## Package index Each package's functions, first 12 at most. Signatures: `/packages/.md`. - builder: new_str, Str.write, Str.write_int, Str.write_line, Str.size, Str.is_empty, Str.finish, Str.take, Str.reset, new_bytes, Bytes.write_byte, Bytes.write (+6 more) - byteutil: find, find_wire, has_prefix, has_suffix, trim, split - compress: gzip, gzip_level, deflate, deflate_raw, deflate_raw_flush, gunzip, inflate, inflate_raw - crypto: sha256, hmac_sha256, rand, sha1, md5, pbkdf2_sha256, argon2id_raw, argon2id_verify, rsa_verify_sha256, sha384, sha512, hmac_sha384 (+28 more) - csv: options, parse, parse_with, parse_bytes, new_reader, feed, end, finished, line, next, to_maps, write_options (+3 more) - decimal: zero, from_int, from_units, parse, parse_bytes, from_float, scale_of, precision, is_zero, is_neg, sign, format (+22 more) - encoding: hex_encode, hex_decode, base64_encode, base64_decode, base64url_encode, base64url_decode, url_encode, url_decode, form_encode, form_decode, query_get, query_keys (+11 more) - flags: new, Set.add_str, Set.add_int, Set.add_float, Set.add_bool, Set.add_list, Set.require, Set.set_takes, Set.stop_at_first_arg, Set.parse, Set.parse_or_exit, Set.usage (+8 more) - fs: open, create, read, pread, write, close, mkdir, realpath, fstat - http: header, header_or, headers, request, parse_frame, method_is, method_is_at, path_is, path_is_at, path_param, path_param_at, frame_method (+40 more) - http2: client_settings, client_preface, client_request_msg, client_read_response, client_start, client_max_streams, client_call, client_close, raw_msg, grant_msg, response_msg, response_trailers_msg (+57 more) - httpc: parse_url, header, new_client, enable_cookies, clear_cookies, parse_cookie_date, set_cookie, cookies, new_request, client_send, client_get, client_head (+18 more) - io: read_line, read_all, eprint, eprintln, flush, is_tty, term_width, term_height, read_secret, raw_on, raw_off, read_key - json: see /packages/json.md - log: debug, info, warn, error - math: abs, min, max, clamp, sign, is_nan, is_inf, is_finite, is_normal, signbit, copysign, nextafter (+119 more) - math/numtheory: mod_pow, mod_mul, ext_gcd, mod_inv, is_prime, next_prime, prev_prime, primes_below, factorize, divisors, totient, crt (+2 more) - math/stats: normal, Normal.pdf, Normal.cdf, Normal.quantile, log_normal, LogNormal.pdf, LogNormal.cdf, LogNormal.quantile, uniform, Uniform.pdf, Uniform.cdf, Uniform.quantile (+67 more) - nats: options, with_creds, connect, connect_with, publish, publish_reply, publish_headers, publish_full, subscribe, queue_subscribe, next_msg, unsubscribe (+15 more) - net: listen, port, accept, dial, dial_until, lookup_srv_until, lookup_txt_until, dial_unix, listen_unix, send, recv, recv_until (+22 more) - os: setenv, unsetenv, environ, pid, hostname, tmpdir, exists, is_dir, is_file, size, mtime, mtime_ns (+6 more) - pg: rows_as, parse_url, sqlstate, scram_client_final, connect, connect_config, close, usable, in_transaction, server_param, arg_text, arg_int (+39 more) - proc: shutdown_requested, active_tasks, wait_idle, getenv, args, cwd, spawn, kill, wait - rand: new, from_os, fork, next_u64, below, between, unit, chance, shuffle, pick, backoff, jitter - redis: encode, decode, decode_at, slot, parse_url, connect_config, connect, do, close, usable, ping, echo (+132 more) - regex: compile, free, groups, is_match, is_match_bytes, find, find_at, find_bytes, find_bytes_at - s3: new_client, from_env, put_object, get_object, get_range, head_object, delete_object, list_objects, create_multipart, upload_part, complete_multipart, abort_multipart (+8 more) - sort: ints, floats, strings, by, by_stable, search_ints, search_floats, search_strings, search_by - sql: open, close, exec, last_insert_id, prepare, finalize, reset, bind_int, bind_float, bind_text, bind_blob, bind_null (+8 more) - strings: find, rfind, contains, has_prefix, has_suffix, count, trim, trim_start, trim_end, to_upper, to_lower, slice (+14 more) - template: parse, parse_text, add, render, render_named, escape_html - time: mono, wall, sleep, http_date, put_http_date - timez: Time.rfc3339, Time.rfc3339_milli, Time.rfc3339_nano, Time.rfc3339_at, Time.http_date, Time.rfc2822, Time.format, parse_date, parse_rfc3339, parse_http_date, parse_rfc2822, relative (+116 more) - unicode: valid, valid_bytes, count, runes, encode, truncate, fold, equal_fold, nfc, nfd, is_nfc - uuid: v4, v7, v7_at, is_valid, parse, version, time_ms, to_binary, from_binary - websocket: options, dial, dial_with, send_text, send_text_bytes, send_binary, ping, recv, text, close, close_now, is_open (+1 more)