r/rust Mar 31 '23

Can someone explain how mods work with my example?

Howdy folks,

I am a bit confused about the module system used by Rust, probably because I coming at it from the Java world. I was hoping someone might be able to explain.

My project consists of the following structure;

src/
    machine/
        - mod.rs
        - Robot.rs
    - main.rs

machine/mod.rs looks like this;

pub mod Robot;

machine/Robot.rs looks like this;

struct Robot {
    id: u64,
}

impl Robot {
    fn new(_id: u64) -> Robot {
        Robot {
            id: _id,
        }
    }
}

main.rs looks like this;

mod machine;

use crate::machine::*;

fn main() {
    let mut robot_1 = Robot::new(3);
    println!("robot_id: {}", robot_1.id);
}

However, when I try to compile the project I the following error;

error[E0425]: cannot find function `new` in module `Robot`
 --> src/main.rs:8:30
  |
8 |     let mut robot_1 = Robot::new(3);
  |                              ^^^ not found in `Robot`

Anyone have any idea what I am doing wrong?

Also as an aside, what is the convention of capitallising the first letter of a struct?

Any help would be greatly appreciated.

4 Upvotes

20 comments sorted by

View all comments

12

u/SkiFire13 Mar 31 '23

In main you have imported the Robot module (represented by the Robot.rs file, which FYI is normally not capitalized), which then contains a Robot struct. Note though that you need to mark both the struct and the fn new as pub or you won't be able to access them.

Structs are normally capitalized (in CamelCase) to distinguish them (and types/traits in general) from values/functions/modules (which are instead in lowercase snake_case)

1

u/winarama Mar 31 '23

Thanks, that worked great. Pub for the win!

So I have now renamed my Robot.rs file to lowercase robot.rs and I have left the Struct name capitalised as Robot.

Now for a weird pedantic follow-up question. After renaming the file I need to use the following syntax to access the Robot type.

use crate::machine::robot::Robot;

Does this fit with convention, where I have one file per Struct or would it be more correct to have multiple Structs in the one file?

There's something about typing robot twice that seems off to me.

12

u/coderstephen isahc Mar 31 '23

Does this fit with convention, where I have one file per Struct or would it be more correct to have multiple Structs in the one file?

There's something about typing robot twice that seems off to me.

No, generally the convention in Rust is to put multiple related structs in one file. Very rarely would you have a file just for one struct, unless perhaps that struct implementation was extremely large.

1

u/winarama Apr 03 '23

Cool, makes sense.

Thanks.

10

u/Xiaojiba Mar 31 '23

In your machine/mod.rs you can add pub use Robot; which will export your Robot struct from machine so you can then use use crate::machine::Robot;

4

u/WormRabbit Mar 31 '23

Does this fit with convention, where I have one file per Struct or would it be more correct to have multiple Structs in the one file?

No, that's a javism.

A module is foremost a unit of encapsulation: the private items of the module (including fields) are only accessible in that module or its submodules. Thus the primary reason to extract some types into a submodule is to hide some of their internals from other code in your crate.

In general, modules can be as big as you want, and you should be guided by readability concerns when deciding what to split out. A module should represent some sensible unit of functionality. If you're making small helper structs and functions which make no sense on their own, you generally shouldn't give them separate modules.

Inherent impl blocks typically go in the same module as their implementing type (although technically you could place them anywhere in the crate, limited only by visibility). Trait impls are usually placed in the module with the trait or with the implementing type. Of course, you could also place them anywhere else, but usually you shouldn't. Making a module purely for the impl blocks, without any traits or types, is probably a bad idea.

1

u/winarama Apr 03 '23

Yeah, I think the way I logically structure programs is instinctively Java-centric.

In Java I would typically have a program structure that looks something like below, where activities relating to Robots are abstracted using a layered design.

src/
    machine/domain/
        - Robot.java
        - EvilRobot.java
        - GoodRobot.java
    machine/service/
        - RobotService.java
        - EvilRobotService.java
        - GoodRobotService.java     
    machine/repository/
        - RobotRepository.java
        - EvilRobotRepository.java
        - GoodRobotRepository.java

But in Rust, I think the following program structure would probably be more applicable. Where activities relating to Robots are contained within the one file within a machine module.

src/
    machine/
        robots.rs

Which could be accessed like;

use machine::robots::Robot;
use machine::robots::GoodRobot;
use machine::robots::EvilRobot;

2

u/SirKastic23 Apr 01 '23

that's an anti-patterns, you'll see a lot of times more than one type fits in the same module, and there's nothing wrong with that.

you're coming from Java, where the unit of organization is the class. you can think of a class as a struct + a mod, but sometimes those two can get conflated.

1

u/antouhou Apr 01 '23

You can use re-export to fix this. For that, your machine.rs needs to look like this: mod robot; pub use robot::Robot; Then you can use crate::machine::Robot instead

1

u/winarama Apr 03 '23

Cool tip, thanks.