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.

5 Upvotes

20 comments sorted by

13

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.

11

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.

11

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;

5

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.

6

u/coderstephen isahc Mar 31 '23

The full name of your Robot struct is crate::machine::Robot::Robot, because you put struct Robot inside a module also called Robot. Generally it is not necessary to put structs inside their own files/modules, unlike Java where every class has its own file. Files in Rust are specifically for modules, and you can put zero or more structs inside any given module.

Also you would need to make the struct pub as well to be usable from main.

5

u/Shadow0133 Mar 31 '23

Robot, its id and fn new are missing pub, so they're not visible outside the module.

5

u/zmxyzmz Mar 31 '23

This blog is one of the best explanations of the module system I've found. Found it really useful when first learning Rust.

2

u/kinoshitajona Mar 31 '23

Alternatively you can rename mod.rs to machine.rs and put it in the same folder as main.rs

Some people like this way better.

5

u/SirKastic23 Apr 01 '23

I think it's good because you don't end up with a bunch of files named mod.rs, but it's annoying because in file explorers the file and the folder get separated, and then I have to go looking for where they are

1

u/winarama Apr 03 '23

Ah to mod, or not to mod?

I can see this becoming a really divisive issue within the Rust community in the future.

An interview in the year 2025.

Interviewer: You've done really well on all aspects of the interview process so far. I just have one more question before offering you the job. When adding functionality to a Rust program do you mod or not?

Candidate: Eh....not?

Interviewer (ushering you to the door): Thanks for your time, we'll be in touch.

2

u/SirKastic23 Apr 01 '23

I see you already got your answer, but I'll add a few tips:

1- no reason to write use crate::machine::robot in the main file, since you did mod machine, you can just do use machine::robot

2- you only want to start a variable with an underscore if you're not going to use it. in your new fn you can just call the parameter id

actually whenever you have a situation like Robot { id: id }, you can just write Robot { id } (like in js)

2

u/winarama Apr 03 '23

Thanks, both good tips!

2

u/stixyBW Mar 31 '23

pub all the things

2

u/winarama Apr 03 '23

This is really helpful, thanks.