Skip to content

Releases: Louiml/Rak

Rak v0.8.4

Choose a tag to compare

@github-actions github-actions released this 03 Oct 16:52

0.8.4 — 2026-10-03

Security and performance release. This one should be read before upgrading, not
after.

Security

FFI pointer provenance — an arbitrary write anywhere in the process is closed.

ffi_write and ffi_read did this:

unsafe { *((ptr as usize + off) as *mut u8) = byte }

with no check that ptr was ever allocated, and none that off was inside it.
Because ffi_ptr(n) builds a pointer from any integer,
ffi_write(ffi_ptr(ADDRESS), 0, 0x41) could write a byte anywhere the process
could reach. ffi_read was the matching read. This is the most severe item in the
security review, and unlike most of that list it was completely real.

A pointer is now provenanced if Rak allocated it (ffi_alloc,
ffi_string_to_cstr) or was told it owns a region (ffi_trust). Provenanced
pointers are range-checked on every access against the size recorded at allocation.
A pointer that is neither is refused by name, and the error says what to do about
it.

ffi_trust is the deliberate escape hatch. A language with FFI that refused every
foreign address would be useless, and resolving a symbol's address and then calling
it is legitimate work. What is not acceptable is an address that is silently
accepted, because that is indistinguishable from a safety check that always passes.
ffi_trust turns unchecked pointer arithmetic into an assertion written down in the
source — the same bargain unsafe { reason } makes everywhere else. It also
narrows: trusting 4 bytes of a 16-byte allocation makes an access at offset 4 fail
again, which is tested.

ffi_cstr_to_string was a denial of service by another route — it scanned for a NUL
byte with no bound, so a pointer to a NUL-free buffer walked off the end into
unmapped memory, reachable from a pointer that came out of a network response. It
is now bounded at 1 MiB.

ffi_read_i32 checks all four bytes, not merely that off is inside: off being
valid while the read runs three bytes past the end is still a read past the end.

The bounds arithmetic is done in u128, not u64, on purpose. ptr + off in u64
wraps, and a bounds check a large offset can defeat is not a check. There is a test
that specifically tries to defeat it.

Both backends share rak_stdlib::ffi::Allocations so the logic lives in one tested
place. ffi_trust is covered by the existing ffi_ capability prefix, so it needs
no new sandbox entry.

Existing ffi_alloc/ffi_free and legitimate access are unchanged and tested.

Performance

Constant folding. compile_expr had none. Every 2 * 3 became two LoadConsts,
an AddI and a push, on every evaluation — inside the innermost loop of every
numeric program. fold_int_binary now folds the literal-on-literal case for the
arithmetic and bitwise operators, and is deliberately narrow: both operands must be
literals; division or remainder by zero, out-of-range shift counts, and overflow all
fold to nothing so the runtime behaviour is unchanged.

rakc bench now measures something. The old version timed
rakc::eval(&source) against vm.run() and nothing else. That is not a backend
comparison: the interpreter figure included lexing, parsing and setup, the VM figure
was bytecode execution on an already-compiled chunk, and compile time was attributed
to nobody. It also used as_millis(), so anything under a millisecond printed
0 ms.

It now reports five phases — lex, parse, compile, interp, vm — each over --repeat
samples (default 5) after an unmeasured warm-up, as a median. The two execution
numbers are finally like for like, and the output labels them "execution only" so
they cannot be misread. On examples/bench.rak in a debug build it reports 6.61x,
which is a measured number where the README previously had a hand-written estimate.

A benchmark corpus. examples/bench/ has six workloads covering recursion,
arithmetic, arrays, map fields, strings and bytes.

Fixes

Three backend message divergences, all the same defect — the VM and the interpreter
described the same failure differently, so an error message told you which backend
you were on:

expression interpreter VM (before)
5 % 0 Division by zero rem by zero
5 / 0 Division by zero div by zero
n * x Undefined variable: x Undefined: x

The first two were Rust's internal panic wording copied into hand-written Err
strings. The interpreter was itself inconsistent on the third (Undefined variable:
in two places, Undefined: in a third); both backends are now normalised to the
clearer form.

Upgrade notes

If you use FFI, you may need to add ffi_trust calls. Any ffi_write/ffi_read
through a pointer that did not come from ffi_alloc or ffi_string_to_cstr will now
fail at the point of use rather than corrupting memory silently — which is the
intended behaviour, but it is a behavioural change.

Release notes are generated from .github/release_body.md at tag time.

Rak v0.8.3

Choose a tag to compare

@github-actions github-actions released this 03 Oct 12:31

Rak v0.8.3

Cross-module variables that behave like Python's, byte-exact binary file I/O, a
for loop that survives a NUL byte, and fmt that understands a format spec.

About the version number

