Proper file structure and imports

I am creating my first project and I'm a little lost on how to properly structure all the files.

The file structure I currently have works, but it somehow feels wrong and I'm not sure if that's the way it is supposed to be, or if I'm doing something wrong.
The files are as follows: (not showing all code for all files to keep it short)

main.rs:

use projectname::graph::graph::Graph;

lib.rs:

pub mod graph;

graph.rs:

pub mod edge;
pub mod graph;
pub mod node;

graph/edge.rs
graph/node.rs
graph/graph.rs:

use super::edge::Edge;
use super::node::Node;

graph/routing.rs:

pub mod routing;

graph/routing/routing.rs:

use super::super::edge::Edge;
use super::super::graph::Graph;
...

My main questions are is that it feels weird to have a file that just contains some pub mod statements...

And secondly the use of super::super in graph/routing/routing.rs feels wrong

and Thirdly in main.rs it feels weird that I have to use graph::graph instead of just graph.

Am I doing something wrong or is this how it is supposed to be?

I don't see anything wrong and as you stated, your imports work. There are some things you can do to enhance your setup, though:

I wouldn't worry too much about this. If the file is too empty for you, you can always fill it with documentation :smile:

Maybe absolute paths are more to your liking? I.e. replacing super::super::edge::Edge with crate::graph::edge::Edge?

You can re-export the contents of src/graph/graph.rs in src/graph.rs. Add the following to your src/graph.rs:

mod graph;

pub use graph::*; // re-export everything from your `graph` submodule

Then in your main.rs you can import Graph with:

use projectname::graph::Graph;
3 Likes

graph/graph.rs is generally discouraged (I think clippy might warn about it by default). It's generally preferred to put the code you have in graph/graph.rs in graph.rs instead.

Otherwise it can get a little confusing which is the file defining the outer graph module.

For the most part though I agree that your structure looks fine. One thing to keep in mind is that having types in a lot of different modules can be confusing for users of a library. When there isn't an obvious reason to keep them separate, it can be better to re-export most of your types and functions at the top level[1] instead of making all the intermediate modules public.

For example, in graph.rs I would probably do

pub use edge::Edge;
pub use node::Node;
pub use graph::Graph;

  1. or just a higher level module ↩ī¸Ž

5 Likes

Confusing, or even just annoyingly verbose. In Rust, it is common but not universal style to use module/crate imports instead of individual symbols — that is, like this:

use std::fmt;

impl fmt::Debug for MyType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {

In my opinion, you should design your library so that it is comfortable to use that way — not requiring the user to import very many modules to get concise yet meaningful paths at usage sites. This may mean that your public API has significantly fewer public modules than it does private implementation modules, where the public modules might re-export items defined in the private modules.

Also, more broadly, it is not idiomatic Rust to have a module per type. Which isn't to say that you shouldn't; just that you should do it for a reason and not just because it's the default choice. In general, the time when a type has a public module for itself is when it has related items — usually other types that are closely related to it, like iterators. For example, std::collections::hash_map exists because HashMap has many iterators and Entry.

8 Likes