How most Rust projects are organized (Part 2)
2022-01-102 minute read
If you are a newcomer to Rust, you might be interested in how you should properly organize your project files and folders.
In part 1 I collected data from GitHub REST API to look for large patterns in how most Rust projects organize their files and folders. The results however, were kinda expected. So that's why I decided in this article to go for a more qualitative approach.
Instead of going through hundreds of projects, I'm going to manually look for patterns in a smaller selection of projects.
I wanted this article to include practical advice based on observations in the projects I went through.
Now let's jump into what I found out!
Where should I put my workspace members?
The cargo workspace feature allows you to separate your code into smaller crates, instead of having them as modules in a single crate.
If you want to use this feature, I suggest you create a top level crates
folder and put your workspace members inside here. This way, you could include the following entry in Cargo.toml:
# Cargo.toml
# ...other stuff hidden for brevity
[workspace]
members = ["crates/*"]
That said, there is nothing wrong about organizing this differently.
Let's look at three of the most popular game engines written in Rust. They have each decided to put the workspace members different places:
- Bevy have a root level
crates
folder that all but theerrors
crate resides in (this is also what the workspace RFC1525 suggests). Amethyst
have these in its project root folder, all prefixed withamethyst_
- Piston chose to put this into subfolders inside
src
I don't see any practical differences in how you organize your workspace members into folders. I would however appreciate some sort of suggested default, so this becomes more recognizable at a glance, instead of having to open Cargo.toml
. It would be cool if cargo new
got an option --member
or something that automatically patched Cargo.toml
and created a subfolder crates
with the new member crate inside it. Like this:
$ tree
.
├── Cargo.toml
├── src
│ └── main.rs
└── crates
├── project-core
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── project-log
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
└── project-ui
├── Cargo.toml
└── src
└── lib.rs
8 directories, 9 files
There's already an issue#6378 suggesting a solution to this.
Where should I put examples on how to use my project?
I would suggest you create a root level examples
folder and add entries to Cargo.toml
, so they can be executed with cargo run --example hello_world
, like this:
# Cargo.toml
# ...other stuff hidden for brevity
[[example]]
name = "hello_world"
path = "examples/hello_world.rs"
If you do not have any examples, I would encourage you to create some. They're super helpful for learning about you project quickly.
Eight of the projects I went through had a top level examples
folder. Some projects (e.g. Macroquad) have this in a separate repo and links to this from the README.md instead.
Having examples is obviously more common for library type of projects, and less for binaries like games, command line utils, etc.
Where should I put my custom error types?
A very popular pattern is to have a error.rs
file which defines a custom enum. Here's an example of such enum, copied from the source code of Veloren server:
// server/src/error.rs
pub enum Error {
NetworkErr(NetworkError),
ParticipantErr(ParticipantError),
StreamErr(StreamError),
DatabaseErr(rusqlite::Error),
PersistenceErr(PersistenceError),
Other(String),
}
In part 1 I found that 23% of Rust projects on GitHub have a error.rs
file in it's src
folder. This time, when I manually inspected the projects I found this file in more than half of the projects. It's not always inside the src
folder because of cargo workspaces and custom folder structures. Some projects put them inside a subfolder src/error/
.
Bevy had this unique way of putting error codes in the form of unit-like structsinside a errors
folder. Each error code (e.g. B0001) have its own markdown file explaining the error.
Quick tips for managing growing projects
When looking through the projects, I also observed some nice tools and techniques that I figured would be nice to share.
Tip no. 1: Use cargo husky
to make sure cargo test
, cargo clippy
and cargo fmt
is done before commits are pushed. It will prevent your git history from containing commits like "Fix formatting", "Fix broken unit test" etc. because someone forgot to run these commands before merging.
Tip no. 2: Use cargo-deny
to prevent unwanted dependencies to creep into your project. You may want to do this to keep known vulnerabilities or unwanted licensing out of your project. This will involve creating a file deny.toml
in your project, containing this blacklist of crates. Bevy does this in case you want an example.
Tip no. 3: Use rustfmt.toml
to ensure a coherent format across you project. This is kind of the same as using a .editorconfig
file, but it has more features as it's configuring cargo fmt
. Used together with cargo husky
, you'll ensure a clean, coherent code style across all source code.
Tip no. 4: Add [[bench]]
entries to your Cargo.toml
to make it easy to run benchmarks.
The list of projects I looked at
This is how I made the list:
- I went on GitHub and searched using the query
language:Rust
. - I checked out the categories on crates.io
- I included the few open source Rust games on itch.io
- I found a great list of command line utilities written in Rust on another tech blog zaiste.net. Kudos to Jakub for making such a great list!
I'll recommend people to manually look at the projects themselves. It is quite difficult to convey the insight I gained in an entertaining manner.
Repo | Category |
---|---|
Bevy | Game engine |
Amethyst | Game engine |
Piston | Game engine |
ggez | Game engine |
Macroquad | Game engine |
Actix Web | Web server |
Rocket | Web server |
Rouille | Web server |
Rust-Postgres | Database library |
Rusqlite | Database library |
Mongo Rust Driver | Database library |
Rust Mysql Simple | Database library |
NES Oxidized | Emulator |
Zinc64 | Emulator |
Epicinium | Game |
Veloren | Game |
Unflock | Game |
FishFight | Game |
bat | Command line util |
exa | Command line util |
fd | Command line util |
procs | Command line util |
sd | Command line util |
dust | Command line util |
ripgrep | Command line util |
tokei | Command line util |
hyperfine | Command line util |
ytop | Command line util |
tealdeer | Command line util |
bandwhich | Command line util |
grex | Command line util |
rmesg | Command line util |
zoxide | Command line util |
delta | Command line util |
nushell | Command line util |
Hey, I'm Magnus, a developer from Norway.
I'm currently employed at Fink AS.
Hey, I'm Magnus, a developer from Norway.
I'm currently employed at Fink AS.
I also write about technical stuff in general
Newtype or newtrait? Ways around the orphan rule
2024-10-203 minute read
Can we work around the orphan rule in Rust using traits instead of newtypes?
I made an AI chatbot answering questions for employees at our company
2023-10-219 minute read
In this article, I'll explain to you how I used OpenAI embeddings and completions API to implement an AI chatbot answering questions for employees at our company. The code examples I'll show are written in Typescript, but the same principles apply to any programming language.
Kunstig Humor – Improv theater meets AI
2023-10-184 minute read
This fall, I've been assisting the comedy group Vrøvl in setting up a show where improvisers and the audience interact with AI live on stage.
My first stab at 3D game development
2023-08-233 minute read
I wanted to learn about 3D game development, so I set out on a small project inspired by a friend of mine who lives on a farm. His name is Gunnar and he lives on a farm called Steinseth Gård.
How most Rust projects are organized
2022-01-072 minute read
I collected data from GitHub.com to see what resides in most Rust projects src
folder.
I implemented Twitter in the woods using military radios
2018-02-0415 minute read
In this project I implemented a very simple Twitter-like application for use in networks with very low bandwidth and high packet loss rate.