fastc-core¶
fastc-core is the curated package set that ships alongside fastC v1.0. Each package's public API lives in two places:
- In the v1.0 compiler's built-in prelude — every fastC program
can write
use cli::has_flag;(or any other module) without installing anything. - In a public preview repo at
github.com/Skelf-Research/fastc-core-<name>— these repos host the canonical API documentation and will become installable viafastc addwhen the v1.1 vendor-consumption flow ships.
Until v1.1 lands, the v1.0 prelude is the implementation; the GitHub repos are the spec. Both surfaces are kept in lock-step so code written against the prelude today moves to the vendor-installed form unchanged.
The eleven packages¶
| Package | Purpose | Cap |
|---|---|---|
cli |
argv access + flag parsing | none |
log |
structured leveled logging | none |
json |
JSON encode + integer-field decode | none |
toml |
read-only flat-table TOML | none |
http |
HTTP/1.1 client | CapNetConnect |
time |
wall-clock + ISO 8601 | CapTimeRead |
base64 |
RFC 4648 encode/decode | none |
uuid |
RFC 4122 v4 + parse/format | CapRand for v4 |
crypto-primitives |
SHA-256, HMAC, constant-time compare | CapRand for random_bytes |
regex |
Thompson NFA, no backreferences | none |
sqlite |
FFI to libsqlite3 | CapFsWrite |
Five packages need no capability token — they are pure data transforms
or read-only views of process state. The other six gate I/O through
Cap* tokens minted in main via caps::init(); see
Capabilities for the threading rules.
cli¶
Argv access and flag parsing.
use cli::count; // () -> i32
use cli::arg_at; // (i: i32) -> raw(u8) — null when OOB
use cli::program_name; // () -> raw(u8)
use cli::has_flag; // (name: raw(u8)) -> bool
use cli::flag_value; // (name: raw(u8)) -> raw(u8) — null if absent
use cli::flag_int; // (name: raw(u8), fallback: i32) -> i32
use cli::is_null; // (p: raw(u8)) -> bool
Two flag forms — --name=value and --name value. First match wins.
use cli::flag_int;
use cli::has_flag;
fn main() -> i32 {
let n: i32 = flag_int(cstr("count"), 1);
if (has_flag(cstr("verbose"))) {
// verbose path
}
return n;
}
Repo: https://github.com/Skelf-Research/fastc-core-cli
log¶
Structured leveled logging. Four levels (debug / info / warn /
error) plus kv_* helpers for typed key-value pairs.
kv_* calls emit on the same line as the next level call:
use log::info;
use log::kv_int;
use log::kv_str;
fn main() -> i32 {
kv_str(cstr("user"), cstr("alice"));
kv_int(cstr("requests"), 42);
info(cstr("hourly stats"));
return 0;
}
Produces user="alice" requests=42 [INFO] hourly stats.
All log functions allocate nothing — safe inside @noalloc regions.
Repo: https://github.com/Skelf-Research/fastc-core-log
json¶
A builder-style encoder plus a small decoder slice. The v1 decoder covers the "pull an integer out of an HTTP response" case; a full DOM ships when there's user demand for it.
use json::new_builder;
use json::obj_start;
use json::obj_end;
use json::key;
use json::int_value;
use json::str_value;
use json::find_int; // (text: raw(u8), key: raw(u8), fallback: i64) -> i64
Encoding:
fn render() -> Str {
let b: JsonBuilder = new_builder();
obj_start(addrm(b));
key(addrm(b), cstr("id")); int_value(addrm(b), 42);
key(addrm(b), cstr("name")); str_value(addrm(b), cstr("alice"));
obj_end(addrm(b));
return (deref(addr(b))).out;
}
Decoding (top-level integer field):
let resp: raw(u8) = cstr("{\"id\": 42, \"count\": 7}");
let id: i64 = find_int(resp, cstr("id"), cast(i64, -1));
Repo: https://github.com/Skelf-Research/fastc-core-json
toml¶
Read-only flat-table parser scoped to the root table. Skips
[section] headers entirely. Honors # ... comments. The 80% case
is "pull a port or timeout out of fastc.toml-shaped config" — that
is what v1 covers.
use toml::find_int; // (text: raw(u8), key: raw(u8), fallback: i64) -> i64
use toml::find_bool; // (text: raw(u8), key: raw(u8), fallback: bool) -> bool
let cfg: raw(u8) = cstr("port = 8080\ndebug = true\n");
let port: i64 = find_int(cfg, cstr("port"), cast(i64, -1)); // 8080
let debug: bool = find_bool(cfg, cstr("debug"), false); // true
Out of scope in v1: arrays of tables, inline tables, dotted-key paths, date / time values, multi-line strings.
Repo: https://github.com/Skelf-Research/fastc-core-toml
http¶
HTTP/1.1 client. v1 covers GET and the status code; bodies,
headers, methods, redirects, and TLS land in follow-up slices.
Returns the 3-digit HTTP status or -1 on any error.
use http::get_status;
use caps::init;
fn main() -> i32 {
let bundle: Caps = init();
let status: i32 = get_status(
addr(bundle.net_connect),
cstr("127.0.0.1"),
8088,
cstr("/"),
);
return status;
}
CapNetConnect is the wedge — a function without c: ref(CapNetConnect)
in its signature structurally cannot reach the network. The token is
minted only in main via caps::init(); library code never
fabricates it.
WASI: the runtime ships link-compatible stubs that return -1 until
the wasi:sockets Preview 2 surface stabilizes.
Repo: https://github.com/Skelf-Research/fastc-core-http
time¶
Wall-clock + ISO 8601.
use time::now; // (cap: ref(CapTimeRead)) -> i64 — epoch seconds
use time::now_ms; // (cap: ref(CapTimeRead)) -> i64 — epoch milliseconds
use time::format_iso8601; // (epoch_secs: i64) -> Str — RFC 3339, always UTC
use time::parse_iso8601; // (s: Str) -> opt(i64) — None on malformed input
use time::Duration;
use time::Duration::from_secs; // (s: i64) -> Duration
use time::Duration::from_millis; // (ms: i64) -> Duration
now / now_ms need ref(CapTimeRead) — wall-clock reads are an
observable side channel. format_iso8601, parse_iso8601, and the
Duration constructors are pure.
use time::now;
use time::format_iso8601;
use caps::time_read;
fn main() -> i32 {
let t: i64 = now(&time_read);
let stamp: Str = format_iso8601(t);
return 0;
}
Both clock readers clamp negative system clocks to 0 so downstream
i64 math is monotonic-ish even on broken hosts.
Repo: https://github.com/Skelf-Research/fastc-core-time
base64¶
RFC 4648 encode / decode. Standard alphabet and URL-safe alphabet.
use base64::encode; // (bytes: slice(u8)) -> Str
use base64::decode; // (s: Str) -> opt(Vec[u8])
use base64::encode_url; // (bytes: slice(u8)) -> Str
use base64::decode_url; // (s: Str) -> opt(Vec[u8])
| Function pair | Alphabet | Padding |
|---|---|---|
encode / decode |
§4 standard (A-Za-z0-9+/) |
= required |
encode_url / decode_url |
§5 URL-safe (A-Za-z0-9-_) |
none |
use base64::encode;
use base64::decode;
fn main() -> i32 {
let raw: slice(u8) = b"fastC v1.0";
let s: Str = encode(raw); // "ZmFzdEMgdjEuMA=="
let back: opt(Vec[u8]) = decode(s);
return 0;
}
Decoders return None on any invalid input — characters outside
the chosen alphabet, malformed padding, or truncated quanta.
Pure data transform — no capability token.
Repo: https://github.com/Skelf-Research/fastc-core-base64
uuid¶
RFC 4122 v4 generation + parse / format.
use uuid::Uuid; // struct { bytes: arr(u8, 16) }
use uuid::v4; // (cap: ref(CapRand)) -> Uuid
use uuid::nil; // () -> Uuid
use uuid::parse; // (s: Str) -> opt(Uuid)
use uuid::format; // (u: Uuid) -> Str — canonical lowercase hyphenated
use uuid::v4;
use uuid::format;
use caps::CapRand;
fn main(cap: ref(CapRand)) -> i32 {
let u: Uuid = v4(cap);
let s: Str = format(u);
return 0;
}
v4 needs ref(CapRand) — every entropy draw flows through a
capability surface and is auditable. nil, parse, and format
are pure.
Repo: https://github.com/Skelf-Research/fastc-core-uuid
crypto-primitives¶
SHA-256, HMAC, constant-time compare, secure random.
use crypto_primitives::sha256;
// (data: slice(u8)) -> arr(u8, 32)
use crypto_primitives::hmac_sha256;
// (key: slice(u8), data: slice(u8)) -> arr(u8, 32)
use crypto_primitives::constant_time_compare;
// (a: slice(u8), b: slice(u8)) -> bool
use crypto_primitives::random_bytes;
// (cap: ref(CapRand), n: usize) -> Vec[u8]
use crypto_primitives::sha256;
use crypto_primitives::constant_time_compare;
fn main() -> i32 {
let msg: slice(u8) = b"hello, fastc";
let a: arr(u8, 32) = sha256(msg);
let b: arr(u8, 32) = sha256(msg);
if (constant_time_compare(a[..], b[..])) {
return 0;
}
return 1;
}
sha256, hmac_sha256, constant_time_compare are pure.
random_bytes needs ref(CapRand) — drawing entropy without it is
a compile-time error, not a runtime one.
These primitives are intended for general-purpose use inside fastC programs. The implementation has not been independently audited; users with FIPS 140-3 or NIST-validation obligations should wrap a vetted native library through fastC's FFI instead.
Repo: https://github.com/Skelf-Research/fastc-core-crypto-primitives
regex¶
Thompson NFA. No backreferences. Linear-time guarantee
(O(n * m) in input and pattern), no catastrophic backtracking.
use regex::Regex; // opaque
use regex::Match; // { start: usize, end: usize }
use regex::RegexError;
use regex::compile; // (pattern: Str) -> res(Regex, RegexError)
use regex::match_one; // (re: ref(Regex), text: Str) -> opt(Match)
use regex::match_all; // (re: ref(Regex), text: Str) -> Vec[Match]
use regex::replace; // (re: ref(Regex), text: Str, replacement: Str) -> Str
use regex::release; // (re: Regex) -> void
Supported constructs: literals, ., [a-z], [^a-z], ^, $,
*, +, ?, {n,m}, (...), |. Anything else is a
RegexError from compile.
use regex::compile;
use regex::match_all;
use regex::release;
fn main() -> i32 {
let re: Regex = compile(cstr("[a-z]+"))?;
let hits: Vec[Match] = match_all(ref(re), cstr("the quick brown fox"));
release(re);
return 0;
}
Backreferences are deliberately out of scope — they turn matching into an NP-hard problem and are the source class behind every production ReDoS incident. Code that genuinely needs them can call PCRE through FFI and pay the variance explicitly.
Pure — no capability token.
Repo: https://github.com/Skelf-Research/fastc-core-regex
sqlite¶
FFI to system libsqlite3. Pass -lsqlite3 is handled by the build
driver when any module imports sqlite::*.
use sqlite::Db;
use sqlite::Cursor;
use sqlite::Row;
use sqlite::SqliteError;
use sqlite::open; // (path: Str, cap: ref(CapFsWrite)) -> res(Db, SqliteError)
use sqlite::exec; // (db: ref(Db), sql: Str) -> res(i32, SqliteError)
use sqlite::query; // (db: ref(Db), sql: Str) -> res(Cursor, SqliteError)
use sqlite::next; // (cursor: mref(Cursor)) -> opt(Row)
use sqlite::get_int; // (row: ref(Row), col: i32) -> i64
use sqlite::get_text; // (row: ref(Row), col: i32) -> Str
use sqlite::close; // (db: Db) -> void
use sqlite::open;
use sqlite::exec;
use sqlite::query;
use sqlite::next;
use sqlite::get_text;
use sqlite::close;
use caps::init;
fn main() -> i32 {
let bundle: Caps = init();
let db: Db = open(":memory:", addr(bundle.fs_write))?;
exec(addr(db), "CREATE TABLE users (id INTEGER, name TEXT)")?;
exec(addr(db), "INSERT INTO users VALUES (1, 'ada')")?;
let mut cur: Cursor = query(addr(db), "SELECT id, name FROM users")?;
loop {
match next(mref(cur)) {
Some(row) => { /* read row */ }
None => break,
}
}
close(db);
return 0;
}
open requires ref(CapFsWrite) — never CapFsRead. SQLite writes
to a rollback journal even for read-only queries the moment a
transaction needs durability, so the cap gate trips at open to
match the engine's real I/O profile.
Repo: https://github.com/Skelf-Research/fastc-core-sqlite
How to consume — today¶
In v1.0, every fastc-core module lives in the prelude. Imports just work:
No fastc.toml entries. No lockfile churn. No fastc add step.
The compiler binary is the implementation, and the public preview
repos document the spec.
How to consume — v1.1 vendor flow¶
When the fastc add consumption flow ships in v1.1, fastc.toml
will accept:
[dependencies]
fastc-core-cli = { git = "https://github.com/Skelf-Research/fastc-core-cli", rev = "v0.1.0", sha256 = "..." }
fastc-core-http = { git = "https://github.com/Skelf-Research/fastc-core-http", rev = "v0.1.0", sha256 = "..." }
The rev pins a tag or commit; the sha256 is the integrity
digest recorded in fastc.lock and re-verified on every build.
The exact same use cli::has_flag; source compiles against either
the prelude-bundled v1.0 implementation or the v1.1 vendored package
without source changes.
Why "one curated answer per domain"¶
fastC commits to one curated stdlib answer per domain instead of an ecosystem with eleven competing JSON libraries. The reasoning is concrete:
- Decision-load drops to zero. A new fastC program needs a
logger, a JSON encoder, a TOML config reader, and an HTTP
client. The answer is
log,json,toml,http. There is no shortlist to evaluate, no benchmark spreadsheet to maintain, no Reddit thread to read. - LLM-write paths simplify. When the model has one correct import for "open a sqlite database", code generation collapses to a single path. The error surface narrows from "did the model pick the right crate" to "did the model pick the right function".
- Capability audits stay feasible. Every fastc-core package
declares its
Cap*requirements up front in this table. Auditing the I/O surface of a fastC program is a finite, tractable task; auditing the I/O surface of a Cargo dependency tree is not.
The trade-off is real — a domain whose 80% case isn't covered by the v1 surface (JSON streaming, regex backreferences, TLS) needs a follow-up package or an FFI call. The curated set is the floor, not the ceiling.
Cross-links¶
- Capabilities —
CapNetConnect,CapTimeRead,CapFsWrite,CapRandand the fabrication-check rules - Modules —
use mod::item;imports and the v1.3 header surface - CLI:
fastc add— the v1.1 vendor consumption flow - CLI:
fastc lock—fastc.lockintegrity surface