Batteries Included: Seq Standard Library
Philosophy
Inspired by Go’s “batteries included” approach:
- Opinionated: One obvious way to do things
- Self-sufficient: Build real applications without external dependencies
- Cohesive: Consistent naming, patterns, and idioms across the stdlib
- Practical: Focus on what developers actually need, not academic completeness
The Rust Advantage
Seq’s runtime is implemented in Rust, which provides a massive architectural advantage for building a batteries-included stdlib. Instead of:
- Writing crypto from scratch (dangerous, years of work)
- Binding to C libraries like OpenSSL (complex, CVE-prone, platform headaches)
- Building HTTP/TLS stacks from first principles
- Maintaining fragile C FFI bindings
Seq can leverage Rust’s ecosystem directly:
| Capability | Rust Crate | Quality |
|---|---|---|
| Crypto hashing | sha2 | RustCrypto, audited |
| HMAC | hmac | RustCrypto, audited |
| Encryption | aes-gcm | RustCrypto, audited |
| Signatures | ed25519-dalek | Audited, widely used |
| HTTP client | hand-rolled over may + rustls | Strand-yielding HTTP/1.1, no ureq dependency |
| TLS | rustls | Memory-safe, modern |
| Regex | regex | Fastest in class |
| Compression | flate2, zstd | Fast, well-maintained |
| Random | rand | Industry standard |
| UUID | uuid | Complete implementation |
| Database | rusqlite | Mature, stable (not yet integrated) |
Pattern Already Proven
This isn’t theoretical - Seq already uses this pattern successfully:
| Existing Builtin | Rust Foundation |
|---|---|
| TCP networking | may (coroutine-aware) |
| File I/O | std::fs |
| Channels | crossbeam |
| Time | std::time |
| String ops | std::string |
| Base64/Hex encoding | base64, hex |
| Crypto (SHA-256, HMAC, AES-GCM, PBKDF2, Ed25519, Random, UUID) | sha2, hmac, aes-gcm, pbkdf2, ed25519-dalek, rand, uuid |
| Regular expressions | regex |
| Compression | flate2, zstd |
| Arena allocator | Custom, but Rust memory safety |
Each builtin is a thin FFI wrapper that exposes Rust functionality to Seq. Adding crypto, HTTP client, or regex follows the exact same pattern.
Why This Matters
- Security: Audited Rust crates vs. hand-rolled crypto
- Speed: Zero-cost abstractions, no interpreter overhead
- Reliability: Rust’s type system catches bugs at compile time
- Velocity: Days to add features, not months
- Maintenance: Crate updates flow through automatically
- Cross-platform: Rust handles platform differences
This is Seq’s unfair advantage: a concatenative language with the full power of the Rust ecosystem behind it.
Binary Contents
A seqc build binary contains exactly what the source uses. The
runtime ships with every capability built in, and the link removes
what the program does not reference.
There are no seqc flags for capability selection. There are no
source annotations. The typechecker already knows which builtins
each program touches; the linker uses the seq_main reachability
boundary to drop the rest. A hello world ships no HTTP code, no
TLS, no regex, no crypto, no compression — and pays nothing for
their presence in the runtime archive.
This means “batteries included” is honest: adding a builtin to the runtime imposes no cost on programs that do not call it.
Note: The runtime crate (
crates/runtime/Cargo.toml) still exposes Cargo features (crypto,http,regex,compression) for builds that bypassseqcand depend on the runtime directly. These are a Cargo-build internal — they are never surfaced throughseqc buildand have no bearing on what a Seq source program can reference.
Current State
Runtime Builtins (Rust FFI)
| Category | Capabilities |
|---|---|
| Core | Stack ops, arithmetic, booleans, bitwise |
| Types | Int, Float, Bool, String, Symbol, List, Map, Variant |
| Strings | Concat, split, trim, case conversion, JSON escape |
| I/O | stdin/stdout, file read/write, path operations |
| Concurrency | Channels, strands (green threads), weave (coroutines) |
| Networking | TCP (listen/accept/connect/read/write/close/local-port), UDP (bind/send-to/recv-from), TLS client upgrade, HTTP client (GET/POST/PUT/DELETE), DNS resolve |
| Time | Unix timestamp, high-res time, sleep |
| Testing | Assertions, test runner, pass/fail counts |
| Serialization | SON format (Seq Object Notation) |
| Encoding | Base64 (standard + URL-safe), Hex |
| Crypto | SHA-256, HMAC-SHA256, AES-256-GCM, PBKDF2, Ed25519 signatures, secure random, UUID v4 |
| HTTP Client | GET, POST, PUT, DELETE with TLS support |
| Regex | match, find, find-all, replace, captures, split, valid? |
| Compression | gzip, gunzip, zstd, unzstd with compression levels |
| OS | Args, env vars, path operations, exec, exit |
Standard Library (Pure Seq)
Located in crates/compiler/stdlib/ (~2900 lines):
| Module | Lines | Description |
|---|---|---|
json.seq | 1166 | Full JSON parser and encoder |
yaml.seq | 752 | YAML parser |
zipper.seq | 328 | Functional list zipper |
http.seq | 190 | HTTP response building, request parsing |
imath.seq | 145 | Integer math utilities (abs, min, max, clamp) |
fmath.seq | 109 | Float math utilities |
signal.seq | 66 | Signal handling |
son.seq | 57 | SON serialization helpers |
list.seq | 55 | List helpers |
stack-utils.seq | 46 | Stack manipulation utilities |
map.seq | 30 | Map helpers |
HTTP Server Example
examples/http/http_server.seq (18KB) demonstrates:
- Concurrent request handling with strands
- Channel-based worker dispatch
- HTTP routing with
cond - Closure capture for connection handling
# Working HTTP server pattern
8080 net.tcp.listen
[ conn-id |
conn-id net.tcp.read
http-request-path
cond
[ "/health" string.equal? ] [ drop "OK" http-ok ]
[ "/api" string.starts-with ] [ handle-api ]
[ true ] [ drop "Not Found" http-not-found ]
end
conn-id net.tcp.write
conn-id net.tcp.close
] accept-loop
Gaps for “Batteries Included”
| Category | Status | Priority |
|---|---|---|
| HTTP client | Complete | High |
| Regex | Complete | Medium |
| TLS/HTTPS | Complete (via rustls) | Medium |
| Templates | Not started | Medium |
seq fmt | Not started | Medium |
seq.toml | Not started | Medium |
| Logging | Not started | Low |
| Compression | Complete | Low |
| Database | Not started | Future |
| HTML parsing | Not started | Future |
Already Mature
| Category | Status |
|---|---|
| JSON | Complete (1166 lines) |
| YAML | Complete (752 lines) |
| HTTP server | Working (helpers + example) |
| HTTP client | Complete (builtin) - GET, POST, PUT, DELETE with TLS |
| Regex | Complete (builtin) - match, find, replace, captures, split |
| Compression | Complete (builtin) - gzip, zstd with levels |
| LSP | Complete (2200+ lines) - diagnostics, completions |
| REPL | Complete - with LSP integration, vim keybindings |
| Testing | Complete (builtin) |
| Result/Option | Convention-based (value Bool) pattern, no stdlib module |
| Base64/Hex | Complete (builtin) - standard, URL-safe, hex |
| Crypto Phase 1 | Complete (builtin) - SHA-256, HMAC, random, UUID |
| Crypto Phase 2 | Complete (builtin) - AES-256-GCM encryption, PBKDF2 key derivation |
| Crypto Phase 3 | Complete (builtin) - Ed25519 digital signatures |
Priority 1: HTTP Client
The HTTP client is a hand-rolled HTTP/1.1 implementation sitting on the may-aware TCP/TLS/DNS layers — every IO step yields the cooperative carrier instead of blocking it. It keeps its own connection pool keyed on (scheme, host, port).
API
# GET request - returns response map
"https://api.example.com/users" net.http.get
# Stack: ( Map ) where Map = { "status": 200, "body": "...", "ok": true }
# POST request with body and content-type
"https://api.example.com/users" "{\"name\":\"Alice\"}" "application/json" net.http.post
# Stack: ( Map )
# PUT request (same signature as POST)
"https://api.example.com/users/1" "{\"name\":\"Bob\"}" "application/json" net.http.put
# DELETE request
"https://api.example.com/users/1" net.http.delete
# Stack: ( Map )
Response Map
All HTTP operations return a Map with these keys:
"status"(Int): HTTP status code (200, 404, 500, etc.) or 0 on connection error"body"(String): Response body as text"ok"(Bool): true if status is 2xx, false otherwise"error"(String): Error message (only present on failure)
Example Usage
# Make a GET request and handle the response
"https://httpbin.org/get" net.http.get
dup "ok" map.get drop
if
"body" map.get drop io.write-line
else
"error" map.get drop "Error: " swap string.concat io.write-line
then
Implementation Details
- Transport: hand-rolled HTTP/1.1 over
may-aware TCP /rustlsTLS - TLS: Built-in via
rustlswithringcrypto provider (no OpenSSL dependency) - Per-IO request/response timeout: 30s default, override with
SEQ_HTTP_REQUEST_TIMEOUT_MS - TLS handshake timeout: 10s default, override with
SEQ_TLS_HANDSHAKE_TIMEOUT_MS - TCP connect timeout: 10s default, override with
SEQ_TCP_CONNECT_TIMEOUT_MS - Max body size: 10 MB
- Connection pooling: Process-wide pool keyed on
(scheme, host, port); idle entries return viaConnection: keep-alive
Security: SSRF Protection
The HTTP client includes built-in SSRF protection. The following are automatically blocked:
- Localhost:
localhost,*.localhost,127.x.x.x - Private networks:
10.x.x.x,172.16-31.x.x,192.168.x.x - Link-local/Cloud metadata:
169.254.x.x(blocks AWS/GCP/Azure metadata) - IPv6 private: loopback, link-local, unique local addresses
- Non-HTTP schemes:
file://,ftp://,gopher://, etc.
Blocked requests return an error response with ok=false.
Additional recommendations:
- Use domain allowlists for sensitive applications
- Apply network-level egress filtering
Priority 2: Regular Expressions
Regex support is implemented via the Rust regex crate (v1.11). Fast, safe, and no catastrophic backtracking.
# Check if pattern matches anywhere in string
"hello world" "wo.ld" regex.match? # ( String String -- Bool )
# Find first match
"a1 b2 c3" "[a-z][0-9]" regex.find # ( String String -- String Bool )
# Find all matches
"a1 b2 c3" "[a-z][0-9]" regex.find-all # ( String String -- List Bool )
# Replace first occurrence
"hello world" "world" "Seq" regex.replace # ( String String String -- String Bool )
# Replace all occurrences
"a1 b2 c3" "[0-9]" "X" regex.replace-all # ( String String String -- String Bool )
# Extract capture groups
"2024-01-15" "(\\d+)-(\\d+)-(\\d+)" regex.captures
# ( String String -- List Bool ) returns ["2024", "01", "15"] true
# Split by pattern
"a1b2c3" "[0-9]" regex.split # ( String String -- List Bool )
# Check if pattern is valid
"[a-z]+" regex.valid? # ( String -- Bool )
Examples:
examples/text/regex-demo.seq- Demonstrates all regex operationsexamples/text/log-parser.seq- Practical log parsing with regex
Priority 3: Cryptography
Crypto is essential for real-world applications but often requires hunting through external packages. A batteries-included approach means shipping these out of the box.
Tier 1: Essential
| API | Rust Crate | Use Cases |
|---|---|---|
crypto.sha256 | sha2 | Checksums, content addressing, password hashing input |
crypto.hmac-sha256 | hmac + sha2 | Webhook verification, JWT signing, API auth |
crypto.random-bytes | rand | Tokens, nonces, salts, session IDs |
crypto.uuid4 | uuid | Unique identifiers |
crypto.constant-time-eq | custom | Timing-safe comparison for signatures |
# Hashing
"hello world" crypto.sha256 # ( String -- String ) hex-encoded
# HMAC for API authentication
"webhook-payload" "secret-key" crypto.hmac-sha256
# ( message key -- signature )
# Verify webhook signature
received-sig computed-sig crypto.constant-time-eq
# ( String String -- Bool ) timing-safe comparison
# Generate secure random token
32 crypto.random-bytes # ( n -- String ) 32 random bytes as 64-char hex string
# Generate UUID v4
crypto.uuid4 # ( -- String ) "550e8400-e29b-41d4-a716-446655440000"
Tier 2: Encryption
| API | Rust Crate | Use Cases |
|---|---|---|
crypto.aes-gcm-encrypt | aes-gcm | Encrypting data at rest, secure storage |
crypto.aes-gcm-decrypt | aes-gcm | Decrypting data |
crypto.pbkdf2-sha256 | pbkdf2 | Password-based key derivation |
# Symmetric encryption (AES-256-GCM)
# Key must be 64 hex chars (32 bytes = 256 bits)
plaintext hex-key crypto.aes-gcm-encrypt # ( String String -- String Bool )
ciphertext hex-key crypto.aes-gcm-decrypt # ( String String -- String Bool )
# Key derivation from password
"user-password" "salt" 100000 crypto.pbkdf2-sha256
# ( password salt iterations -- hex-key Bool )
# Full example: derive key and encrypt
"user-password" "unique-salt" 100000 crypto.pbkdf2-sha256
if
"secret data" swap crypto.aes-gcm-encrypt
if
"Encrypted: " swap string.concat io.write-line
else
drop "Encryption failed" io.write-line
then
else
drop "Key derivation failed" io.write-line
then
Tier 3: Signatures
| API | Rust Crate | Use Cases |
|---|---|---|
crypto.ed25519-keypair | ed25519-dalek | Generate signing keys |
crypto.ed25519-sign | ed25519-dalek | Digital signatures |
crypto.ed25519-verify | ed25519-dalek | Signature verification |
# Generate keypair
crypto.ed25519-keypair # ( -- public-key private-key ) both as 64-char hex
# Sign a message
message private-key crypto.ed25519-sign # ( String String -- String Bool )
# Verify signature
message signature public-key crypto.ed25519-verify # ( String String String -- Bool )
# Full example
crypto.ed25519-keypair
"Important document" swap crypto.ed25519-sign
if
swap "Important document" rot rot crypto.ed25519-verify
if "Signature valid!" else "Signature invalid!" then io.write-line
else
drop drop "Signing failed" io.write-line
then
Encoding Helpers
Available as encoding.* builtins:
# Base64 (standard with padding)
"hello" encoding.base64-encode # ( String -- String ) "aGVsbG8="
"aGVsbG8=" encoding.base64-decode # ( String -- String Bool )
# URL-safe Base64 (no padding, for JWTs/URLs)
data encoding.base64url-encode # ( String -- String )
encoded encoding.base64url-decode # ( String -- String Bool )
# Hex (lowercase output, case-insensitive decode)
"hello" encoding.hex-encode # ( String -- String ) "68656c6c6f"
"68656c6c6f" encoding.hex-decode # ( String -- String Bool )
Priority 4: Compression
Data compression via gzip and Zstandard (zstd). All operations use base64 encoding for string-safe output.
API
# Gzip compression (default level 6)
"hello world" compress.gzip # ( String -- String Bool )
# Gzip with custom level (1-9, where 1=fastest, 9=best)
"hello world" 9 compress.gzip-level # ( String Int -- String Bool )
# Gzip decompression
compressed compress.gunzip # ( String -- String Bool )
# Zstandard compression (default level 3)
"hello world" compress.zstd # ( String -- String Bool )
# Zstandard with custom level (1-22, where 1=fastest, 22=best)
"hello world" 19 compress.zstd-level # ( String Int -- String Bool )
# Zstandard decompression
compressed compress.unzstd # ( String -- String Bool )
Return Values
All compression operations return ( String Bool ):
- On success:
compressed-data true(data is base64-encoded) - On failure:
error-message false
Decompression accepts base64-encoded input and returns the original string.
Example Usage
# Compress and decompress with gzip
"Hello, World!" compress.gzip
if
dup "Compressed: " swap string.concat io.write-line
compress.gunzip
if
"Decompressed: " swap string.concat io.write-line
else
drop "Decompression failed" io.write-line
then
else
drop "Compression failed" io.write-line
then
# Compare compression algorithms
"Large text data..." dup
compress.gzip if string.length else drop 0 then
swap compress.zstd if string.length else drop 0 then
# Compare sizes
When to Use Each
| Algorithm | Best For | Level Range |
|---|---|---|
| gzip | Web content, HTTP compression, broad compatibility | 1-9 |
| zstd | Large data, better ratio, modern systems | 1-22 |
- gzip: Universal compatibility, good for HTTP
Content-Encoding - zstd: Better compression ratio and speed, ideal for data storage
Implementation Details
- Crates:
flate2(gzip),zstd(Zstandard) - Output encoding: Base64 for string-safe transport
- Input/Output: String → compressed base64 String
Examples:
examples/io/compress-demo.seq- Demonstrates all compression operations
Design Principles
1. Composition Over Configuration
# Good: Composable pieces
request
auth-middleware
logging-middleware
rate-limit-middleware
handler
http.handle
# Avoid: Giant config objects
2. Stack-Friendly APIs
# Good: Works naturally on stack
users [ is-active ] list.filter
# Avoid: Deeply nested structures that fight the stack
3. Explicit Over Magic
# Good: Clear what happens
response "body" map.get drop json-parse drop
# Avoid: Hidden transformations
4. Errors as Values
# Use (value Bool) pattern
"data.txt" file.slurp # ( String -- String Bool )
if
process-content
else
drop "File not found" io.write-line
then
5. Consistent Naming
| Pattern | Example | Meaning |
|---|---|---|
noun.verb | net.http.get, json-serialize | Action on type |
noun? | list.empty?, map.has? | Predicate |
-> | string->int, int->float | Conversion |
Comparison: Seq vs Go
| Aspect | Go | Seq |
|---|---|---|
| Paradigm | Imperative, structural | Stack-based, functional |
| Concurrency | Goroutines + channels | Strands + channels |
| Error handling | error return value | Result types on stack |
| Generics | Type parameters | Row polymorphism |
| Build | go build | seqc build |
| Packages | Module path | include std:module |
| Std library | ~150 packages | ~15 modules (focused) |
References
- Go Standard Library
- HTMX - HTML-centric approach to interactivity
- Hyperscript - Stack-like scripting for HTML
- Factor - Concatenative language with rich stdlib