Rust programs written entirely in Rust
Posted on
mustang
is a system for writing Rust programs entirely in Rust, meaning
they don't use libc
, crt1.o
, or any C code. It's experimental, but it's
complete enough to run a std
-using Hello World and other simple programs
on Linux on x86-64, x86, and aarch64.
See mustang
's README for information on how to use it. This post walks
through the major steps leading to this point.
System calls
The first building block for mustang
was rustix
, a crate providing a
syscall-like API, but which can be configured to use different backends,
currently either libc or raw Linux syscalls. rustix
has been a place to
help prototype the io-lifetimes
crate and I/O safety feature, support
Wasmtime's WASI implementation, support cap-std's filesystem sandbox
implementation, and to push the boundaries of low-level performance
while maintaining safe and relatively idiomatic Rust APIs.
These continue to be the main motivators for rustix
, and that's an
important point: while mustang
isn't particularly practical, it isn't all a
one-off effort. Some of the major pieces have real-world practical use cases.
The first part of the inspiration for mustang
was the realization that rustix
could be used in this way.
Process startup
crt1.o and process startup has always had an aura of mystery about it, at least to me. There are parts implemented in assembly, parts that use obscure C compiler features, parts that are heavily conditionalized with ifdefs, and support for dynamic linking requires several particularly tricky features.
The other part of the inspiration for mustang
was the realization that simple
Rust programs don't need most of what goes into crt1.o and libc startup code
The minimum needed is just a little bit of assembly to translate from the
initial process state that the OS sets up into the Rust calling convention.
Once Rust code is running, it just needs to compute argc, argv, and envp,
call any .init_array
functions, and then call main
.
This code is in mustang's origin
crate; it's very small.
To tell the linker to avoid linking in crt1.o so that it uses origin
's startup
code instead, mustang
defines custom targets named *-mustang
. These primarily
add -nostdlib
to the link command so that crt1.o
is not linked in.
Cargo's build-std mode allows mustang
to easily compile the standard
library with its custom targets, so it doesn't need to make any upstream
standard library changes (for now...).
C-scape
If we can start a process with Rust code, and make system calls with Rust code, what's left that's not Rust?
As a practical matter, Rust's standard library depends on libc
and uses many
libc APIs. One option would be to build a runtime library that runs on
no_std
. However, in the limit, that would essentially require a
reimplementation of std
, which would be a lot of work, and a huge burden to
maintain.
So mustang
instead defines C-compatible ABIs providing functions like write
,
strlen
, memcpy
, mmap
, and other things used by Rust's standard library,
using rustix
internally to do actual system calls. These implementations are
currently very minimal, but they are enough to support Hello World, running
with std
.
And going forward, it should be relatively straightforward to add additional
libc
APIs as needed by std
.
That said, the goal here is to just implement enough of libc to get std
working on top of it. Mustang
doesn't need to implement a whole new libc,
which would be a lot more work, and much of it wouldn't be needed in Rust
programs.
This code is in mustang's c-scape
crate.
The global_allocator
Rust allows users to configure their global allocator with the
global_allocator
attribute. The default global allocator ultimately
uses libc malloc
. Writing a new malloc
is a lot more work that I was
looking to do here, so the question here was, is there a nice Rust allocator
crate that can be used with #[global_allocator]
?
It turns out, there are a few options, but one which turned out to be
particularly easy to integrate was wee_alloc
. Built for reducing code size
on WebAssembly, wee_alloc
doesn't do fancy optimizations, but it does plug
directly into #[global_alloctor]
with no additional hassle, and it is
portable, so it was very easy to set up. And it only uses a few things from
libc
which c-scape
is easily able to support.
Putting it all together
The mustang
crate pulls all these pieces together to make it easy to use
them all at once. But note that since mustang
is aiming at supporting
regular std
-using Rust code, it isn't something you explicitly call
yourself. As such, it currently needs an extern crate mustang
to link it
into the program.
A note about safety (and a lack thereof)
There is often a temptation whenever one is working with C code to
Rewrite It In Rust, however it's not always a good idea. In mustang
's case,
existing libc implementations are mature, robust, and well optimized. Libc
interfaces are relatively portable, and on some operating systems, libc
interfaces are the only stable interfaces to operating system functionality.
There's no urgency to stop using libc or to rewrite it.
mustang
is inspired by the realization that some of the major pieces were
already available, and the remaining pieces to get basic examples working could
be written with a relatively quick effort. It's new code, it's experimental, it
has a lot of unsafe
code, it lacks important optimizations, it's overall
far less safe to use than just using libc, and it would take a lot of work to
change this. But, it's fun, and educational.
It's also possible that in the future, mustang
could be part of a path to
designing new low-level system features like command-line arguments, environment
variables, or OS error codes are handled with more safety or other ideas such as
first-class I/O. This is also part of why the goal isn't to just build a Rust
implementation of libc: many libc APIs consist of a small amount of code behind
a function signature that uses raw pointers; just putting Rust behind those
kinds of APIs isn't going to make them significantly safer. But that's another
post; for now...
Hello, World!
See mustang
's README for step-by-step instructions for how to build an
run mustang
.
If you're interested in following the story, helping out, or even just asking questions, come say hi in the chat channel, or file an issue!