What Burxt does not do
Every language has gaps. Most projects let you find them one at a time, in your own code, at the worst moment. This page is the other approach: everything deliberately absent, and everything not built yet, in one place, with the reason.
Two kinds of entry, and the difference matters more than the list:
- Decisions — these are not coming. Each was chosen, each has a reason, and changing one would change what the language is.
- Not yet — real gaps with real plans. If one blocks you, it is a legitimate thing to ask about.
If something is missing from this page rather than from the language, that is a bug and worth reporting. A gap document that is wrong about a gap is worse than none, because it will be believed.
Decisions — not coming
No floating point
There is no Float, and there will not be one. Money is Decimal<scale>, an exact scaled integer.
This is the one that surprises people, so here is the strongest evidence for it: the flagship case
nobody thought was reachable without floats — vector similarity search — turned out reachable
and better. Exact Decimal<7> cosine similarity carries a claim no float-based store can make:
scores are byte-identical on every machine, every target and every run. That is not a workaround
for the missing feature. It is the feature.
If you need transcendental maths on physical quantities, this is the wrong language and that is fine.
No null, no truthiness, no unwrap
Absence is Option<T>, failure is Result<T, E>, and conditions must be Bool. There is no
unwrap because a name that means “crash here, later, in production” should be as inconvenient as
what it does.
No GC, no reference counting, no runtime
Memory is regions: every block is one, and a block releases what it allocated when nothing outside can still reach it. No pauses, no finalizers, no background thread. Release is one pointer assignment.
The cost is stated in the memory guide: region granularity is coarser than Rust’s borrow checker. A value the analysis cannot prove safe to release simply is not released — the failure direction is memory, never a dangling pointer.
No inheritance
Composition and interfaces only. Dropped deliberately; dynamic Trait covers what polymorphism is
actually used for.
No closures
This one is recent and worth explaining, because “no closures” usually means “not yet.”
dynamic Trait is a function value — passable as a parameter, callable, and it captures state in
the implementor’s fields. interface Predicate<T> plus a class that implements it does what a
closure does, with the captured state written out where a reviewer can see it. map, filter,
fold and sort_by are all written this way in lib/fn.bx.
Closures were buildable when this was decided — the memory question that had blocked them was answered — and declined anyway, because a second way to express one thing costs every reader the question of which one they are looking at.
No unsafe, no reflection, no conditional compilation
No escape hatch, no runtime type inspection, no #ifdef. A program means one thing on every platform.
No catch or recover
A failed contract, an overflow, an out-of-range index: the program prints a named error and exits 70.
There is no handler and no unwinding. Every failure is named — this is the one guarantee the
language treats as non-negotiable, and it is why sdiv is not allowed to fault silently.
Smaller shape decisions
Bitwise operations are seven named builtins, not operators · String ordering is byte order, never
locale collation · no operator overloading · no C-style ternary · % is the percent literal, so
modulo is remainder(a, b) · no format-spec mini-language inside interpolation · no implicit prelude
or glob imports · Map iteration order is defined and never “unspecified” · no wildcard _ match arm,
because a new enum variant should break the code that ignored it · contracts are never stripped,
in any build mode.
Rust is not going away
Burxt compiles Burxt to a byte-identical fixpoint, so self-hosting is proven rather than aspirational. The Rust compiler stays anyway, as the trust anchor and the differential: two independent implementations that must agree. That disagreement is the single most productive bug-finding tool this project has — it has caught defects no test suite saw, including memory corruption that every green suite missed.
Not yet — real gaps
No package registry, and no transitive resolution
Most of this section used to say “no dependency management” and was wrong on three counts. There
is a manifest — burxt.package. There is a lockfile — burxt fetch writes burxt.lock, pinning
exact commits. And there is a visibility marker: public is a keyword in both compilers, the
boundary is the package rather than the file, and reaching a declaration that is not public
is a named refusal. Two packages depend on Burxt this way today.
What is actually missing:
- No registry. A dependency is a git URL and a commit, written out. That is an operational commitment rather than a language feature, and it is not scheduled.
- Resolution is FLAT. Only the root manifest’s dependencies are read, so a package cannot bring
its own: an application depending on star-burxt must also declare
bmxitself, under the same name star uses. One level from breaking quietly, and the fix is scheduled rather than done. - No version constraint. A manifest pins a commit; it cannot say
requires burxt >= 1.3. The first thing that needs it is a package’s own CI, not a user.
Printing a String stops at a zero byte
A Burxt String is not NUL-terminated: len is one load from the eight bytes before the first
byte, so a String genuinely holds a zero and +, substring and byte_at all preserve it. But
every print path formats a String with %s, which stops at the first zero — so len says three and
print writes one.
let s: String = from_bytes([65, 0, 66]);
print(len(s)); // 3
print_exact(s); // writes `A`
print, print_error, print_exact and interpolation all do it, because they share one formatter
— which is why the two compilers agree about it exactly. The trap is that the length is the honest
half: a program that declares a byte count from len and then prints the text writes fewer bytes
than it promised, which for a framed protocol means a reader waiting for bytes that never arrive.
Why it is not fixed. The exact write is fwrite(ptr, 1, len, stdout), and stdout is a libc
data symbol that Darwin spells __stdoutp. Stage-0 already carries one such rename as its
documented exception to byte-identical IR, and stage-1 avoids the whole class by emitting
dprintf(2, ...) rather than ever naming a stderr global — so the fix costs a second renamed
symbol in one compiler and a mechanism that does not exist in the other.
What made that trade acceptable was closing the path that actually reached it. lib/json.bx
escaped seven characters and passed the other twenty-five control bytes through raw, which RFC 8259
§7 forbids — so json_render emitted invalid JSON, and for a zero byte it emitted a string that
never closed. That was the reachable damage, it was in the library rather than in a libc symbol, and
it is fixed: every byte below 0x20 is now \u00XX, checked against Python’s json for all
thirty-two. What remains is text built from arbitrary bytes and printed directly — rendering a
document into a page, rather than serialising it.
The byte paths are faithful, and it is measured. write_bytes wrote 64 bytes holding 16 zeros
and read_file returned all 64 exactly; a .vsix with 685 zero bytes round-tripped through
file_write byte-for-byte, and an 83,629-byte wasm module with 11,214 zero bytes was served and
fetched back through net_write/http_serve with a matching digest. Those take an explicit length,
where a print is null-terminated by the format string it passes through. So: serve and store
binary freely; do not print it.
Reported by the BMX session against published 1.6.0 and measured with the star-burxt session, 2026-08-21.
No threads. Processes, yes
No threads, no async, no channels. os_fork and os_wait_for_child in lib/os.bx are what a Burxt
program has for doing more than one thing at once, and a pre-forked server is written with them
today.
Threads need pthread_create, which takes a function pointer — C calling back into Burxt, which
is the door below that is genuinely still shut.
The sequencing is deliberate rather than disinterested. The intended claim is that two threads cannot corrupt a balance, derived from a declared invariant rather than from a lock somebody remembered, and shipping that half-done would be worse than not shipping it. Separate processes get the same guarantee the coarse way: two workers share no memory, so there is nothing to corrupt.
TCP, HTTP and TLS, yes. No DNS
lib/net.bx opens, binds, accepts, reads and writes TCP sockets, both as a server and as a client.
A Burxt program answers HTTP requests. What is missing above it:
-
No TLS library—lib/tls.bxships it, and writing one was never the plan: the primitives are BOUND, because this language gives no control over instruction timing and a hand-rolled handshake that looks fine is the silent wrong answer it exists to refuse.It verifies, and all three parts are needed together. The system CA store,
SSL_VERIFY_PEER, and the hostname checked against the certificate. Without the second, OpenSSL never builds the chain andSSL_get_verify_resultanswers OK vacuously — so a program can set a hostname, read 0, and have verified nothing. Measured: no CA store gives verify code 20, a hostname outside the certificate gives 62, andhttps_getrefuses both by name. Link with-lssl -lcrypto; CI does not run it, anda_burxt_program_fetches_over_verified_https -- --ignoredis where it lives and why.Still absent inside it: no client certificates, no session resumption, no ALPN — so no HTTP/2.
No HTTP—lib/http.bxships it, both halves, over the sockets that already existed. A request is parsed into anHttpRequest(method, path, decoded query, headers, body), aHandlerinterface is what a server takes since Burxt has no function values, and a client answers anHttpResponse. What is still absent inside it, named rather than discovered: no chunked transfer encoding (a body is read byContent-Lengthor REFUSED, never truncated), no keep-alive (one request per connection, andConnection: closeis sent so a client knows), and no DNS, so a client takes four octets.-
No DNS, and this one is a single missing builtin rather than a design problem. An address is four octets today —
net_connect_ipv4(93, 184, 216, 34, 80).Measured 2026-08-18:
getaddrinfoitself already succeeds from a Burxt program, returning0for a real hostname, andc_bytes_atreads back the eight bytes it wrote — a genuine heap address. What cannot be done is turn those eight bytes into aCPointeragain, so theaddrinfochain cannot be walked.c_string_at,c_bytes_atandc_bytes_toall cross the wall; the mirror that reads a POINTER field out of a C struct does not exist yet. - Blocking, with no timeouts.
net_acceptwaits.
This section said “It cannot open a network connection” until it was measured. It could: every
call but bind worked with no compiler change, and bind needed one builtin.
C interoperation is limited on purpose
You can call C functions that take and return scalars, strings and opaque pointers. C cannot call
back into Burxt, which is what stops sqlite3_exec and most iteration APIs. The prepare/step/column
path works; the callback path does not.
Cryptography: some built, most deliberately not
Hashing, HMAC, PBKDF2, hex and base64 are built — lib/hash.bx, lib/encoding.bx and
lib/secure.bx — because they have published test vectors and no secret-dependent branching, so
“it compiles” and “it is correct” are the same statement, checkable against the values the standards
publish. SHA-256, SHA-512, HMAC, PBKDF2, CRC-32, FNV-1a, hex, base64 and base64url, every RFC 4648
vector pinned in a fixture. Entropy comes from getentropy, with secure_uuid_v4 and
secure_token_hex over it, and string_equals_constant_time for comparing any of it.
(This paragraph said “being built” for two versions after they landed. A gap document that is wrong about a gap is worse than none — including when it is wrong in the direction of modesty, because the reader who needed a digest went and shelled out for one.)
AES, ChaCha20, RSA, Ed25519, X25519, TLS, Argon2 and bcrypt are deliberately absent and will be
bound rather than written. Two reasons: Burxt gives no control over instruction timing or cache
behaviour, and RSA and the elliptic curves need arbitrary-precision integers, which the language does
not have — Decimal is a scaled i64 capped at scale 18. Hand-rolling them would produce ciphertext
that looks perfectly fine, which is exactly the silent wrong answer this language exists to refuse.
A percent is a number, and the % is the only thing that says so
This is the one place on the money surface where Burxt accepts a silent wrong answer, and it is here rather than buried because the direction of the error is charging somebody too much.
decimal2_percent_of(80.00, 12.5%) // 10.00 — what was meant
decimal2_percent_of(80.00, 12.5) // 1000.00 — accepted, a hundred times too much
A percent literal is a Decimal<4>: 12.5% holds 0.125. So the rate parameter is a fraction,
a bare number is a valid fraction, and widening a scale is safe — nothing refuses it.
No contract can catch it. By the time the function sees its argument the two spellings are the
same value, so requires rate < 1 would refuse the mistake and also refuse 150%, which is a
legitimate markup. The information is destroyed at the literal, and only a distinct TYPE could keep
it — which is a change to the flagship claim rather than a patch, and it is open rather than
decided.
Until it is decided: write the literal. 12.5%, never 0.125 and never 12.5. Held by
tests/pass/a_percent_literal_is_not_a_bare_number, so the day this changes the output changes with
it. Found by star-burxt writing a real money example, where the wrong spelling was the first one
they reached for.
A secret cannot be zeroed
There is no zeroise. A String lives until its region closes, so a key stays resident after the code
using it has returned. Fixing this properly needs a primitive that writes over region memory. Saying
so is the minimum.
Unicode is ASCII-correct, and the names say so
string_to_upper_ascii is spelled that way because it is what it does: non-ASCII passes through
unchanged. Full Unicode case mapping needs ~1,400 generated entries plus locale edges — Turkish ı,
and ß becoming SS, one codepoint becoming two.
Codepoint iteration, char_count, from_codepoint and is_valid_utf8 are all present. A String is
UTF-8 and that is checked at every entry point — read_file, argument and c_string_at, which is
also how os_env is covered — so invalid bytes are a named error naming the door they came through,
rather than a corrupted value discovered later somewhere that can only report the symptom. Overlong
forms, surrogates, anything above U+10FFFF and a sequence the buffer cut short are all refused.
This sentence was here before the check was, which is worth admitting rather than quietly correcting: the guarantee was published and unenforced until v0.0.284, and a published guarantee is exactly the kind a reader stops verifying.
The cost, stated plainly: you cannot read binary through read_file any more. file_read_bytes
answers Option<[Int]> and is the door for data that is not text.
Nothing runs on a phone or in a browser yet
Objects compile for eight targets including wasm32, aarch64-apple-darwin and
x86_64-pc-windows-msvc, and the IR is byte-identical across all of them. What is missing is
linking: a sysroot per platform, Android’s NDK and JNI shell, iOS signing, and a wasm host. That is
packaging work rather than compiler work.
Other absences
No formatter (burxt fmt) · no regex · no stack trace on failure, only the named error and its
location — though as of v0.0.285 there is no longer any failure WITHOUT one: a runaway recursion
names itself and exits 70 like everything else, where it used to die of a bare SIGSEGV · no default parameter values or named arguments · no if as an expression · no attributes ·
no warnings about your program — every diagnostic the compiler can produce about Burxt code is a
refusal to compile, so there is no way to flag something without stopping the build · parse errors
arrive one at a time, with no token-stream recovery.
The one thing that prints the word warning is the driver, not the compiler: -g without -O0
says so, because a line table over optimised code is honest about instructions and misleading about
statements. That is a remark about how you invoked the tool, not a judgement about your program, and
the distinction is the whole of why the rule above still holds.
How to read this page
The list is long because it is honest, not because the language is thin. Every entry above is either a decision with a reason or a gap with a plan, and the ones we consider most limiting for real work today are dependency management and the network — in that order.
The debugger used to head that list, and it is gone: burxt build -O0 -g emits DWARF, so a debugger
stops on the line you wrote, names the function, prints your locals as values rather than addresses,
and can break on a requires clause and show you the arguments that violated it.
What you get in exchange is narrow and unusual: exact decimal arithmetic with no floating point anywhere, contracts that are always checked, no runtime, no garbage collector, memory that is released when a block ends, and two independent compilers that must agree on every program.
If that trade is wrong for what you are building, this page has done its job.