Tidy up your Rust imports with a prelude.rs

06/08/2022

I love Rust. Seriously, it's a really amazing language, it has its flaws but all around it's pretty good. But one thing that drives me crazy is having to import everything under the sun in every single file. And you may or may not have had this experience, I didn't until I tried building a web API in Rust.

Here's an example of what I mean...

use crate::models::clubs_md::{Club, NewClub, ClubDetails};
use crate::models::users_md::{User, UserDetails, NewUser, Admin};
use crate::models::club_members_md::{MembershipStatus, ClubMember, NewClubMember};
use crate::Db;

In my case I started off trying to build a relatively simple web application and before I knew it I had 20 different types and certain web handlers needed to access most of these types. And assuming this was one handler file that might be okay, but when you have your handlers separated into multiple files it just seems obnoxious.

Unlike C# or Java namespaces basically don't exist

In Java and C#, namespaces are treated such that anything living in a file in a namespace is visible to other files in that namespace. This is why C# and Java don't have mod.rs files and they also don't need to use use super::other_child::other_function in order to guarantee the visibility of adjacent files. As a result, you can't assume that because you import a module that all of its child modules will be imported. You need to import it all manually and that can get messy.

Where prelude.rs comes in

I've found two very different useful way to utilize a prelude.rs in a project.

  1. For small projects a simple crate level prelude.rs that contains most the imports for your project.
  2. For larger projects writing prelude.rs files for crate submodules can be particularly useful, for example when grouping and building web handlers.

It's worth noting that these two ideas can be combined and may be practical for a sufficiently large enough and segmented project. For example, a library where you have want to have several re-exports available for inclusion in other projects but you still want to hide the verbosity of Rust imports in every single file. Either way the process of adapting existing code to use this prelude.rs file will look relatively similar no matter the reason or exact implementation.

  1. Simply take all of your imports in your given file or files.
  2. Place those imports into an adjacent prelude.rs with pub use rather than use
  3. Make sure your prelude is visible in your module tree.
  4. Finally, in each file that needs all those imports just replace it with use super::prelude::*; or use crate::prelude::*; if your prelude is crate-level.

Some example file structures

Here is an example file tree where you would use use super::prelude::*;.

- main.rs
- features
	- mod.rs
	- feature1.rs
	- feature2.rs
	- feature3.rs
	- prelude.rs

And here is an example of a file tree where you would use use crate::prelude::*;;

- main.rs
- prelude.rs
- some_other_feature.rs
- related_features.rs
	- mod.rs
	- feature1.rs
	- feature2.rs
	- feature3.rs

Conclusion

So whether you are simply tidying up a really massive import in one file or trying make a reusable import for multiple files in a submodule a prelude.rs is really useful.

This has personally made it easier for me to maintain my imports in my projects and I hope this tip will help you too! 😁