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 the errorscrate resides in (this is also what the workspace RFC1525 suggests).
  • Amethyst have these in its project root folder, all prefixed with amethyst_
  • 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 testcargo 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.

RepoCategory
BevyGame engine
AmethystGame engine
PistonGame engine
ggezGame engine
MacroquadGame engine
Actix WebWeb server
RocketWeb server
RouilleWeb server
Rust-PostgresDatabase library
RusqliteDatabase library
Mongo Rust DriverDatabase library
Rust Mysql SimpleDatabase library
NES OxidizedEmulator
Zinc64Emulator
EpiciniumGame
VelorenGame
UnflockGame
FishFightGame
batCommand line util
exaCommand line util
fdCommand line util
procsCommand line util
sdCommand line util
dustCommand line util
ripgrepCommand line util
tokeiCommand line util
hyperfineCommand line util
ytopCommand line util
tealdeerCommand line util
bandwhichCommand line util
grexCommand line util
rmesgCommand line util
zoxideCommand line util
deltaCommand line util
nushellCommand line util

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?

Read more

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.

Read more

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.

Read more

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.

Read more

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.

Read more

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.

Read more