no_std Rust with bin and lib
Splitting Rust programs into a binary and a library is a common pattern. The binary has a main() entry point, and the library typically has a run() function. This post will cover setting up a project that uses this pattern, including multiple binaries in one project. The post will then convert the project to no_std Rust. no_std Rust is used for embedded systems development, and getting a working no_std project template may entail a few more headaches than someone getting started wants to deal with.
Software Versions
Instructions
std Project with a Binary and Library
First, create a new project with cargo.
Optionally, test the project.
Next, copy main.rs to lib.rs.
In lib.rs, rename main to run and make some boilerplate changes so the library run() function can be called as if it were a C function.
src/lib.rs
Call the run() function in main.rs,
src/main.rs
Next, edit Cargo.toml and specify that the project contains a binary and a library that can be built.
Cargo.toml
Finally, build and run to make sure that the project works. If the project was tested before changes were made, the output should be the same.
That is all there is to splitting a std Rust project into a binary and library.
Multiple Binaries
A project can contain multiple binaries, but only one library. If multiple binaries exist, default-run will need to be defined for cargo run to work.
Cargo.toml
Alternatively, the –bin flag can be used to specify which binary to run.
The extra main files will not be needed in the next steps.
no_std Rust
First, convert lib.rs to no_std Rust that relies on libc to write to stdout. Feel free to later remove the libc dependency if it does not make sense for your real project, but the terminal output is very handy for making sure the project template works.
src/lib.rs
Add libc to the dependencies in Cargo.toml.
Cargo.toml
At this point, the std Rust main() function should be able to call the no_std Rust run() function.
The next step is to convert main() to no_std Rust.
src/main.rs
Specify a panic setting in Cargo.toml.
The no_std template is complete. The no_std main() function is now calling the no_std run() function!
Removing the libc Dependency
Remove the libc-related code from src/lib.rs.
src/lib.rs
Also, remove the libc dependency from Cargo.toml.
Cargo.toml
If you try building and running, an error will be generated because macOS expects binaries to be linked to libc. To build the binary, extra parameters will need to be passed to the linker. Also, cargo needs to explicitly be told to build the binary. See this article for more information.
The library does not have an entry point, and there is only one in any given project, so fewer parameters need to be passed to the linker. cargo still needs to explicitly be told to build the library.
Testing the program gives the following results.
Something is clearly wrong. A bare metal no_std program was compiled and run on an operating system. The start entry point typically does things that the operating system assumes are being handled, like initializing the stack. In practice, a no_std Rust program that does not rely on libc will probably be running on an embedded device without an operating system to kill it. In any case, where this template goes is beyond the scope of this post.
More Information
The Rust Embedded Workgroup has produced useful documentation that is useful for programmers who need to use no_std Rust. The Embedonomicon and the Embedded Rust Book are two core resources.