This release is tagged v0.8.3. The tags before it read v0.4.0 … v0.7.2 and
then switched to v8.0.0, v8.1.0, v8.1.1, so v0.8.3 sits numerically below the
8.x line it follows.

That is deliberate, and it has one consequence worth stating plainly: cargo
will read this as a downgrade from 8.1.1.
A cargo update will move the dependency
backwards rather than forwards, and a lockfile pinning 8.1.1 will not move to
0.8.3 on its own. If you depend on Rak, take the commit rather than the tag, or
specify =0.8.3 deliberately.

The two commits this release contains are titled Rak v8.2.0, which was the number
in use when they were written. Rewriting them would have meant force-pushing already
public history to make the subjects agree with a number decided afterwards, which
seemed the worse trade. The titles describe the work; the tag names the release.

A for loop stopped at the first 0x00

The headline fix, and it is worse than anything else in this release.

let buf = bytes([0, 15, 16, 255])
let mut n = 0
for b in buf { n = n + 1 }
dump n            // interpreter: 4    VM, before: 0

The VM's loop decided whether to continue by testing the element's truthiness
rather than the loop bound — IndexGet, then JumpIfFalse. Since 0, "" and
false are all falsy, iteration ended at the first of them. No diagnostic, and
silent, because the interpreter was fine and the divergence gate did not cover it.

0x00 is the most common byte in a binary file, so for b in buffer walked a buffer
and then stopped dead at the first NUL. It was found by writing a hex editor on top
of Rak: the engine's first buffer walk died partway through a test file, and the
cause was a language bug rather than anything in the editor.

Op::Len now makes the bound idx < len. A loop bound has to be a comparison
against a length, not a test of the value being carried.

It survived review because the one test covering byte iteration used bytes([1, 2]),
which has no falsy byte in it.

fmt takes real format specs

dump fmt("{:02X}", 5)     // 5, before.  05, now.
dump fmt("{:#x}", 255)    // 0xff
dump fmt("[{:>4}]", n)     // right-aligned, width 4
dump fmt("{:.2f}", ratio)  // two decimals, on both backends

The spec was matched by asking whether the text contained 04X, 08X, x or X.
Exactly two widths worked and no other type did, so {:02X} — the width a hex dump
wants — fell through to the default rendering.

The VM's copy had drifted further and had no float branch at all, so
fmt("{:.2f}", x) printed a rounded value on the interpreter and the raw float on
the VM. Two hand-written copies of the same chain is the underlying mistake; they are
now one shared parser (rakc/src/fmt_spec.rs), and rakc/tests/fmt_specs.rs runs a
table of specs through both backends and fails if they disagree.

The width counts the sign, matching Rust, Python and Go: {:05} of -42 is -0042.

Cross-module variables

import m used to bind a snapshot of the module's exports, so a pub let mut the
module reassigned never reached the importer. A module's own top-level let mut
reset on every call, because a module body ran in a scope on the importer's
environment and Env::clone deep-copies scopes — bump(); bump() gave 1, 1.

import m
m.X = v         // writes the module's state, and only if it is `pub let mut`
from m import x as y   // a copy, on both backends

The interpreter already did this. Five of the six documented VM differences are now
closed: a private top-level is no longer visible to the importer, two modules may
export the same name, from m import x copies with or without an alias, mod { }
blocks no longer collide, and reading a private or misspelled name through a handle
reports it by name instead of returning nil.

The last one is not fixed, and docs/V8-KNOWN-ISSUES.md says what it needs: a
module body still sees a name it never declared. Closing it requires a module
scope in the compiler, and the compiler has no list of builtin globals — the VM
registers those at startup — so it cannot tell len from a leaked name. Guessing
would break every module that calls a builtin. The test that covers it fails loudly
with "convert this to agree_on" if it ever closes.

Byte-exact binary file I/O

file_read is fs::read_to_string, so it fails on any file containing a byte
sequence that is not valid UTF-8, and write coerces through UTF-8, so a 0xFF
came back as U+FFFD. There was no way to open a binary file at all.

let buf = file_read_bytes("firmware.bin")
buf[0] = 0xFF
file_write_bytes("patched.bin", buf)

let m = mmap_open("firmware.bin", "rw")
mmap_write(m, 0x100, 0x90)          // the mapping *is* the file

Plus bytes([...]), byte indexing, byte assignment, byte iteration, and buffer
concatenation. mmap_write refuses a read-only mapping and an out-of-range offset
by name, and a multi-byte write that would run past the end applies none of its
bytes.

