Files
MontaukOS/programs/rust

Rust on MontaukOS

Rust programs are built for x86_64-unknown-montauk, a built-in target of a forked rustc with a native std (target_os = "montauk", not a Unix). Both std programs and no_std programs (on montauk-rt) are supported.

Setup (once)

# rustup, plus a nightly whose cargo the linked toolchain borrows
curl --proto '=https' -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain none
~/.cargo/bin/rustup toolchain install nightly

# The fork: rust-lang/rust, branch `montauk` (bootstrap.toml is set up)
cd ~/dev/mtk/rust
./x build library --stage 1 --target x86_64-unknown-linux-gnu,x86_64-unknown-montauk
rustup toolchain link montauk ~/dev/mtk/rust/build/host/stage1

The stage1 build takes about five minutes (it downloads CI LLVM). After changing only library/, rebuild with ./x build library --stage 1 --target x86_64-unknown-montauk (seconds); the link stays valid.

rust-toolchain.toml selects the montauk toolchain for this directory. It has no cargo of its own; rustup falls back to the nightly's cargo. make programs builds the Rust programs when that toolchain is linked, and prints a notice and skips them otherwise. It never downloads a toolchain (RUSTUP_AUTO_INSTALL=0).

Building

make -C programs rust          # cargo build --release, copies to bin/os/*.elf
cd programs/rust && cargo build --release

Output: target/x86_64-unknown-montauk/release/<name>, installed as programs/bin/os/<name>.elf. Add new binaries to RUST_PROGRAMS in programs/GNUmakefile and to the workspace members.

The GCC driver links crt1/crti/crtbegin, the libc and libgcc from toolchain/sysroot, the same as ports. After changing programs/lib/libc, re-run toolchain/build-montauk-toolchain.sh so Rust programs pick it up. make rust relinks the programs when the sysroot libc or the toolchain's std is newer than the last build (cargo tracks neither).

Layout

Path Purpose
.cargo/config.toml target = x86_64-unknown-montauk, linker = x86_64-montauk-gcc
rust-toolchain.toml the linked montauk toolchain
ruststd/ std smoke test (ruststd, ruststd net <host> [port])
montauk-rt/ no_std runtime: syscalls, allocator, print!, argv, threads, futex Mutex, panic handler
rusthello/ no_std smoke test (rusthello, rusthello panic, rusthello panic-thread, rusthello fault-thread)

The std port (in the fork)

Fork path What
compiler/rustc_target/src/spec/base/montauk.rs, targets/x86_64_unknown_montauk.rs Target: static non-PIE ET_EXEC, no red zone, local-exec TLS, panic=abort, linker x86_64-montauk-gcc
library/std/src/sys/pal/montauk/ Platform layer: abi.rs (syscall numbers, status codes, SYSCALL wrappers)
library/std/src/sys/*/montauk.rs alloc, env, fs, io errors, net, paths, process, random, stdio, futex, thread, time
library/std/src/os/montauk/ std::os::montauk::ffi::{OsStrExt, OsStringExt}

std calls the kernel directly. The libc is used only where C code in the same process must agree: the heap (malloc), the environment table (setenv), exit (atexit handlers) and line-buffered terminal input (read(0)). The syscall numbers in abi.rs are a hand-kept subset of Api/Syscall.hpp.

Platform behaviour

  • Errors: the raw OS error of an io::Error is the kernel status code (positive, montauk/statuscodes.h); ErrorKind is mapped from it.
  • Paths: 0:/os/x.elf is absolute; joining onto it replaces the base. Search paths (PATH, env::split_paths) are separated by ;, not :.
  • Files: kernel handles have no position, so File keeps its own cursor. OpenOptions::truncate deletes and recreates the file; set_len can only grow a file. No links, permissions are read-only, fsync is a no-op.
  • Threads: stacks are mapped with SYS_MMAP_ANON (no guard page). A detached thread is reaped (joined, stack freed) by the next spawn or join, so its kernel slot does not leak. available_parallelism() is an error (the kernel does not report a CPU count). std::process::exit from any thread ends the process (libc exit -> SYS_EXIT_GROUP).
  • Sync: Mutex, RwLock, Condvar, Once and thread parking use the generic futex implementations over SYS_FUTEX_WAIT/SYS_FUTEX_WAKE.
  • thread_local! uses native #[thread_local] (PT_TLS); destructors run when a spawned thread exits (not on the main thread).
  • Time: Instant has 1 ms resolution; SystemTime comes from the wall clock (local time minus the timezone offset) with 1 s resolution.
  • Processes: Command supports arguments, environment, current_dir (entered in the parent for the duration of the spawn), wait, try_wait and kill. Only inherited stdio is supported; piped/null stdio and output() return Unsupported, because a redirected child's output stream disappears with the child. A bare program name is searched in PATH, else 0:/os and 0:/games; .elf may be omitted. ExitStatus shows abnormal ends as "terminated by signal N".
  • Net: IPv4 only. TCP connect/accept/read/write with read timeouts and non-blocking mode, UDP send_to/recv_from/connect, DNS via SYS_RESOLVE. No socket options, no peek, no shutdown(Write), and accepted connections do not know the peer's address.
  • panic = "abort": a panic on any thread prints the message and kills the whole process (exit status "signal 6").
  • HashMap's random keys come from SYS_GETRANDOM, which is not a CSPRNG.