TYCHO Search

Search documentation

    ↑ ↓ to move · Enter to open · Esc to close

    corelib — Tycho's core library

    Memory: Every corelib function puts its results in the caller's memory and returns independent values, never aliasing the inputs. The one exception is a C-owned opaque handle (ptr) from the C-backed packages below, which is not caller-managed: release it with that package's own free function.

    corelib is Tycho's core library: a set of packages under corelib/, imported with the core: collection root. The modules below are usable today.

    package main
    
    import "core:math"
    
    fn main():
        println(f"gcd={math.gcd(12, 18)}")
    

    Quick start

    1. import "core:<pkg>" in your source. The name <pkg> is bound to the package. The transpiler finds corelib/ next to its own binary by default, so no setup is needed in this repo. Set TYCHO_CORELIB to a corelib directory to override.
    2. Call its functions either free-standing (math.gcd(12, 18)) or method-style (x.abs() — any function can be called with its first argument as the receiver, see Methods).

    Resolution

    import "core:<pkg>" resolves to <corelib>/<pkg> and binds the name <pkg>, where <corelib> is TYCHO_CORELIB if set, otherwise corelib/ next to the transpiler binary. Non-core: imports stay relative to the importing package, unchanged. tychoc resolves core: natively, with no bundling step required.

    How corelib is shaped

    corelib is generic free functions over a type parameter $T, with where constraints (comparable(T), numeric(T), has_str(T)) picking which element types each function accepts. Array and iterator utilities are single generic packages over any element type [$T] (core:arrays, core:iter) rather than one package per element type. The functions are also callable method-style through method-style call, so index_of(xs, v) and xs.index_of(v) are the same call.

    Higher-order helpers (map / filter / reduce that take a function) live in core:iter and its siblings, and take a first-class fn/closure argument. Lambdas in Tycho are expression-bodied; pass a named function where you need a multi-line body.

    Packages

    The filesystem fence. A lexical check cannot see a symlink, so four functions make a real syscall, and all fail closed — "" is a refusal and callers must treat it as one before using the path.

    One caution: a check followed by a separate open can race if an attacker can write inside the root. resolve_under narrows that window to an attacker with write access inside the root; a fully race-free form would need the open done inside the check.

    C-shim (FFI-backed) modules

    A core module can wrap a C library via FFI. Drop a <module>/<module>_shim.c next to the .ty; the transpiler auto-compiles and links it on import "core:<module>" (no --shim needed). The .ty declares the shim's functions with extern fn (and extern "Lib" fn auto-adds -lLib for an external library; core:regex needs none — POSIX regex is in libc). Opaque handles cross as ptr (carried by value, never dereferenced or caller-managed — null / is_null). tychoc links a module's shim automatically — no --shim needed for a corelib package.

    Freeing a shim handle

    A ptr handle is not affine, so nothing in the type system stops a caller freeing one twice or reading one after free. core:crypto, core:tls and core:http guard that at run time by keeping the handle's own header: the payload is released on free, so a second free or a later use aborts by name (for example tycho: double free of crypto key handle) rather than reading freed memory. The kept header is never reused for a new handle, so a stale handle can never alias a later one; it is also never freed, so each freed handle keeps a small header for the life of the process — nothing measurable for a one-shot program, and the figure for a long-lived one to check is in the design note.

    core:image uses a monotonically numbered handle into a live set instead: releasing an image frees its header and pixels, and a stale ID is rejected before anything is read. That keeps the same double-free/use-after-free diagnosis without retaining one header per decoded image.

    External dependencies (C-shim deps)

    A shim that needs a system library beyond libc — one with headers to find and a link flag that varies by platform — declares it in a corelib/<module>/deps manifest: pkg-config package names, one per line (# comments and blank lines ignored). For example corelib/http/deps is just libcurl.

    The deps-backed modules are http (libcurl) and crypto (libcrypto), documented above, plus four more:

    A libc-only shim needs no deps — core:regex, core:os, core:net and core:signal among them; a library with no .pc file can still use extern "Lib" fn for a bare -lLib.

    Testing

    make corelib (→ corelib/run.sh): every corelib/test/<name>/main.ty is built and run, and must produce the golden corelib/test/<name>.out. Re-record goldens with RECORD=1 sh corelib/run.sh. Part of make ci.

    Examples

    Every module also has a small, readable usage example at examples/corelib/<name>/main.ty — idiomatic calls with human-friendly output, as opposed to the assertion-style tests above. make corelib-examples (→ examples/corelib/run.sh) validates them the same way against examples/corelib/<name>.out, with the same dependency skip, and is part of make ci.

    The corelib surface (pre-1.0 — NOT frozen)

    The corelib API is not frozen. The packages above are locked by make corelib and make corelib-examples, which is what makes the surface worth relying on in practice. What follows is the promise:

    Deprecation path. A package or function is deprecated by (1) a deprecated: notice in its doc comment, (2) an entry in CHANGELOG.md naming the replacement, and (3) a warning: on use emitted by the compiler.

    Step (3) is mechanical: a # deprecated: <text> comment line directly above a fn marks it, and every call site warns with <text>. Deprecated members keep working for the current major version. Removal is a breaking change: it happens only in a major version bump, is recorded in CHANGELOG.md and RELEASE_NOTES.md, and ships with the migration spelled out.

    Before 1.0 none of that binds: a removal needs the changelog entry and nothing else. Which packages are in the frozen surface is a decision 1.0 has to make, not one inherited from this list — see ROADMAP.md.


    Why corelib is shaped this way — the handle guards, the path-check layering, and the shim conventions — is in design-corelib, for maintainers.