Smaller things

  • Hex no longer renders padded. dump 0x00 printed
    0x0000000000000000 on the VM. The value carried a digit width and Display
    used it, but nothing ever set that width from the source — the compiler
    hardcoded 64 — so all sixteen slots were used. The field had exactly one reader,
    so it is gone.
  • A corrected claim. The known-issues entry said the two backends compared
    Hex differently. They never did: PartialEq has a cross-representation numeric
    fallback and Op::Eq goes straight through it. Retracted, with the evidence,
    rather than left as a warning that would send someone hunting a bug that is not
    there.
  • :dis desynced by one byte after every for loop, because IterItems has
    a 1-byte operand and was missing from the width table.
  • The VM's len did not accept a struct, which the interpreter's did.
  • The IDE version is pinned from the tag by scripts/pin-ide-version.sh, which
    has been run against v0.8.3 to confirm all three files take. v8.1.0 shipped
    four Tauri bundles named 8.0.0 because that check did not exist.

Tests

505 workspace tests pass. module_state.rs is 33, fmt_specs.rs is 9,
bytes_io.rs is 11 — each running its cases on both backends and failing on any
disagreement. The tests that used to pin a divergence now assert agreement; the one
that is still real says so in its name.

The hex editor these primitives were built for is at
https://github.com/Louiml/HexEditor.

Rak v8.1.0

Choose a tag to compare

@github-actions github-actions released this 01 Oct 18:26

Rak v8.1.0

The release where the two backends stopped being two languages.

Why this release exists

Rak has shipped a tree-walking interpreter and a bytecode VM for several
versions, behind one frontend. Every builtin and every language feature has to
be implemented twice, and until now nothing checked that it was.

v8.0.0 shipped nineteen builtins that existed only in the VM. rakc run — the
default backend — failed with Unknown function for every one of them. The test
suite passed, because it exercised each backend separately and nothing covered
the new builtins on the interpreter side at all. A per-backend test suite cannot
catch a per-backend omission.

So this release starts by measuring. rakc::run_on_both runs a program on both
backends and classifies how they agree, and tests/backend_parity.rs gates on
that two ways: a structural check that compares the registration tables
directly, and behavioural tests that require identical output.

The measurement found the gap was 140 builtins, not the three
docs/rak-features-spec.md described. abs, sort, split, sum,
to_string and print were all missing from rakc vm. It could not run an
ordinary program.

After this release: 34. All 34 are blocked on the same thing, and that is a
real limitation rather than a list of forgotten registrations — see Known
limitations
.

Backend parity

The gate

Nothing compared the backends before, so a builtin could be added to one and
forgotten on the other and the suite stayed green. Now it cannot:

  • registrations_match reads the registration sites in both backends and
    requires the name sets to match. Four shapes of registration are recognised,
    each of which was a false positive that would have made the gate untrustworthy
    if left: alternation arms ("regex_match" | "regex_is_match"), for name in [..] loops, vm_natives() tables, and the scope of eval_builtin itself so
    that regex method arms are not mistaken for builtins.
  • Twenty behavioural tests run programs on both and require identical
    output, which catches not just missing builtins but any disagreement.
  • parity_backlog_report prints the current gap, so the backlog is
    measurable rather than remembered.

Closed

  • Sets (spec 7A.11), on both backends: set_of/add/has/discard/
    len/has_all/union/intersect/diff/to_array, plus for x in set and
    x in set.
  • ~100 further builtins the spec never mentioned: math, strings, codecs,
    arrays, JSON, HTML, files, zip, process/environment, and assertions.
  • Spec 7A.4 — VM streams: array, file-line, TCP-line, map, filter,
    take, CSV and JSONL, with a lazy for-over-stream lowering.
  • Spec 7A.5 — VM tunnel and udp_*.
  • Spec 7A.6 — import pkg.sub on the VM.
  • GUI — the VM had no GUI natives at all.

Sets are ordered, and the spec said they would not be

The spec proposed backing sets with Map, on the premise that Map already
iterates in insertion order. It does not: both backends store maps in
std::HashMap, whose order is arbitrary and differs between runs. A set built
that way would enumerate differently every time, which defeats the point for
deduplication and diffing.

So a set is an order-preserving Vec alongside a HashSet of element keys.
Insertion order for iteration, O(1) average membership, deterministic output.
1, 0x1 and 1.0 are one element; 1 and "1" are two.

Two bugs the harness found

  • The backends derived different tunnel keys from the same passphrase. The
    salt, iteration count and key length were inlined separately in the
    interpreter and the compiler, and the two had drifted. Both looked correct.
    They now come from one definition, and a test runs one tunnel through both
    backends and compares the key.
  • udp_recv reported a read timeout as an error on Windows and nil on
    Linux
    , for identical code. A socket read timeout is EAGAIN on Unix but
    WSAETIMEDOUT on Windows, and std surfaces those as different
    ErrorKind variants. The implementation matched only the first. Both
    variants are handled now, with a regression test.

GUI

