Rust programs written entirely in Rust
mustang is a system for writing Rust programs entirely in Rust, meaning
they don't use
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.
mustang's README for information on how to use it. This post walks
through the major steps leading to this point.
The first building block for
rsix, a crate providing a
syscall-like API, but which can be configured to use different backends,
currently either libc or raw Linux syscalls.
rsix 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
rsix, 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
could be used in this way.
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,
.init_array functions, and then call
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
mustang defines custom targets named
*-mustang. These primarily
-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...).
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
std, which would be a lot of work, and a huge burden to
mustang instead defines C-compatible ABIs providing functions like
mmap, and other things used by Rust's standard library,
rsix internally to do actual system calls. These implementations are
currently very minimal, but they are enough to support Hello World, running
And going forward, it should be relatively straightforward to add additional
libc APIs as needed by
That said, the goal here is to just implement enough of libc to get
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
This code is in mustang's
Rust allows users to configure their global allocator with the
global_allocator attribute. The default global allocator ultimately
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
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
wee_alloc doesn't do fancy optimizations, but it does plug
#[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
c-scape is easily able to support.
Putting it all together
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
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
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...
mustang's README for step-by-step instructions for how to build an
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!