Builtins
The names a program may not declare, because the language already means something by them. The list comes from is_reserved_name in src/rust-compiler/typeck.rs; every signature below was compiled while this page was generated, so none of them is a signature the compiler would reject.
| Call | Answers | Allocates? | Reaches |
|---|---|---|---|
print_error |
nothing | no | — |
bit_and |
Int |
no | — |
bit_or |
Int |
no | — |
bit_xor |
Int |
no | — |
bit_not |
Int |
no | — |
shift_left |
Int |
no | — |
shift_right_zeros |
Int |
no | — |
shift_right_sign |
Int |
no | — |
c_is_null |
Bool |
no | — |
c_string_at |
String |
yes | — |
c_bytes_at |
[Int] |
yes | — |
c_bytes_to |
Int — how many it wrote |
no | — |
print |
nothing | no | — |
len |
how many elements, or how many BYTES of a String |
no | — |
byte_at |
the byte at i |
no | — |
byte_as_string |
a one-byte String holding n |
yes | — |
substring |
count bytes of s, starting at from |
yes | — |
to_string |
the value, written out | yes | — |
push |
the new length | yes | — |
truncate |
the new length | no | — |
read_file |
the whole file | yes | touches files |
write_file |
how many bytes went out | no | touches files |
write_bytes |
how many bytes went out | no | touches files |
argument |
the nth command-line argument | no | touches input |
argument_count |
how many arguments there are | no | touches input |
divide_floor |
a / b, rounded toward negative infinity |
no | — |
divide_toward_zero |
a / b, rounded toward zero |
no | — |
remainder |
what is left over | no | — |
hash |
a non-negative hash | no | — |
old |
what the expression was BEFORE the body ran | no | — |
result |
the value being returned | no | — |
exit |
nothing — the program ends | no | — |
main |
nothing — it is not an entry point | no | — |
Allocates means the call builds something, and a value has to be built somewhere. It does not mean you write region. Since v0.0.146 a program has one from the moment it starts and allocates is inferred, so every signature on this page compiles with no region in sight — the probes that verified them have none. You reach for region to release EARLY, around a loop body or a request, which is the whole of keeping a long-running program’s memory flat. See Memory.
print_error
print_error(value)
Writes one value and a newline to standard error. The same statement as print with a different destination, so the per-type formatting cannot fork — two statements would mean two formatters, and the first time one learned about a new type the other would print something else.
Answers nothing. Allocates: no.
bit_and
bit_and(a: Int, b: Int)
Bitwise AND. Named rather than an operator, because a & b == c means a & (b == c) in C — a precedence table a reviewer has to remember is the opposite of what this language is for.
Answers Int. Allocates: no.
bit_or
bit_or(a: Int, b: Int)
Bitwise OR.
Answers Int. Allocates: no.
bit_xor
bit_xor(a: Int, b: Int)
Bitwise XOR.
Answers Int. Allocates: no.
bit_not
bit_not(a: Int)
Every bit flipped. bit_not(0) is -1, because an Int is signed and there is nowhere else for the top bit to go.
Answers Int. Allocates: no.
shift_left
shift_left(x: Int, n: Int)
Shifts left by n, which must be 0 to 63. Bits shifted past the top are discarded — the one place in this language where losing information is not an error, because it is what a shift is for. So it is not x * 2^n: multiplication traps on overflow and this does not.
Answers Int. Allocates: no.
shift_right_zeros
shift_right_zeros(x: Int, n: Int)
Shifts right by n, filling with zeros — a logical shift. shift_right_zeros(-1, 63) is 1. Two right shifts exist because on a negative value zero-fill and sign-fill give different answers, and one symbol cannot say which.
Answers Int. Allocates: no.
shift_right_sign
shift_right_sign(x: Int, n: Int)
Shifts right by n, copying the sign bit — an arithmetic shift. shift_right_sign(-1, 63) is -1, and shift_right_sign(x, n) equals divide_floor(x, 2^n).
Answers Int. Allocates: no.
c_is_null
c_is_null(p: CPointer)
Did the C call fail? One of only two things that may be done with a CPointer. There is no == on one, no arithmetic and no printing — a pointer is a token to hand back to C, not a value to reason about.
Answers Bool. Allocates: no.
c_string_at
c_string_at(p: CPointer)
Copies NUL-terminated bytes from C into a Burxt String. The copy is the wall: afterwards Burxt owns the bytes and the pointer is not kept, so who frees it stops being a question. A null pointer dies here rather than answering "" — unset and empty are different facts.
Answers String. Allocates: yes.
c_bytes_at
c_bytes_at(p: CPointer, n: Int)
Copies n bytes from C into a growable array, one byte per element, zero-extended so 0xFF arrives as 255 rather than -1. The counterpart to c_string_at, and it is what makes OS entropy reachable: /dev/urandom is a character device, so read_file measures it and gets nothing. Where the length comes from is the pointer wall’s one soft edge — n is your claim, and nothing in the type can check it. A null pointer and a negative count are refused.
Answers [Int]. Allocates: yes.
c_bytes_to
c_bytes_to(p: CPointer, bytes: [Int])
Writes an array of bytes into memory C owns, the mirror of c_bytes_at. It is what lets a program hand C a struct: bind wants a sockaddr_in, nanosleep wants a timespec, setsockopt wants a flag — all of them small, all of them by pointer, and until this existed Burxt could read C’s memory and never write it. The length is not a claim here, it is len(bytes); what stays yours is whether the destination is big enough, which belongs to C. A null pointer is refused, and a number outside 0..=255 exits 70 rather than truncating — 256 quietly becoming 0 is a corrupt port or a corrupt length prefix.
Answers Int — how many it wrote. Allocates: no.
print
print(value)
Writes one value and a newline. Takes an Int, a Bool, a String or a Decimal<S>, and an interpolated string goes out piece by piece with nothing built — which is why printing costs no memory.
Answers nothing. Allocates: no.
len
len(xs) -> Int
On a String this counts bytes, not characters. That is deliberate and it is the same decision byte_at makes: the byte-versus-character question is one a program has to answer, and a name that hid it would answer it wrongly for somebody.
Answers how many elements, or how many BYTES of a String. Allocates: no.
byte_at
byte_at(s, i) -> Int
Bounds are always checked. The name says BYTE so that nothing has to guess whether an index into text means a byte or a character.
Answers the byte at i. Allocates: no.
byte_as_string
byte_as_string(n) -> String
The exact inverse of byte_at: byte_at(byte_as_string(n), 0) is n for every one of the 256 values. n must be 0 to 255 — a literal outside that is refused when the program is compiled, and anything computed is checked when it runs.
It is the ONLY way to turn a number into text, and the reason it had to be a builtin rather than a library function: substring of a literal was the only Int-to-String path there was, and a source file must be valid UTF-8, so a byte above 127 could only be written down inside a complete multi-byte character. to_string(233) is a different conversion — three digit characters, "233".
It is also the one builtin that can build a String is_valid_utf8 rejects. byte_as_string(0xC3) on its own is a UTF-8 lead byte with no continuation after it. That is what it is FOR — assembling a sequence one byte at a time — but it means the caller owns the validity of what comes out. For text, use from_codepoint in lib/string.bx, which emits a whole character or refuses.
A zero byte is ORDINARY, not a terminator: a Burxt String carries its length in a header, so byte_as_string(0) has length 1 and the full 0..255 range needs no special case.
Answers a one-byte String holding n. Allocates: yes.
It refuses this:
print(byte_as_string(256));
error: `byte_as_string(256)` has no answer: a byte is 0 to 255. A codepoint above 255 is more than one byte — `from_codepoint` in lib/string.bx encodes it
--> byte_as_string.bx:1:7
|
1 | print(byte_as_string(256));
| ^^^^^^^^^^^^^^^^^^^
substring
substring(s, from, count) -> String
Builds a new String, so it needs somewhere to put it.
Answers count bytes of s, starting at from. Allocates: yes.
to_string
to_string(value) -> String
Takes an Int, a Bool or a Decimal<S>. It shares its formatter with print, so the two can never disagree about what a number looks like. A Bool allocates nothing, because both answers are constants.
There is no way for a class of yours to have one: to_string is a builtin rather than an interface a type can implement, so a user type has no display form. That is a real gap rather than a decision.
Answers the value, written out. Allocates: yes.
It refuses this:
let s: String = "already text";
print(to_string(s));
error: to_string(...) on a String would just copy it — use the value directly.
--> to_string.bx:2:7
|
2 | print(to_string(s));
| ^^^^^^^^^^^^
push
push(xs, value) -> Int
Appends to a growable array. The array lives in a region, which is what makes growing it a bump rather than a reallocation someone has to own.
Answers the new length. Allocates: yes.
truncate
truncate(xs, n) -> Int
Shortens a growable array to n. Nothing is freed — the region owns the storage, and it goes when the region does.
Answers the new length. Allocates: no.
read_file
read_file(path) -> String
Reaches the filesystem, so it is registered as touches files — a function that calls it must say so, and one that does not may not call it.
Answers the whole file. Allocates: yes. Carries touches files, so a caller must declare it.
write_file
write_file(path, contents) -> Int
Replaces the file. Reaches the filesystem, so it carries touches files.
Answers how many bytes went out. Allocates: no. Carries touches files, so a caller must declare it.
write_bytes
write_bytes(path, buffer) -> Int
Writes a growable [Int] where each element is one byte. For output that is not text — and a value outside 0–255 is a refusal rather than a truncation.
Answers how many bytes went out. Allocates: no. Carries touches files, so a caller must declare it.
argument
argument(n) -> String
Reads the command line, which is touches input.
Answers the nth command-line argument. Allocates: no. Carries touches input, so a caller must declare it.
argument_count
argument_count() -> Int
Reads the command line, which is touches input.
Answers how many arguments there are. Allocates: no. Carries touches input, so a caller must declare it.
divide_floor
divide_floor(a, b) -> Int
Int / Int is refused, because the two reasonable answers for a negative numerator disagree and an operator cannot ask which you meant. This is one of them; divide_toward_zero is the other. Dividing by zero stops the program.
Answers a / b, rounded toward negative infinity. Allocates: no.
divide_toward_zero
divide_toward_zero(a, b) -> Int
What C and most CPUs do. The other half of the pair Int / Int refuses to guess between.
Answers a / b, rounded toward zero. Allocates: no.
remainder
remainder(a, b) -> Int
Keeps the sign of its LEFT operand, like C’s %. Worth knowing when the result indexes something: a negative index is a bounds failure, not a wrong answer.
Answers what is left over. Allocates: no.
hash
hash(key) -> Int
Defined on the Equatable types — Int, Bool, String, Decimal<S> — which is exactly the set == works on. The sign bit is cleared, so it can index a table through remainder without producing a negative index. This is the only compiler support lib/map.bx needs.
Answers a non-negative hash. Allocates: no.
old
old(expression)
Legal only inside an ensures clause, where it is what lets a postcondition talk about change rather than only about the answer.
One limit worth knowing, and it is the compiler’s rather than the design’s: an ensures on a method that returns a class is refused today. A class travels back through a hidden pointer into the caller’s storage, and binding result to that needs care a scalar does not. Return a scalar, or drop the clause.
Answers what the expression was BEFORE the body ran. Allocates: no.
It refuses this:
class Counter {
n: Int,
function (self) bumped() -> Counter
ensures result.n == old(self.n) + 1
{ return Counter { n: self.n + 1 }; }
}
error: `ensures` on `Counter.bumped` is not supported yet: it returns a Counter, which travels through a hidden pointer into the caller's storage, so binding `result` to it needs care a scalar does not. Return a scalar, or drop the clause.
--> old.bx:5:17
|
5 | ensures result.n == old(self.n) + 1
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
result
result
A name, not a call, and in scope only inside an ensures clause. A declaration that takes a parameter called result and also writes result in an ensures is refused for the collision rather than shadowed quietly.
Answers the value being returned. Allocates: no.
exit
exit(code)
Reserved because the runtime calls libc’s exit to end a program on a failed contract or a bounds violation. A program that shadowed it would change what a failure does.
Answers nothing — the program ends. Allocates: no.
main
main
Burxt has no entry point: the whole file is the program, and statements at the top level run in order. So a function called main would look like an entry point and not be one — a trap rather than a crash, which is the kind of thing this language refuses. The name is reserved so it cannot be set.
Answers nothing — it is not an entry point. Allocates: no.