The GUI was, in v8.0.0, a HashMap<i64, ()> that discarded the Window and
WebView it created. gui_update, gui_title and gui_close did nothing.
JavaScript could not call into Rak. Closing a window ended the process. Linux
did not work at all.

Now: one event loop on the main thread (tao binds to the display connection
there and rejects any other thread, which is why Linux failed), real handles
kept alive, a command channel from the interpreter's worker thread to the loop,
JS→Rak IPC through rak_call with results returned via rak_result, and
gui_quit(code) for the exit status. Closing a window no longer ends the
process; the loop stops when the program is finished with the GUI.

fn on_click(n) { return n + 1 }
gui_callback("clicked", on_click)
let w = gui_open("Demo", html, 600, 400)
gui_wait()

One real limitation: a callback gets a copy of the environment as it stood
at gui_callback, so it cannot write to a variable the main script later reads.
That follows from Rak having no reference types.

Inline assembly

asm reaches the CPU through a short list of read-only queries: CPUID feature
bits, rdtsc, rdtscp, the invariant-TSC frequency. Every one is a stable
core::arch intrinsic, so there is no hand-written machine code in Rak.

Three independent gates, all required: the asm capability (its own, not
folded into ffi or raw_sockets, because a capability granted alongside raw
would be granted by habit), an unsafe block with a written justification, and
a new inline-asm lint rule.

The operand is restricted to alphanumerics, so it cannot encode an arbitrary
byte string, and an unknown instruction is an error naming what is available.

Tests and CI

421 unit and integration tests, up from 399, plus 20 parity tests and an example
suite that runs every example on both backends.

CI now exists. Through v8.0.0 the release workflow built and published but
never ran a test — which is how the nineteen missing builtins shipped. ci.yml
runs on every push and pull request, on Linux and Windows, because the UDP
timeout bug existed precisely because the two platforms disagree and a
Linux-only test cannot see it. A separate job runs the hardening verifier's
self-test, so a bug in the verifier cannot silently make every release report
"all hardening checks passed".

Known limitations

The full list is in docs/V8-KNOWN-ISSUES.md. The two that matter most:

A function body without return evaluates to nil.

fn dbl(x) { x * 2 }
dump dbl(3)          // [DUMP] nil

This is the most likely thing to bite you, because it looks like it works. It
reproduces identically on both backends and on the v8.0.0 tag, so it is not a
regression — and it is not fixed here, because changing it is a semantics
change rather than a bug fix, and it belongs with the v9 work. Use return in
every function body.

34 builtins exist only on the interpreter, and all of them are blocked on
one thing. A VM native has signature fn(&[Value]): no &mut Vm, no frame. So
a native cannot call a Rak function, suspend, or resume. That rules out
channel, select, timeout, await_all, task_group, the socket family,
spawn, and FFI trampolines. The fix is coroutines in the VM, which is a
project rather than a list of registrations. The parity gate fails on these 34
deliberately, so the gap cannot be forgotten.

Instrumented CFI is not available. The binaries are CFG-compatible with a
guarded dispatch table and CET shadow stacks, but call sites are not
instrumented, and the hardening verifier says so on every build. The spike is in
docs/CFI-SPIKE.md: it needs nightly, full LTO, a single codegen unit, a
rebuilt std, and a CFI-clean dependency graph — and it is not supported for
the Windows target at all.

Not planned: enclaves (use a container or a VM), and a prover (contracts are
checked at runtime, with an honest inconclusive result when the budget runs out
before the program does).

Upgrading

No breaking changes for rakc run programs. Two things to know:

  • import pkg.sub now works on the VM as well, so a program that only ran under
    rakc run will run under rakc vm.
  • asm is new, and it is behind a capability, an unsafe block and a lint
    rule. It is interpreter-only; a bytecode VM has no instructions to escape
    into.

Documentation

  • docs/V8-BACKEND-PARITY.md — the gate, what is closed, and the 34 with
    reasons.
  • docs/V8-KNOWN-ISSUES.md — behaviour you would not expect, led by the
    implicit-return issue.
  • docs/CFI-SPIKE.md — why instrumented CFI is not shippable here.
  • docs/V8-ROADMAP.md — what v8.1.0 closed, and ownership and borrowing on
    v9.0.0.

Rak v8.0.0

Choose a tag to compare

@github-actions github-actions released this 01 Oct 11:39

Rak v8.0.0

What's new in 8.0.0

