I am confused with modules

Referring to the doc: Managing Growing Projects with Packages, Crates, and Modules - The Rust Programming Language

First, I cannot fully understand the definition of the module Modules and use: Let you control the organization, scope, and privacy of paths. In my understanding the modules is something like namespace in other languages.

Second, If the first statement is true, how then I can organize my code bellow:
image

main.rs:

fn main() {
    entities::apple();
    entities::pear();
}

// or
use entities;

fn main() {
    apple();
    pear();
}

apple.rs:

pub fn apple() {
    println!("Hello, apple!");
}

pear.rs:

pub fn pear() {
    println!("Hello, pear!");
}

I tried mod.rs but it makes my functions like (which is wrong):

fn main() {
    entities::apple::apple();
    entities::pear::pear();
}

Any solution for this?

You can re-export items to get the module layout how you want it

main.rs

mod entities;

fn main() {
    entities::apple();
    entities::pear();
}

entities.rs (or entities/mod.rs in the old module layout. They're equivalent)

mod apple;
pub use apple::apple;

mod pear;
pub use pear::pear;

entities/apple.rs

pub fn apple() {
    println!("Hello, apple!");
}

entities/pear.rs

pub fn pear() {
    println!("Hello, pear!");
}

Looks overcomplicated to me, but thanks for the solution :+1:
So literally I have to re-use every single struct, fn, trait in order to achieve my concept.

You don't need to re-export everything in order to use them. It's just that re-exporting gives you the flexibility to structure your public API the way you want.

For example, I might want to split my code into multiple smaller modules because it makes maintenance and code navigation easier, but I don't want my end users needing to write out the full path or know about my internal modules because then I'd be making a breaking change any time I want to move a type to another module.

For example, let's look at the scad-syntax crate from my scad-rs project:

Here, I want my end users to just write scad_syntax::tokenize() and scad_syntax::parse() instead of calling the more verbose scad_syntax::lexer::tokenize() or scad_syntax::grammar::top_level::parse().

Modules are namespaces like you see in other languages, it's just that you can use privacy and re-exports to write code in a way that suits you while also exposing an API that is less confusing for the end user.

2 Likes

FYI, you can do this:

// main.rs
mod entity {
    pub mod apple;
    pub mod pear;
    // do the re-arraging renaming etc if needed here
}

fn main() {
    entity::apple::run();
    entity::pear::run();
}

With this setup:

src
├── entity
│   ├── apple.rs
│   └── pear.rs
└── main.rs

Thanks @Michael-F-Bryan and @erelde , your examples made things clear to me. :+1:

I encountered an issue when I added a struct.
Please see the code bellow:
image

orange.rs:

pub struct orange {
    name: String,
}

impl orange {
    pub fn new() -> Self {
        return orange {
            name: String::from("orange"),
        };
    }

    pub fn print(&self) {
        println!("Hello, {}!", self.name);
    }
}

mod.rs:

mod apple;
pub use apple::apple;

mod pear;
pub use pear::pear;

mod orange;
pub use orange::orange; // compile error: `orange` must be defined only once in the type namespace of this module

main.rs:

mod entities;

fn main() {
    entities::apple();
    entities::pear();
    entities::orange::new("my orange").print(); // compile error: module `orange` is private
}

Looks like I have a conflict of names or something. I cannot understand why it is wrong?

The mod orange binds your orange.rs module to the name orange, but then you also do pub use orange::orange to bring the orange struct into scope. Rust's namespaces are set up such that you can't use the same name for two paths (types, modules, etc.) at the same time.

Incidentally, the compiler should have warned you that structs in Rust are PascalCase, which means you would have called the struct Orange and not run into this issue.

3 Likes

One thing I'd suggest here is to re-question the "every struct should be its own file" that you're probably adopting from other languages.

If you have a bunch of related types and don't need a privacy boundary between them, then just put them in the same file! For example, it's totally ok to put a container type and its three iterator types in the same file. Or if you have a Point and a Vector type, with the usual Point - Point = Vector, Point + Vector = Vector, etc things, they're so related that they probably make sense to be exposed in the same module and plausibly written in the same file. (After all, it's otherwise hard to decide where to put the mixed-type ops trait implementations.)

4 Likes

Nice one :love_you_gesture:

The following code started to work:

orange.rs:

pub struct Orange {
    name: String,
}

impl Orange {
    pub fn new() -> Self {
        return Orange {
            name: String::from("orange"),
        };
    }

    pub fn print(&self) {
        println!("Hello, {}!", self.name);
    }
}

mod.rs:

mod apple;
pub use apple::apple;

mod pear;
pub use pear::pear;

mod orange;
pub use orange::Orange;

main.rs:

mod entities;

fn main() {
    entities::apple();
    entities::pear();
    entities::Orange::new().print();
}

Return:

c:\Code\rust\mod-test>cargo run
   Compiling mod-test v0.1.0 (C:\Code\rust\mod-test)
    Finished dev [unoptimized + debuginfo] target(s) in 0.57s
     Running `target\debug\mod-test.exe`
Hello, apple!
Hello, pear!
Hello, orange!
1 Like

Point - Point = Vector, Point + Vector = Vector

Sorry I don't understand why do I need those Points and Vectors.


It's gonna be db entities (used by sea-orm), so I assume every entity should be in a single file.
Also there will be another crate with services. Service is mostly a struct with some functions (create/update/delete). Also I am going to use more-di for dependency injection of the services.

And it will expose the API (services) to external applications.

I would consider to hide db entities (Orange entity) from user (no pub). And instead use models (DTOs) and make them public. Similar models actually can be placed in one single file because they don't contain any logic, they are just DTOs (CreateOrangeModel, UpdateOrangeColorModel, MoveOrangeToAnotherBasketModel, etc.). Eg.:

use models; // OrangeModel, CreateOrangeModel etc.
use entities; // Orange
use services::core; // DbService

pub struct OrangeService {
    db_service: &DbService,
}

impl OrangeService {
    pub fn new(db_service: &DbService) -> Self {
        return OrangeService {
            db_service,
        };
    }
    
    pub fn list() -> Vector<OrangeModel> {
        return db_service.list<Orange>().map<OrangeModel>();
    }

    pub fn create(model: &CreateOrangeModel) -> Result<Validation, Err> {
        // example with model validation (user error)
        if model.name.length == 0
            return Validation::new("Sorry, name is required.");
        let entity = Orange::new(model.name);
        db_service.save(entity);
        return Validation::success();
    }

    pub fn update_color(model: &UpdateOrangeColorModel) {
        let entity = db_service.get<Orange>(model.id);
        entity.changeColor(model.color);
        db_service.save(entity);
    }

    pub fn move_to_basket(model: &MoveOrangeToAnotherBasketModel) {
        let entity = db_service.get<Orange>(model.id);
        let basket = db_service.get<Basket>(model.basket_id);
        entity.moveTo(basket); // low entity level functional logic, which validates the state of the basket and applies a new `basket_id` to the db entity if the state is `current`. so it means entities could have some functions as well (not just pure struct with properties). If the logic related to the BBL rather then to the entity itself, it will be as a private method in the orange service, or another separated service `OrangeValidatorService`.
        db_service.save(entity);
    }
}

So everyone can get a required service and consume it:

use my_library::models;
use my_library::services;

var orange_service = service_provider.get<OrangeService>();
orange_service.update_color(UpdateOrangeColorModel::new(id: selected_orange_id, color: "red"));

The only modules make me uncomfortable, instead on focusing on the business logic I have to think about all of those mod and use. I hope I can get used to it.

Thanks anyway guys.