Why do I need to use crate:: to access modules in Rust?

I'm reorganising my Rust project into modules, and I have a question about how module paths work. When I structure my project so that each module is located in a different directory, I usually add a mod.rs file in that directory to declare the modules it contains.

However, when I create several modules and need to use one of them from another location, I end up having to write use statements with long or complex paths, often starting with crate::. This makes me wonder why this is necessary and whether there is a cleaner or more idiomatic way to handle it.

Here is an example of my project structure:

src/
β”œβ”€β”€ main.rs
β”œβ”€β”€ models/
β”‚   β”œβ”€β”€ mod.rs
β”‚   β”œβ”€β”€ model_task.rs
└── util/
    β”œβ”€β”€ mod.rs
    └── enum_task.rs
use crate::util::enum_task::StatusTaks;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug)]
pub struct Todo {
    pub id: usize,
    pub description: String,
    pub status: StatusTaks,
    pub create_at: DateTime<Utc>,
    pub update_at: DateTime<Utc>,
}
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub enum StatusTaks {
    Todo,
    InProgress,
    Done,
}

My question is: why do I need to use crate:: to access modules from other parts of my project? Is this the idiomatic way in Rust, or are there better practices to avoid long and repetitive paths?

I end up having to write use statements with long or complex paths

What is the problem with that that you're trying to solve? A lot to type? Let your IDE handle it.

The crate:: prefix is needed to avoid local module names overlapping with crate names.

Sometimes you can avoid long fully qualified paths by using super:foo or by referring to reexports rather than the original definition. But that also creates indirection which can make refactoring more tedious.

Why do I need to use crate:: to access modules in Rust?

A path that does not start with crate:: (or super::, self::, or ::) can refer to items that are either:

  • An item explicitly defined in the current module (or block scope)
  • Other crates (which are imported globally)

If items in the crate root were also available without qualification in every child module, that would create a lot of new potential for name collisions.

This makes me wonder why this is necessary and whether there is a cleaner or more idiomatic way to handle it.

If you wish to achieve shorter paths, I would recommend that you consider using re-exports. For example, I assume that in util/mod.rs you currently have:

pub mod enum_task;

Consider whether you would like to change this to:

mod enum_task; // not public!
pub use enum_task::StatusTaks;

This way, every use of StatusTaks in the rest of the program not only can, but must, refer to it as util::StatusTaks rather than util::enum_task::StatusTaks.

Of course, if the module enum_task is a desirable grouping from an API surface perspective and not just a code organization perspective, then this is not an improvement.

5 Likes

You coincidentally answered my long forgotten question months ago, which I did not care or even think of all this time because I got too used to the more tradtitional method which is simply just pub mod module_name;, so you have my thanks!

1 Like

Thank you very much. You've answered many of my questions and helped me understand a lot of key concepts. Thank you for the solutionβ€”it's exactly what I was looking for.

It is up to you how to organise things, but I tend to have most of types used in a crate in the crate root, and then the sub-modules import them from crate root. So a sub-module will start

import crate::{ X, Y, Z …. };

or

import crate::*;

This can help if you move a type from one module to another, and avoids having to specify a lot of module names.