Security primitives

  • unsafe "reason" { ... } blocks. Anything touching raw memory, FFI, or a
    wire format can be wrapped in a block carrying a written justification. The
    reason is enforced by the parser, so an unjustified exemption is a parse
    error rather than a warning, and grep unsafe returns every exemption with the
    author's own words attached. rakc lint --audit prints the same list as a
    review artifact, and the interpreter records which blocks were actually
    entered during a run. This is a review boundary, not a permission: the block
    executes exactly as written.
  • requires / ensures contracts on fn. Preconditions are checked with
    the parameters bound and before a single statement of the body, so a violated
    precondition blames the caller and a function that would corrupt state before
    validating never gets the chance. Postconditions run after deferred cleanup,
    with result and the parameters in scope. Contracts survive into async fn,
    on both the sequential and the concurrent drive path.
  • rakc verify. Runs a script under finite step, loop-iteration and
    recursion-depth budgets. It reports three outcomes and they are deliberately
    distinct, because conflating them would be misleading: PASS (completed, all
    contracts held), FAIL (a contract broke, an assert tripped, or a runtime
    error), and SKIP (a budget was hit, so nothing was proved). Exit code 2 means
    inconclusive, never "looks fine".
  • Constant-time comparison. ct_eq, ct_eq_hex, and ct_select. Rak's ==
    is a data-dependent branch that leaks the length of a shared prefix through its
    exit timing, which is enough to recover a MAC or a session token one byte at a
    time.
  • Zeroization. zeroize(bytes) overwrites a buffer through a volatile path
    the optimizer cannot elide. secret_delete and the new secret_delete_all
    wipe values from memory and overwrite the store file with zeroes before
    rewriting it. The serialized buffer in the secrets store is wiped too, and
    file permissions are now set before the data lands rather than after.

