Skip to content

v0.12.0

Choose a tag to compare

@hpopp hpopp released this 12 Jun 20:29
3a90523

Added

  • TLS support in the net package, built on the bundled BoringSSL. TCPSocket.connect_tls(host, port) establishes a verified connection (certificate chain and hostname checked against the system CA bundle); connect_tls_with / upgrade_tls / accept_tls cover custom configs and server-side handshakes. TLSConfig.client() / .insecure() / .server(cert, key) / .with_ca(ca) configure verification and credentials. Certificates and keys are in-memory Crypto.Certificate / Crypto.PrivateKey values parsed from PEM text, validated up front — a swapped cert/key pair or an encrypted key fails with a clear message instead of a handshake error. The HTTP client routes https:// URLs through TLS automatically, so HTTP.get("https://...") works out of the box.
  • koja run now executes projects with the interpreter by default — no --backend=llvm required. The Process entry runs in-process with argv passing, sockets, TLS, binary pattern matching, and receive over lifecycle signals (SIGTERM / SIGINT / SIGHUP) and after timeouts, so real services run unmodified with millisecond startup. Process features the interpreter does not cover yet (spawn, cross-process messaging) report a clear runtime error with a --backend=llvm hint.
  • Implicit numeric widening into the default types: sized integers (Int8 / Int16 / Int32 / UInt8 / UInt16 / UInt32) now flow into Int slots without explicit conversion, and Float32 into Float — at call arguments, fields, payloads, returns, bindings, and assignments. Widening is always lossless. Sideways widening (Int8 to Int16), narrowing, UInt64 to Int, binary operators, and generic inference still require exact types.
  • Checked numeric narrowing methods: Int.to_int8 / to_int16 / to_int32 / to_uint8 / to_uint16 / to_uint32 / to_uint64 and UInt64.to_int, each returning Result<T, NumericConversionError> with OutOfRange when the value does not fit. Float.to_float32 is total and rounds to the nearest representable value.
  • koja test --trace prints each test grouped by struct with its path:line and per-test timing instead of progress dots. Names print before the test runs, so a crashing test leaves its name as the last line of output. Trace runs skip the per-binary timeout for long debugging sessions.
  • koja test now enforces a 60-second per-binary timeout, so a hung test process fails fast instead of stalling the suite.
  • Fd.read_binary(count) and TCPSocket.read_binary(count) -- read up to count bytes as a Binary for binary wire protocols.
  • Binary.at(index) -> Option<Int> and Binary.slice(range) -> Binary -- O(1) byte access and inclusive-range byte slicing (endpoints clamp). The byte-oriented complement to String.get / String.slice, whose codepoint indexing is O(n) per call; scanners and parsers should step over text.to_binary() instead.

Changed

  • Breaking: Koja moved from affine ownership to value semantics. Every binding, parameter, return, and field is now an independent value: assignment and argument passing copy, and a value stays usable for as long as it is in scope. Copies are cheap — heap-backed values are reference-counted, shared until mutated (copy-on-write), and reclaimed deterministically at scope exit, with no garbage collector. The move keyword, borrow-by-default parameters, use-after-move errors, and the fn(move T) -> U syntax are gone. Remove move from your signatures; code that previously had to satisfy move/borrow rules now just works.
  • Breaking: the net package reports failures with typed errors instead of strings. Socket operations return Result<_, Net.SocketError>, a cause-based enum (ConnectionRefused, TimedOut, NameNotFound, ..., Unknown(errno)) — match on the cause instead of parsing message text. TLS-capable operations return Result<_, Net.SocketError | Net.TLSError>, with TLSError.VerificationFailed(VerificationError) carrying why certificate verification failed. Crypto.Certificate.parse / Crypto.PrivateKey.parse return Result<_, Crypto.PEMError>. All error enums expose message() for display. TCPEvent.Error now carries a SocketError, and TLSSession.read is removed — use TCPSocket.read, which decodes UTF-8 and reports SocketError.InvalidUTF8.
  • Breaking: Int.parse and Float.parse (and the String.to_int / String.to_float wrappers) now return Result<T, NumericConversionError> instead of Result<T, String>: InvalidFormat for malformed text, OutOfRange for well-formed numbers that don't fit — including an integer overflowing 64 bits and a float like 1e999 that would round to infinity (previously parsed as inf). The inf / nan tokens are rejected as InvalidFormat.
  • Fd.write and TCPSocket.write now accept data: Binary | String (a String is encoded as UTF-8), unifying text and binary writes under one method.

Removed

  • Breaking: fn main is no longer a program entry point. Every compiled program's entry is a type implementing Process<C, M, R>, named by entry = "App" in koja.toml; the program exits with the code mapped from the entry's StopReason. Standalone .koja files can no longer be built or run — use a .kojs script for entry-free programs, or a project for compiled binaries (koja check file.koja still works). koja new scaffolds a Process entry, and the koja test harness runs as one too.
  • Breaking: The Clone protocol and .clone() method are removed. Under value semantics every binding is already an independent value, so explicit cloning is unnecessary — replace x.clone() with x.

Fixed

  • Fixed a scheduler race that could intermittently crash message-heavy programs with a segfault when a yielded process was resumed from a stale stack pointer.
  • Fixed a stack overflow in long-running processes: a self-recursive tail call written as the value of an if / match / receive (notably the receive ... after -> self.loop() actor loop) was not tail-call-optimized, so the loop grew the stack until it crashed. These loops now run in constant stack.
  • Passing a heap-owning local (e.g. a String built with <>, or a List) to a function or method that stores it no longer crashes or returns corrupt data under --backend=llvm.
  • Fixed an unbounded memory leak on common patterns: a heap-backed temporary passed as an argument (f(a <> b)), used as a receiver in a method chain (make().foo().bar()), or produced by a literal or constructor was never released. Long-running programs no longer bleed memory on routine work.
  • Message payloads that are never delivered — sent to a dead process, or still queued when the program exits — are now reclaimed instead of leaked.
  • Fixed a race in the I/O reactor that could leave a process blocked forever on a readiness event delivered between fd registration and the blocking state transition.
  • Closing a file descriptor that another process is blocked on now wakes that process with an error instead of stranding it forever; a Fd.watch owner of the closed fd receives a synthetic readiness-error event so its handler observes the hangup.
  • Closing a file descriptor now drops it from the reactor's watch maps, preventing spurious wakeups when the kernel recycles the fd.
  • Package-level functions are now callable from other packages with qualified syntax (HTTP.get(url)), as the 0.11.0 changelog advertised — previously this failed with unknown identifier. A call to a missing function in a known package now reports package Xhas no functiony``.
  • JSON.Decoder.decode is now O(n): it scans the input's UTF-8 bytes instead of indexing codepoints (which walked from the start of the string on every call, making the old decoder quadratic). Decoding a 200 KB payload drops from ~8 s to milliseconds.
  • The interpreter no longer deep-copies String / Binary payloads on every variable read and argument pass — buffers are now shared via reference counting, matching compiled binaries. Workloads that move large payloads through functions (e.g. decoding a big HTTPS JSON response) run several times faster.
  • koja format now lays out match / cond / receive arms consistently: when any arm body is long enough to wrap, every sibling arm breaks onto its own indented line instead of leaving short siblings inline next to a wrapped one.