Standard Library
The Sun standard library is distributed as a precompiled stdlib.moon file. Include it in your manifest and import the sun namespace:
using sun;
function main() void {
var allocator = make_heap_allocator();
// Use stdlib types...
}
manifest {
moons: ["stdlib.moon"]
}Set SUN_PATH to the directory containing stdlib.moon. When building from source, this is the build/ directory.
HeapAllocator
The standard memory allocator using system malloc/free. Many stdlib types require an allocator for construction.
var allocator = make_heap_allocator();| Method | Signature | Description |
|---|---|---|
alloc<T>(count) | (i64) raw_ptr<T> | Allocate array of count elements |
alloc_raw(size) | (i64) raw_ptr<i8> | Allocate size raw bytes |
dealloc(ptr, size) | (raw_ptr<i8>, i64) void | Free allocated memory |
create<T>(args...) | (...) raw_ptr<T> | Allocate and construct a single T |
copy() | () HeapAllocator | Create a copy of the allocator |
var allocator = make_heap_allocator();
var p: raw_ptr<Point> = allocator.create<Point>(3, 4);
// p points to a heap-allocated, initialized Point
_free(p); // Manual cleanupUnique<T>
A smart pointer that automatically frees its data when deinit is called (at scope exit). Provides safe access to heap-allocated objects without manual unsafe blocks.
var allocator = make_heap_allocator();
var p = Unique<Point>(allocator.create<Point>(3, 4));
p.get().x; // Safe access - returns ref T
p.get_raw(); // Get raw pointer for low-level operations
p.is_null(); // Check if null
// p.deinit() called automatically at scope exit| Method | Signature | Description |
|---|---|---|
get() | () ref T | Safe dereference - returns a reference to the value |
get_raw() | () raw_ptr<T> | Get the underlying raw pointer |
is_null() | () bool | Check if pointer is null |
deinit() | () void | Free memory (called automatically) |
Vec<T>
A generic growable array backed by contiguous memory. Implements IIterable<T, Vec<T>> for use with for-in loops.
var allocator = make_heap_allocator();
var v = Vec<i32>(allocator, 8); // Initial capacity 8
v.push(10);
v.push(20);
v.push(30);
var val = v.get(0); // 10
v.set(1, 25); // Update index 1
var last = v.pop(); // Option.Some(30) (removes last element)
var len = v.size(); // 2| Method | Signature | Description |
|---|---|---|
push(value) | (T) void | Append element (grows if needed) |
pop() | () Option<T> | Remove and return last element; None when empty (ownership moves to the caller) |
get(index) | (i64) T | Get element at index |
set(index, value) | (i64, T) void | Set element at index |
size() | () i64 | Current number of elements |
capacity() | () i64 | Current allocated capacity |
isEmpty() | () bool | Check if empty |
reserve(n) | (i64) void | Ensure capacity for at least n elements |
clear() | () void | Remove all elements (doesn't deallocate) |
first() | () Option<T> | Get first element; None when empty |
last() | () Option<T> | Get last element; None when empty |
iter() | () VecIterator<T> | Get iterator for for-in loops |
Indexing
Vec<T> supports bracket indexing syntax:
v[0] = 42; // Calls __setindex__
var x = v[0]; // Calls __index__Iteration
var v = Vec<i32>(allocator, 4);
v.push(1); v.push(2); v.push(3);
for (var x: i32 in v) {
println(x);
}Map<K, V>
A generic hash map using open addressing with linear probing. Implements IIterable<V, Map<K, V>> for iterating over values. Supports i32, i64, and String keys.
var allocator = make_heap_allocator();
var m = Map<i64, i32>(allocator, 16); // Initial capacity 16
m.insert(42, 100);
var val = m.getOrDefault(42, 0); // 100
m.remove(42);| Method | Signature | Description |
|---|---|---|
insert(key, value) | (K, V) void | Insert or update a key-value pair |
get(key) | (K) V, IError | Get value by key (throws if not found) |
find(key) | (K) Option<V> | Get value by key; None if not present |
getOrDefault(key, default) | (K, V) V | Get value or return default |
remove(key) | (K) V, IError | Remove and return value (throws if not found) |
contains(key) | (K) bool | Check if key exists |
size() | () i64 | Number of entries |
capacity() | () i64 | Number of buckets |
isEmpty() | () bool | Check if empty |
clear() | () void | Remove all entries |
iter() | () MapIterator<K, V> | Get iterator over values |
Iteration
var m = Map<i64, i32>(allocator, 16);
m.insert(1, 10);
m.insert(2, 20);
for (var v: i32 in m) {
println(v); // Iterates over values
}The map automatically grows when load factor exceeds 70%. Initial capacity should be chosen based on expected size for best performance.
String
A heap-allocated mutable string backed by Matrix<u8>. Supports slicing via the [start:end] syntax which returns a StringView.
var allocator = make_heap_allocator();
var s = String(allocator, "Hello");
var t = String("literal only"); // allocator-less: uses a fresh HeapAllocator
println(s.length()); // 5
var ch = s.at(0); // 72 ('H')
s.append_char(33); // Append '!'| Method | Signature | Description |
|---|---|---|
length() | () i64 | Current string length |
capacity() | () i64 | Allocated capacity |
isEmpty() | () bool | True when the string has no bytes |
at(i) | (i64) u8 | Get byte at index |
set_at(i, val) | (i64, u8) void | Set byte at index |
append_char(ch) | (u8) void | Append a single byte |
append(other) | (const ref String) void | Append another String |
append_literal(s) | (static_ptr<u8>) void | Append a string literal |
equals_literal(s) | (static_ptr<u8>) bool | Compare with a string literal |
find_char(ch) | (u8) Option<i64> | Index of first occurrence; None if absent |
rfind_char(ch) | (u8) Option<i64> | Index of last occurrence; None if absent |
contains_char(ch) | (u8) bool | Check if the byte occurs |
find(needle) | (static_ptr<u8>) Option<i64>, (const ref String) Option<i64> | Index of first occurrence of a substring; None if absent |
contains(needle) | (static_ptr<u8>) bool, (const ref String) bool | Check if the substring occurs |
starts_with(s) / ends_with(s) | (static_ptr<u8>) bool, (const ref String) bool | Prefix / suffix check |
append_i64(n) / append_u64(n) | (i64) void | Append an integer in decimal |
append_f64(x) | (f64) void | Append a float in its shortest round-trip form (0.1, 1e+21) |
append(x) | (f64) void, (f32) void, ... | Overloads used by ${x} interpolation for every primitive |
parse_i64() | () Option<i64> | Whole string as a decimal integer; None if malformed or out of range |
parse_f64() | () Option<f64> | Whole string as a decimal float; None if malformed |
c_str() | () raw_ptr<u8> | NUL-terminated pointer for C, valid until the next mutation |
The terminator c_str() writes sits past the length, so the string itself is
unchanged. from_c_str(alloc, p) is the other direction — it copies a
NUL-terminated C string into an owned String, which is how sun.env.get and
read_dir hand back text from libc.
var path = String(alloc, "/tmp/");
path.append_literal("out.txt");
write_string(path.c_str(), body);Text Manipulation
Methods that keep the same bytes, or fewer, edit the string in place. Methods that produce new strings take the allocator to build them with.
var line = String(allocator, " name = Ada ");
line.trim(); // "name = Ada"
line.replace(" = ", "="); // "name=Ada"
line.to_upper(); // "NAME=ADA"
var fields = line.split(allocator, 61); // ['=' is 61] -> ["NAME", "ADA"]
var again = join(allocator, fields, "="); // "NAME=ADA"| Method | Signature | Description |
|---|---|---|
to_lower() / to_upper() | () void | Convert ASCII letters in place |
trim() | () void | Drop leading and trailing ASCII whitespace in place |
trim_start() / trim_end() | () void | Drop whitespace from one end in place |
reverse() | () void | Reverse the bytes in place |
replace(from, to) | (static_ptr<u8>, static_ptr<u8>) void, (const ref String, const ref String) void | Replace every occurrence in place; an empty from does nothing |
clone(alloc) | (const ref HeapAllocator) String | Copy into a new String |
substr(alloc, start, count) | (const ref HeapAllocator, i64, i64) String, IError | Copy a byte range; throws if it falls outside the string |
split(alloc, sep) | (const ref HeapAllocator, u8) Vec<String>, (const ref HeapAllocator, static_ptr<u8>) Vec<String> | Split into owned pieces; always yields one piece more than there are separators |
split_nonempty(alloc, sep) | (const ref HeapAllocator, u8) Vec<String>, (const ref HeapAllocator, static_ptr<u8>) Vec<String> | Like split, but empty pieces are dropped: adjacent, leading, and trailing separators yield nothing |
split_whitespace(alloc) | (const ref HeapAllocator) Vec<String> | Split on runs of ASCII whitespace; never yields an empty piece |
join(alloc, parts, sep) | (const ref HeapAllocator, const ref Vec<String>, static_ptr<u8>) String | Free function: concatenate pieces with a separator between them |
Slicing
String slicing returns a StringView (non-owning view, no copy):
var s = String(allocator, "Hello, World!");
var view = s[0:5]; // StringView of "Hello"
println(view.length()); // 5StringView
A non-owning view into a String. Provides read-only access to a substring without copying.
| Method | Signature | Description |
|---|---|---|
length() | () i64 | View length |
at(i) | (i64) u8 | Get byte at index (relative to view) |
equals_literal(s) | (static_ptr<u8>) bool | Compare with a string literal |
find_char(ch) | (u8) Option<i64> | Index of first occurrence; None if absent |
rfind_char(ch) | (u8) Option<i64> | Index of last occurrence; None if absent |
contains_char(ch) | (u8) bool | Check if the byte occurs |
append_i64(n) / append_u64(n) | (i64) void | Append an integer in decimal |
append_f64(x) | (f64) void | Append a float in its shortest round-trip form (0.1, 1e+21) |
append(x) | (f64) void, (f32) void, ... | Overloads used by ${x} interpolation for every primitive |
parse_i64() | () Option<i64> | Whole string as a decimal integer; None if malformed or out of range |
parse_f64() | () Option<f64> | Whole string as a decimal float; None if malformed |
A StringView must not outlive the parent String. The view holds a reference to the String's internal memory.
Matrix<T>
N-dimensional matrices backed by contiguous heap memory. Supports bracket indexing and slicing.
var allocator = make_heap_allocator();
var m = Matrix<i32>(allocator, [3, 3]); // 3x3 matrix
m[0, 0] = 1;
m[1, 1] = 5;
var val = m[1, 1]; // 5| Method | Signature | Description |
|---|---|---|
get(indices) | (ref array<i64>) T | Get element at indices |
set(indices, value) | (ref array<i64>, T) void | Set element at indices |
shape() | () array<i64> | Get dimensions |
size() | () i64 | Total number of elements |
ndims() | () i64 | Number of dimensions |
See Matrices for detailed documentation.
Mutex
Thread synchronization primitive using atomic compare-and-swap with futex-based blocking.
var m = Mutex();
m.lock();
// Critical section
m.unlock();| Method | Signature | Description |
|---|---|---|
lock() | () void | Acquire the lock (blocks if contended) |
unlock() | () void | Release the lock |
See Threads for usage with spawned threads.
File I/O
stdlib/io.sun (module sun.io). Every path is taken two ways: as a
static_ptr<u8> — what a string literal is — and as a ref String for a path
put together at runtime.
using sun;
using sun.io;
var f = File();
try {
f.open("notes.txt", FileMode.Write);
f.write("Hello, World!");
f.close();
} catch (e: IError) {
eprintln("could not write notes.txt");
}
// Or in one step
var text = read_to_string(alloc, "notes.txt");
// A runtime path
var path = String(alloc, "/tmp/");
path.append_literal("report.csv");
write_string(path, text);FileMode is Read (must exist), Write (create or truncate) or Append.
Whence is Start, Current or End.
| Method | Signature | Description |
|---|---|---|
open | (path: Path, mode: FileMode) void, IError | Open a file |
adopt | (fd: i32) void | Take ownership of an open descriptor |
write | (data: static_ptr<u8>) i64 / (data: ref String) i64 | Bytes written |
write_bytes | (buf: raw_ptr<u8>, len: i64) i64 | Write raw bytes |
read_all | (alloc: ref HeapAllocator) String, IError | Read to end of file |
read_into | (buf: raw_ptr<u8>, len: i64) i64 | Read into a buffer |
seek | (offset: i64, whence: Whence) i64 | New offset from the start |
tell / size | () i64 | Current position / total length |
sync / truncate | () void, IError / (length: i64) void, IError | Flush / resize |
close / is_open / get_fd | Close (idempotent), query, borrow the fd |
A File closes itself at scope exit. The descriptor it holds is 0 when it
holds nothing, so a moved-from File — which is zeroed — closes nothing rather
than closing stdin.
Free functions:
| Function | Signature |
|---|---|
read_to_string | (alloc: ref HeapAllocator, path: Path) String, IError |
write_string | (path: Path, contents: ref String) void, IError |
remove_file / remove_dir | (path: Path) void, IError |
rename_file | (old_path: Path, new_path: Path) void, IError |
make_dir | (path: Path, mode: i32) void, IError |
exists / is_dir / is_file | (path: Path) bool |
file_size | (path: Path) i64, IError |
read_line | (alloc: ref HeapAllocator) Option<String> |
Path stands for the two overloads each of these has: static_ptr<u8> (a
string literal) and ref String (a path built at runtime). rename_file has
all four combinations.
read_line reads stdin one byte at a time and strips the newline, returning
None only at end of input with nothing read. It is deliberately unbuffered: a
buffered reader would swallow bytes a child process spawned later expects to
find on the descriptor.
There is no stat. struct stat has a different field order and padding on
x86-64 and aarch64, and the stdlib is compiled from one source for both, so the
questions people actually ask are answered with access, opendir and lseek
instead.
Reading a directory
read_dir returns the entries with their kind attached, taken from the
directory record itself — no second syscall per entry, and no application-level
parsing of getdents64 output.
var entries = read_dir(alloc, "/etc");
for (var e: DirEntry in entries) {
if (e.is_dir()) {
print("dir ");
} else {
print("file ");
}
println(e.name());
}| Item | Signature | Description |
|---|---|---|
read_dir | (alloc: ref HeapAllocator, path: Path) Vec<DirEntry>, IError | . and .. excluded, filesystem order |
DirEntry.name | () ref String | Borrowed — the entry keeps it |
DirEntry.kind | () FileKind | File, Dir, Symlink, Other or Unknown |
DirEntry.is_dir / is_file | () bool |
Some filesystems do not report a kind, giving FileKind.Unknown; is_dir(path)
answers it with a syscall when that matters.
Waiting on descriptors
Poller wraps poll(2). Descriptors keep the position they were added at, so
is_readable(i) answers for the i-th one. Nothing here needs a struct pollfd
assembled by hand.
var poller = Poller(alloc);
poller.add_read(sock_fd);
poller.add_read(pipe_fd);
var ready = poller.wait(1000); // -1 blocks forever
if (poller.is_readable(0)) { /* sock_fd has data */ }
if (poller.is_hup(1)) { /* pipe_fd's writer closed */ }| Method | Signature | Description |
|---|---|---|
add_read / add_write / add_read_write | (fd: i32) void | Watch a descriptor |
add | (fd: i32, events: i16) void | Watch with an explicit POLL* mask |
wait | (timeout_ms: i32) i32, IError | Number ready; -1 blocks |
is_readable / is_writable / is_hup / is_error | (i: i64) bool | |
fd_at / revents | (i: i64) i32 / (i: i64) i16 | |
clear / count | Reset, or how many are watched |
The bits are POLLIN, POLLPRI, POLLOUT, POLLERR, POLLHUP and
POLLNVAL. Do not call add while a wait is in flight: growing the set
reallocates it.
Environment
stdlib/env.sun (module sun.env).
using sun.env;
var home = match get(alloc, "HOME") {
Option.Some(p) => p,
Option.None => String(alloc, "/")
};
try { set("LANG", "C"); } catch (e: IError) { }
var here = cwd(alloc);| Function | Signature | Description |
|---|---|---|
get | (alloc: ref HeapAllocator, name: Name) Option<String> | None if unset |
has | (name: Name) bool | Set at all, whatever the value |
set | (name: Name, value: Name) void, IError | Replaces any existing value |
remove | (name: Name) void, IError | Removing an unset variable is fine |
cwd | (alloc: ref HeapAllocator) String, IError | Working directory |
set_cwd | (path: Name) void, IError | Process-wide, and it persists |
args | (alloc: ref HeapAllocator, argc: i32, argv: raw_ptr<raw_ptr<i8>>) Vec<String> | argv[0] first |
Name stands for the two overloads each of these has: static_ptr<u8> (a
literal) and ref String (a name or value built at runtime). set has all
four combinations.
args takes argc and argv from main rather than discovering them. Under
the JIT the running process is the compiler, so its own argv is not the
script's — see Entry point.
function main(argc: i32, argv: raw_ptr<raw_ptr<i8>>) i32 {
var alloc = make_heap_allocator();
var cli = args(alloc, argc, argv);
for (var s: String in cli) { println(s); }
return 0;
}Listing every environment variable needs the environ global, and Sun has no
extern global declaration yet.
Processes
stdlib/process.sun (module sun.process). Command covers running a program;
the raw POSIX calls are there for programs that drive the fork themselves.
using sun.process;
var cmd = Command(alloc, "/bin/sh");
cmd.arg("-c");
cmd.arg("printf hello");
var out = cmd.output();
println(out.status()); // 0
println(out.stdout()); // helloCommand
| Method | Signature | Description |
|---|---|---|
init | (alloc: ref HeapAllocator, program: static_ptr<u8> | ref String) | The program is argv[0] |
arg | (value: static_ptr<u8>) void / (value: ref String) void | Append one argument |
stdin / stdout / stderr | (mode: Stdio) void | Inherit, Piped or Null |
start | () Child, IError | Run it, return immediately |
status | () i32, IError | Run to completion, inheriting the streams |
output | () Output, IError | Run to completion, capturing both streams |
Arguments are passed to the program as they are given — a space inside one argument stays inside it, with no shell reparsing.
Child
| Method | Signature | Description |
|---|---|---|
id | () i32 | Process id |
wait | () i32, IError | Exit code; a killed child reports 128 + signal |
try_wait | () Option<i32>, IError | None while it is still running |
kill | (sig: i32) void, IError | |
write_stdin / close_stdin | (data: ref String) i64 / () void | Needs stdin(Stdio.Piped) |
take_stdout / take_stderr | () i32 | Hand the pipe over |
collect | (alloc: ref HeapAllocator) Output, IError | Drain both pipes and wait |
collect polls the two pipes rather than draining one and then the other: a
child that fills the stderr pipe while the parent is stuck reading stdout would
deadlock. Output gives status() i32 and borrowed stdout() / stderr().
A Child releases any pipes it still holds when it goes out of scope. It never
waits and never signals there — a moved-from Child is all zeroes, and reaping
or killing on that would touch a process the object no longer stands for. Call
wait to reap.
Identity, signals and raw control
| Function | Signature | Description |
|---|---|---|
pid / parent_pid | () i32 | |
uid / euid | () u32 | |
kill | (target: i32, sig: i32) void, IError | |
set_pgid | (target: i32, pgid: i32) void, IError | 0 means "this one" |
new_session | () i32, IError | setsid |
exit / exit_now | (code: i32) void | With / without atexit handlers |
fork | () i32, IError | 0 in the child, the child's pid in the parent |
exec | (cmd: ref Command) void, IError | Only returns on failure |
wait_pid | (target: i32, options: i32) i32, IError | Raw wait status; -1 = any child |
exited / exit_status / signaled / term_signal | (status: i32) | Read a wait status |
open_pipe | (alloc: ref HeapAllocator) Pipe, IError | read_fd() / write_fd() |
dup_fd | (old_fd: i32, new_fd: i32) void, IError | dup2 |
Signal numbers are SIGHUP, SIGINT, SIGQUIT, SIGKILL, SIGSEGV,
SIGPIPE, SIGALRM, SIGTERM, SIGCHLD, SIGCONT, SIGSTOP; wait_pid
takes WNOHANG.
Between fork and exec a child may only call things that are safe inside a
signal handler — no allocation, no method that might allocate. Command is
written to that rule; hand-rolled forks have to be too.
Time
stdlib/time.sun (module sun.time).
using sun.time;
var start = now();
sleep(millis(50));
println(start.elapsed().as_millis());
println(format(alloc, utc(unix_time()), "%Y-%m-%d %H:%M:%S"));| Item | Signature | Description |
|---|---|---|
nanos / micros / millis / seconds | (n: i64) Duration | Build a Duration |
Duration.as_secs / as_millis / as_micros / as_nanos | () i64 | |
Duration.as_secs_f64 | () f64 | |
Duration.subsec_nanos | () i64 | Remainder on top of as_secs |
now | () Instant | Monotonic clock |
Instant.elapsed | () Duration | Time since it was taken |
Instant.since | (earlier: const ref Instant) Duration | Zero if this is the earlier |
unix_time / unix_time_millis | () i64 | Wall clock, since the epoch |
sleep | (d: const ref Duration) void | A signal can cut it short |
utc / local | (unix_secs: i64) DateTime | Break into calendar fields |
to_unix_utc / to_unix_local | (dt: const ref DateTime) i64 | And back |
format | (alloc, dt: const ref DateTime, fmt: raw_ptr<u8>) String | strftime |
Only differences between Instants mean anything: the zero point is arbitrary,
and the clock never jumps backwards when the system time is adjusted.
DateTime uses human units — year is the full year and month is 1-12, not
C's tm_year/tm_mon offsets. It also carries day, hour, minute,
second, weekday (Sunday = 0), yearday and utc_offset.
Option<T> and Result<T, E>
Payload enums for absence and fallibility, defined in option.sun:
enum Option<T> { Some(T), None }
enum Result<T, E> { Ok(T), Err(E) }Inspect them with match; construction infers type arguments from the
payload (Option.Some(42)) or from the expected type (Option.None in
return position or against an annotation):
var v = match m.find(key) {
Option.Some(x) => x,
Option.None => fallback
};Use Option for "might not be there" (lookups, first/last, pop,
iterator next), and IError throwing for actual failures. Result<T, E>
suits fallible operations whose error is a value rather than an exception.
Iteration: IIterator<T, Container> and IIterable<T, Self>
The iteration protocol lives in iterator.sun and is what for-in drives:
interface IIterator<T, Container> {
function next(container: ref Container) Option<T>;
}
interface IIterable<T, Self> {
function iter() IIterator<T, Self>;
}next() yields the next element or Option.None once the sequence is
exhausted (and keeps returning None afterwards). for (var x: T in c) calls
iter() when c has one, then loops on next() until None; the loop
variable's type must match T. Iterators are separate objects that receive the
container by ref on every call, so a container can be iterated more than once
and iterators stay cheap:
class Countdown implements IIterator<i32, Countdown> {
var n: i32;
function init(n: i32) { this.n = n; }
function next(self: ref Countdown) Option<i32> {
if (this.n == 0) { return Option.None; }
this.n = this.n - 1;
return Option.Some(this.n + 1);
}
}
function main() i32 {
var sum: i32 = 0;
for (var x: i32 in Countdown(5)) {
sum = sum + x;
}
return sum; // 5 + 4 + 3 + 2 + 1 = 15
}Iterators can also be driven by hand:
var it = v.iter();
var going = true;
while (going) {
match it.next(v) {
Option.Some(x) => { println(x); },
Option.None => { going = false; }
};
}A container implements IIterable and returns its concrete iterator from
iter(); the loop then passes the container to every next() call, so
next must take that container type by ref (the compiler checks this —
it is what keeps the container access inside next memory-safe):
class Range implements IIterable<i32, Range> {
var start: i32;
var end: i32;
function init(s: i32, e: i32) { this.start = s; this.end = e; }
function iter() RangeIterator { return RangeIterator(this.start); }
}
class RangeIterator implements IIterator<i32, Range> {
var cur: i32;
function init(s: i32) { this.cur = s; }
function next(r: ref Range) Option<i32> {
if (this.cur >= r.end) { return Option.None; }
this.cur = this.cur + 1;
return Option.Some(this.cur - 1);
}
}Because iter() returns a class rather than the IIterator<T, Self> fat
pointer the interface names, an IIterable implemented this way is
static-only: it drives for-in, but a Range cannot be converted to an
IIterable<i32, Range> value (there is no way to dispatch iter() through
it soundly).
Vec<T>, LinkedList<T>, Map<K, V> (values) and ContiguousBuffer<T>
implement IIterable.
BigUint
Arbitrary-precision unsigned integer from bigint.sun: little-endian u64
limbs in a Vec, schoolbook arithmetic built on _mul_hi_u64. Values are
owned (clone() to copy); the by-ref argument of a binary operation is never
modified.
var n = BigUint(alloc, 1);
for (var i: u64 = 2; i <= 30; i = i + 1) { n.mul_small(i); } // 30!
var text = String(alloc, "");
n.append_decimal(text); // 265252859812191058636308480000000| Method | Signature | Description |
|---|---|---|
BigUint(alloc) / BigUint(alloc, v) | (const ref HeapAllocator[, u64]) | Zero, or a machine word |
clone() | () BigUint | Independent copy |
is_zero(), bit_length(), limb_count(), limb(i), low_u64() | Inspection; limb(i) is 0 beyond the top | |
to_u64() | () Option<u64> | The value if it fits in 64 bits |
set_u64(v), assign(other), clear() | Replace the value | |
compare(other), compare_u64(v), equals(other) | (const ref BigUint) i64, (u64) i64, (const ref BigUint) bool | Ordering (-1/0/1) |
add(other), add_small(v) | (const ref BigUint) void, (u64) void | In-place addition |
sub(other) | (const ref BigUint) void, IError | In-place subtraction; throws OverflowError if the result would be negative |
mul(other), mul_small(v), mul_pow10(n) | (const ref BigUint) void, (u64) void, (i64) void | In-place multiplication |
shl(bits), shr(bits) | (i64) void | In-place shifts |
divmod_small(d) | (u32) u32, IError | Divide in place by a 32-bit divisor, returning the remainder |
append_decimal(out) | (ref String) void | Append the decimal digits |
set_decimal(text) | (const ref String) bool | Parse decimal digits; false (value left at zero) if malformed |
Numeric conversion
A value never narrows on its own: assigning an i64 to an i32 is a compile
error. math.sun provides safe_convert<T>(x), which converts between any two
numeric types and throws ConversionError when the value does not fit:
var ms: i64 = 200;
var t: i32 = safe_convert<i32>(ms); // 200
var big: i64 = 4294967498;
var u: i32 = safe_convert<i32>(big); // throws ConversionError
var n: u64 = safe_convert<u64>(-1); // throws: negative to unsigned
var b: u8 = safe_convert<u8>(255.0); // 255
var c: u8 = safe_convert<u8>(256.0); // throws
var h: f32 = safe_convert<f32>(1.0e300); // throws: infinite in f32Checked: integer values outside T's range (including a sign change that flips
the value), floats that are NaN or outside T's range, and an f64 that
overflows f32. A fraction is truncated toward zero, as with _convert.
Widening and integer-to-float conversions always succeed.
safe_convert takes two type parameters, <T, U>; the source type U is
inferred from the argument, so only the target is written. When the value is
known to fit, the _convert<T>(x) intrinsic does the same conversion with no
check (see Intrinsics).
Numeric limits
math.sun also defines I64_MAX, I64_MIN, F64_MAX, F64_MIN_POSITIVE (smallest normal) and F64_EPSILON.
Json
json.sun parses JSON text into an owned tree and writes it back, compact or
pretty. Objects keep their members in insertion order (lookups are a linear
scan), numbers are Int(i64) when the text is an integer that fits and
Float(f64) otherwise, strings are UTF-8 Strings.
var alloc = make_heap_allocator();
var doc = json_parse(alloc, "{\"name\": \"sun\", \"tags\": [1, 2.5]}");
var name: ref String = doc.get("name").as_string();
var second: f64 = doc.get("tags").at(1).as_f64();
var obj = json_object(alloc);
obj.set(String(alloc, "ok"), Json(true));
obj.set(String(alloc, "tags"), json_array(alloc));
obj.get("tags").push(Json(42));
println(obj.to_string(alloc)); // {"ok":true,"tags":[42]}
println(obj.to_pretty_string(alloc, 2));The tree node is class Json wrapping a public var value: JsonValue:
enum JsonValue { Null, Bool(bool), Int(i64), Float(f64), String(String), Array(Vec<Json>), Object(Vec<JsonMember>) }
class JsonMember { var key: String; var value: Json; }Match on j.value directly when the accessors below are not enough.
The tree owns everything in it. Json(s), push(item) and set(key, value)
take their String and Json arguments by value, which in Sun means they are
moved in: the caller cannot use them afterwards, and the borrow checker rejects
a later use. When a value is still needed after being placed in a document,
store a copy instead. json_string(alloc, ref s) does that for a String:
// cfg.model is still needed later, so the document gets a copy
req.set(String(alloc, "model"), json_string(alloc, cfg.model));
// Equivalent to an explicit clone:
req.set(String(alloc, "model"), Json(cfg.model.clone(alloc)));
// Moves cfg.model into the document; using it again is a compile error
req.set(String(alloc, "model"), Json(cfg.model));| Function / Method | Signature | Description |
|---|---|---|
json_parse(alloc, text) | (ref HeapAllocator, ref String) Json, IError | Parse a complete document (also accepts a static_ptr<u8> literal). Rejects trailing text, leading zeros, control characters in strings, and nesting deeper than 256 |
Json(), Json(b), Json(n), Json(x), Json(s) | (), (bool), (i64), (f64), (String) | Build a scalar node (Json(s) moves the String into the node; see above) |
json_array(alloc) / json_object(alloc) | (ref HeapAllocator) Json | Empty array / object |
json_string(alloc, text) | (ref HeapAllocator, static_ptr<u8>) Json, (ref HeapAllocator, ref String) Json | String node from a literal, or a copy of a borrowed String (the caller keeps its own) |
is_null(), is_bool(), is_int(), is_float(), is_number(), is_string(), is_array(), is_object() | () bool | Kind checks |
as_bool(), as_i64(), as_f64() | () T, IError | Scalar readers; as_i64 accepts an integral Float, as_f64 accepts an Int |
as_string() | () ref String, IError | Borrow the string |
len() | () i64 | Array items or object members (0 for scalars) |
at(i) | (i64) ref Json, IError | Array item |
get(key) | (static_ptr<u8>) ref Json, IError, (ref String) ref Json, IError | Object member value |
has(key) | (static_ptr<u8>) bool | Whether an object has the key |
key_at(i) / value_at(i) | (i64) ref String, IError / (i64) ref Json, IError | Object members by position, in insertion order |
push(item) | (Json) void, IError | Append to an array |
set(key, value) | (String, Json) void, IError | Set an object member (replaces an existing key's value in place, otherwise appends) |
write(out) / write_pretty(out, indent) | (ref String) void / (ref String, i64) void | Append JSON text to a String |
to_string(alloc) / to_pretty_string(alloc, indent) | (ref HeapAllocator) String / (ref HeapAllocator, i64) String | JSON text as a new String |
Readers throw JsonError (code 60) on a kind mismatch or missing key; parse
errors carry the byte offset of the problem in offset(). When writing,
Float values keep a .0 or exponent so they read back as Float, and
non-finite floats become null.
Error Types
All error types implement IError with code() i32 and message() String
methods. message() returns an owned clone of the description, so the text
stays usable for as long as the caller keeps it, independent of the error
object itself.
| Error Type | Code | Description |
|---|---|---|
Error | custom | Generic error with custom code and message |
EmptyError | 1 | Operations on empty containers |
NotFoundError | 2 | Failed lookups |
IndexOutOfBoundsError | 3 | Invalid array/vector indices |
DivisionByZeroError | 4 | Division by zero |
OverflowError | 5 | Numeric overflow |
InvalidArgumentError | 6 | Invalid function arguments |
IOError | 7 | I/O operations |
AllocationError | 8 | Memory allocation failures |
ConversionError | 9 | A value does not fit the type safe_convert targets |
JsonError | 60 | JSON parse errors (with offset()) and kind mismatches |
function pop_first(v: ref Vec<i32>) i32, IError {
if (v.isEmpty()) {
throw EmptyError();
}
return v.get(0);
}Error takes its message either as a literal or as a String put together at
runtime. Given a String it keeps its own copy, so the message stays readable
after the String it came from is gone — including in a caller that catches the
error several frames up.
var path = String(alloc, "/tmp/");
path.append(name);
throw Error(errno(), path);
// ... several frames up:
catch (e: IError) {
var msg: String = e.message(); // an owned clone
eprintln(msg);
}Print Functions
Overloaded print (no newline) and println (with newline) from print.sun. Both accept any of the following argument types:
| Overload | Description |
|---|---|
i32, i64, u32, u64 | Print integer value |
f64 | Print f64 |
bool | Print true or false |
static_ptr<u8> | Print string literal |
ref String | Print a String |
(no argument, println only) | Print a bare newline |
Interpolated strings can be passed directly:
var x: i32 = 42;
println(`x = ${x}`); // prints "x = 42\n"
print("no newline");
println();eprint and eprintln write to stderr instead, so a caller can tell
diagnostics apart from the program's real output. They take a string literal or
a ref String; interpolation covers everything else.
eprintln("could not open the file");
eprintln(`giving up after ${attempts} tries`);