Crypto

  • RSA — rsa_keypair, rsa_sign, rsa_verify, rsa_encrypt, rsa_decrypt
    (PKCS#1 v1.5 over SHA-256, OAEP with SHA-256). Keys are DER.
  • ECDSA over NIST P-256 — ecdsa_keypair, ecdsa_sign, ecdsa_verify, with
    64-byte r||s signatures. DER keys.

The suite previously had only Ed25519 and X25519, both non-standard curves, so
nothing interoperated with ordinary OpenSSL tooling. Everything remains pure Rust
with no OpenSSL linkage.

Static analysis

  • Six new security lint rules: hardcoded-secret, plaintext-url,
    weak-crypto, secret-compare, ffi-raw-pointer, insecure-transport, plus
    unsafe-thin-reason.
  • This required fixing a real bug: the linter had no Expr::String arm, so
    string literals fell through to _ => {} and no rule could ever inspect a
    byte of user text. A hardcoded sk-live-... was completely invisible. There is
    a regression test for exactly that.
  • rakc fmt and rakc lint are now listed in rakc --help.

Packet crafting

  • ICMP — net_raw_icmp, net_raw_icmp_ping, net_raw_icmp_echo_reply, with
    matching id/seq so a reply can be correlated to its request.
  • ARP — net_raw_arp_request, net_raw_arp_reply, and net_raw_arp_parse,
    which accepts a frame with or without the Ethernet header and returns nil on
    anything that is not ARP, so raw capture can be fed straight in.

caps.rs previously gated a net_raw_icmp builtin that did not exist; the gate
now matches real builtins, and packet builders are documented as reachable
inside a sandbox because they only construct a bytes value.

Fuzzing on the stable toolchain

  • rakc fuzz <target> — a deterministic mutation loop in the compiler
    itself, covering the lexer, parser, interpreter and the stdlib parsers. 12
    targets, reproducible via --seed, with a crashing input written to disk and
    the seed printed for replay.
  • The 13 cargo-fuzz targets in fuzz/ are still there for long
    coverage-guided campaigns, but they need nightly and -fsanitize=fuzzer,
    which stable-x86_64-pc-windows-msvc cannot provide. Nothing was running them
    automatically before this.

Build hardening

  • Release profile gains lto = "fat", codegen-units = 1, panic = "abort"
    and strip = "symbols". The one that matters most is overflow-checks = true:
    Cargo's default in release is false, which silently wraps and turns a length
    calculation into a heap overflow.
  • .cargo/config.toml adds /guard:cf, /CETCOMPAT, /DYNAMICBASE,
    /HIGHENTROPYVA, /NXCOMPAT on MSVC, and full RELRO, a non-executable stack,
    a stack canary and forced frame pointers on Linux.
  • Stated precisely, because these are easy to oversell. /guard:cf marks the
    image CFG-compatible and guards the dispatch table, but rustc does not
    instrument Rust's own indirect calls, so there is no __guard_check_icall in
    the binary. This is not equivalent to Clang's -fsanitize=cfi. It closes a
    real class of bugs; it does not close ROP or JOP.
  • dist/verify_hardening.py reads the produced binary back and asserts the bits
    are really set, and both release jobs run it. It has a --self-test covering
    both the PE and ELF parsers on synthetic headers, positive and negative. This
    already caught a real mistake: an early flag set passed both /NXCOMPAT and
    /NXCOMPAT:NO, silently disabling DEP.

Validating the hardening against real binaries

Both platforms were checked before tagging, by building the actual rakc and
reading the result back rather than trusting the flag list.

  • Linux: rakc (7.8 MB) built in WSL with the repo's exact
    .cargo/config.toml. PIE, full RELRO, non-executable stack and BIND_NOW
    all land correctly. The stack canary does not, and cannot be made to:
    rustc's x86_64-unknown-linux-gnu target does not enable -fstack-protector,
    and -C target-feature=+stack-protector is rejected outright with "not a
    recognized feature for this target". Getting a canary into Rust code needs
    nightly -Z stack-protector, or a C object built with GCC's
    -fstack-protector and linked in.
  • So the verifier reports the canary as an advisory finding rather than a
    required one. A check the stable toolchain cannot satisfy is either a
    permanently red build or something everyone learns to ignore, and neither is
    useful. The four checks that are satisfiable remain hard failures.
  • The same exercise turned up that rustflags is silently ignored when placed in
    Cargo.toml ("unused manifest key"). It only takes effect in
    .cargo/config.toml, which is where it lives. A virtual manifest also rejects
    a [target] section outright.

Deep recursion no longer kills the process

The tree walker burned several native frames per Rak call, and a Rak call costs
several KB of native stack in a debug build. fib(15) needed roughly a megabyte,
which is more than the default thread stack allows, so a moderately recursive
program died with a bare "has overflowed its stack" and no Rak-level line number.
The interpreter now runs on a thread with an explicit 64 MB stack. rakc verify
additionally bounds recursion depth, loop iterations and total steps, so runaway
recursion is a reportable outcome rather than a crash.

Tooling

  • The VS Code TextMate grammar's builtin list was a single 2,900-character line,
    close to TextMate's practical limits and unmaintainable. It is now 17 grouped
    per-family patterns, the longest 371 characters.
  • The builtin lists in the LSP, the IDE editor, and the grammar were all stale
    and disagreed with each other. All three are updated, and the LSP list had been
    missing the entire batteries, OSINT, iterator, FFI, mmap and net_raw families.
  • New docs/content/safety.md, covering what is enforced and — just as
    importantly — what is not.

Correctness

  • docs/content/vm.md claimed binary pattern matching, method-call dispatch and
    user enum patterns were unsupported on the VM. All three shipped; the table was
    wrong.
  • Pattern::Bind does not exist: the @ binding-pattern syntax the spec
    advertised does not parse. Marked [SPEC] rather than [SHIPPED], with the
    implementation steps recorded. Spec markers in both directions were wrong and
    are now corrected against the code.
  • expr_str fell back to the AST debug form for calls, indexing, field access
    and ranges. Contract clauses and assertion failures are mostly calls, so the
    most important diagnostics were the least readable. Now rendered as source.

Housekeeping

  • All version strings moved to 8.0.0, including the REPL banner (still said
    v0.3.0) and rakpkg (still said 0.7.0).
  • raklib/ was an empty untracked directory. Removed.
  • adblocker/ added to .gitignore: it is a nested git repository kept
    deliberately local, and without this git add -A fails outright.
  • scripts/run-tests-safe.ps1 runs the test binary under a memory and time
    watchdog. A stack overflow becomes a Windows Error Reporting event, and with
    the system default of automatic memory dumps that writes a multi-gigabyte file.

Known limitations

Stated plainly rather than discovered later.

  • The GUI is incomplete and Windows-only. gui_update, gui_title and
    gui_close are declared but do nothing: gui.rs stores HashMap<i64, ()> and
    discards the Window and WebView handles. JavaScript cannot call into Rak —
    the IPC handler receives the message and drops it. Closing a window ends the
    process, because tao's run calls process::exit. Linux does not work at all,
    because gui_open builds the event loop off the main thread, which tao
    rejects. The VM has no GUI natives, so rakc vm reports
    Undefined: gui_open. The README and docs now say this instead of implying
    otherwise.
  • **No ownership or...
Read more

Rak v0.7.2

Choose a tag to compare

@github-actions github-actions released this 27 Sep 10:00

Rak v0.7.2

What's new in 0.7.2

Debugger (DAP server)

  • New rakc dap — a Debug Adapter Protocol server so any DAP-capable editor/IDE can debug .rak scripts: set/remove breakpoints, step, step-over, finish, continue, inspect stack frames, scopes, and variables, and evaluate expressions live.
  • The VS Code extension ships a debug configuration (adapter type rak, launching rakc dap) — open a script, set a breakpoint, and press F5. Includes a matching examples/dap_demo.rak.
  • Fixed the DAP line-number mapping so client-side (1-based) breakpoints hit the correct VM (0-based) line.

stdlib batteries

  • New built-in modules covering common tasks:
    • Time — time_now, time_parse, time_format, time_utc, time_elapsed, sleep helpers.
    • Random — rand_int, rand_float, rand_uuid, rand_seed, rand_bytes.
    • Data formats — csv_* read/write (RFC-4180), yaml_parse, JSON round-tripping helpers (new stdlib/src/datafmt.rs).
    • Archives — gzip/gunzip, zip_archive/zip_list/zip_extract (new stdlib/src/archive.rs).
  • New gallery examples: batteries_demo.rak.

OSINT pack

  • Practical open-source-intelligence toolkit (new rakc/src/ext_osint.rs, stdlib/src/{whois,ctlogs,yara,report}.rs):
    • WHOIS — whois_lookup + whois_parse for domain records.
    • CT logs — ct_subdomains certificate-transparency subdomain enumeration.
    • YARA-lite — yara_scan over raw bytes (hex/string patterns, at/and/all of them/none of them).
    • Reports — report_markdown structured Markdown evidence output.
  • New gallery examples: osint_demo.rak, osint_pack_demo.rak, plus docs page osint.md.

Language core

  • in operator — x in collection membership tests for arrays, maps, strings, and streams.
  • Destructuring let — bind multiple variables from an array/map in one statement.
  • Slice & negative indexing — a[1..3], a[-1], array/byte/string slicing with safe bounds.
  • New docs page batteries.md; README "What's new in 0.7.2".

IDE upgrades

  • Autocomplete + syntax highlighting for every new batteries & OSINT builtin.
  • Three new example scripts in the gallery (batteries, osint, core_v072), each verified to run byte-identically on both the interpreter and the bytecode VM.
  • IDE + VS Code extension bumped to 0.7.2.

Installers & bundles

  • Linux: portable rak-ide tar.gz, offline rak-bundle tar.gz, .deb, and .AppImage.
  • Windows: portable rak-ide zip, offline rak-bundle zip, NSIS -setup.exe, and WiX .msi.
  • Standalone rakc, rakpkg, and rak-setup binaries for both platforms.

Bug fixes

  • DAP: 1-based ↔ 0-based line translation on setBreakpoints / stackTrace.
  • examples/osint_pack_demo.rak: fixture keys now match the normalized WHOIS map; YARA demo uses a verified rule set; live network lookups commented so the demo stays hermetic.
  • Removed the redundant core_v08.rak example (duplicate of core_demo.rak).

Rak v0.7.1

Choose a tag to compare

@github-actions github-actions released this 18 Sep 20:05

Rak v0.7.1

This release pulls the language together. Rak now type-checks before it runs, mutability actually means something, and a few long-missing primitives (char, base literals) are first-class. Same installer, IDE, vscode-rak extension, and docs as 0.7.0, refreshed to match.

What changed

Static type checking

rakc check <file> now runs a real type-checking pass, not just lex+parse. It infers literal types, checks let x: T = value annotations and function-call arguments, and reports expected-vs-found types with the source line (error[E0308]). Numeric literals are mutually compatible; char and string are not, so let x: char = "hi" is caught before anything runs.

Mutability is now enforced

let x = 10; x = 20 is an error (cannot assign to immutable variable 'x'). Use let mut x to rebind. Enforced on both the interpreter and the bytecode VM, so the two backends agree.

char type

A first-class Unicode scalar, distinct from u8 and bytes. let c: char = 'א', plus \u{...} and \xNN escapes. Works across the interpreter, VM, and type checker.

Base literals

0b1010, 0o755, and underscore separators (1_000_000). All integer forms compare equal by value, so 0xA == 10 is true on both backends.

Generic functions

fn identity<T>(v: T) -> T, callable as identity<int>(42) or inferred identity(42). Type arguments are validated and then ignored; the runtime stays dynamic.

Enum variant patterns

match e { Event::Connect(host) => ... } with tuple and unit variants, on newline- or comma-separated arms. The checker flags non-exhaustive matches and repeated variant patterns.

defer

defer cleanup() runs in LIFO order when the function exits, on a normal return or a raised error. New Op::DeferCall makes the bytecode VM behave identically to the interpreter.

Test framework

rakc test [file] [--filter NAME] [--verbose] runs test "name" { ... } blocks. assert, assert_eq/ne/true/false, expect_error(fn() {...}), and panic(msg) all work. A failing test exits non-zero.

Synced tooling

The vscode-rak TextMate grammar, the IDE tokenizer/autocomplete, and the docs-site syntax highlighter all recognize the new keywords (defer, test, assert), the char type, \u{...} escapes, and 0b/0o literals. Full docs updated to match.

Full changelog

rakc check        static type checker
char type         Unicode scalar with \u{...} / \xNN escapes
let mut           immutable bindings reject reassignment
0b / 0o           base literals + underscores; 0xA == 10
fn f<T>()         generic functions (explicit or inferred)
Event::Variant()  enum variant patterns; exhaustive-match warnings
defer expr()      LIFO cleanup in interpreter and VM (Op::DeferCall)
rakc test         test blocks + assert / assert_eq / expect_error / panic

Install

See README for curl | bash (Linux) and iwr | iex (Windows), or grab rak-setup from this release. The Tauri installers (NSIS/MSI/.deb/AppImage) are also attached.

Rak v0.7.0

Choose a tag to compare

@github-actions github-actions released this 18 Sep 15:53

Rak v0.7.0

Upgraded compiler, new key features, IDE + installer fixes.

What's new in 0.7.0

Compiler & language upgrades

  • Structured errors — catch e binds a first-class Error value with a kind, source span (file:line:col), cause, context map, and backtrace. New builtins: error, err_message, err_kind, err_line, err_col, err_file, err_cause, err_context, err_with_context (interpreter + VM).
  • True async concurrency — await_all, select (race), timeout, task_group, async_sleep, async_yield, and deferred async fn bodies running on a bounded worker pool (thousands of lightweight ops). Added the shared Tokio runtime (async_rt.rs) with a non-Tokio counting semaphore.
  • Streaming — pull-based Value::Stream with stream_from_array, stream_map, filter, take, stream_next, collect, read_lines, and tcp_stream; lazily consumed by for.
  • CLI — optional fn main(argv) -> int entry with real process exit codes, plus argv(), stdin_read_line, stdin_read_all, eprint, and the parse_args(spec, argv) flag parser.
  • Data processing — lazy stream_csv (RFC-4180) / stream_jsonl parsers and parse_csv_line, plus gzip/gunzip/deflate/inflate and zip_archive/zip_list/zip_extract (new stdlib/src/stream_io.rs).
  • VM line mapping — the compiler emits a source line-marker per top-level statement into Chunk.lines for source-to-bytecode mapping.

rakc debug

  • New bytecode-VM source debugger: break, continue/c, step/s, next/n, finish, locals, stack/backtrace, frame, print <name>, and full disassemble with line markers + operands.

rakpkg

  • Now a lib + bin so parse_manifest_str is reusable/fuzzable.
  • Version/rev constraints (user/repo@^1.2, #rev), rakpkg.lock (resolved rev + manifest SHA-256 checksum), and new commands: update, lock, tree (cycle-safe dependency graph), audit, publish.

Fuzzing

  • 8 proptest harnesses (stable CI) for the lexer, parser, DNS, packet builders, WebSocket, TLS, JSON, and tunnel framing (rakc/tests/proptest_harness.rs).
  • A standalone libFuzzer / cargo-fuzz pack (fuzz/, 13 targets: lexer, parser, eval, manifest, dns, tls, json, websocket, tunnel, netraw, csv, gzip, zip).

IDE + VS Code

  • Fixed bugs in the IDE.
  • Added the full v0.7.0 syntax vocabulary to the IDE editor (CodeEditor) and the VS Code extension (vscode-rak grammar, version 0.5.1 → 0.7.0): structured errors, async orchestration, streaming, CLI, and compression builtins, plus 9 new editor snippets (fn main, await_all, task_group, timeout, stream, read_lines, stream_csv, parse_args, structured errors).

Documentation

  • Restructured the docs: content is now split into markdown page files under docs/content/*.md (based on README.md and rak-features-spec.md), and docs.html is now a lightweight markdown-driven viewer (dependency-free renderer + hash router) served by GitHub Pages.

Bug fixes

Rak installer (rak-setup)

  • Fix PATH clobbering on Windows — the installer no longer overwrites a user's entire PATH. Previously a broken reg query parse could capture the registry type token and setx /M could write the system PATH even for user installs, breaking unrelated CLIs (e.g. rustc/cargo). Now PATH edits are append-only on the correct registry hive (HKCU for user, HKLM for system), setx is removed, and a WM_SETTINGCHANGE broadcast refreshes running programs.
  • Uninstall now removes only the rak PATH entry (instead of deleting the whole PATH value) and only recurses into rak-owned directories.
  • Component selection is now an explicit multi-select on the first screen (space to toggle, enter to confirm), with rakc + rakpkg preselected.
  • --install validates component names and rejects unknown ones.

Rak v0.6.1

Choose a tag to compare

@github-actions github-actions released this 18 Sep 03:01

Full Changelog: v0.6.0...v0.6.1

Rak v0.6.0

Choose a tag to compare

@github-actions github-actions released this 17 Sep 00:53

Full Changelog: v0.5.1...v0.6.0

Rak v0.5.1

Choose a tag to compare

@github-actions github-actions released this 10 Sep 10:44

Full Changelog: v0.5.0...v0.5.1