Hands-on Rust: Rust newbie feedback: mod keyword (page 78)

I’m new to Rust; this is probably already well understood by others, but it tripped me up.

On page 78 (PDF) and in this block in particular:

mod map; // (1)

pub mod prelude { // (2)
    pub use bracket_lib::prelude::*; // (3)
    pub const SCREEN_WIDTH: i32 = 80; // (4)
    pub const SCREEN_HEIGHT: i32 = 50;
    pub use crate::map::*; // (5)
}

use prelude::*; // (6)

I’m left questioning whether pub mod prelude appends to the map namespace imported by the earlier mod map statement or if it sets up a new namespace specific to the current file. I will re-read this section a few more times before researching further.

Refs: http://media.pragprog.com/titles/hwrust/code/BasicDungeonCrawler/dungeon_crawl_map/src/main.rs

Sharing this feedback in case it is helpful in some way.

2 Likes

Reading back over this section again, typing out the code and reading coverage elsewhere I think I understand now. Ultimately I think I misread here.

2 Likes

The modules system is one of the topics that confuses newcomers to Rust the most, myself included when I started. It’s powerful, but often not very clear coming from other languages. It works well; it’s definitely a step up from the header system in C/C++ (the new C++ modules looks to be an improvement) - but it’s a lot less obvious than Java/C# and similar. I’ll see if I can get a better explanation into the book.

The mod x keyword tells Rust "I’m adding a module to the project, and it is named x. The namespace hierarchy is a tree. So if you type mod my_thing in main.rs you are appending it below the top level in the tree - crate::my_thing. If you were already in a module named my_thing you’ve created crate::my_thing::my_thing.

Some of the confusion comes from mod being able to do 3 things:

  • mod my_thing { // my stuff } creates a module entirely in the file you are currently writing. It’ll have the namespace of your file (crate for main.rs, crate::my_thing is you are working on a my_thing module imported into main.rs and so on).
  • mod my_thing; looks for a my_thing.rs file and loads the module content from there. It’s the same in terms of namespacing; you get the same crate::my_thing or crate::my_thing::my_thing as with declaring it inline.
  • mod my_thing; can also look for a directory named my_thing with a mod.rs file inside it (effectively acting as the main file for that module). Again, the namespacing is the same as if you’d just imported it directly in the source.

The use statement ties into this. use is an alias: you are saying "I don’t want to type legion::world::SubWorld all the time, and would much rather just type SubWorld. You can even alias with use legion::world::SubWorld as MyCleverName - but I generally don’t recommend it, because it makes reading your code pretty confusing when you come back to it a while later. On its own, use applies the alias within the scope (you can use inside a function and only have the alias apply in there, use in the top-level scope for a file applies to the whole file). Throw in pub use and you are exporting the alias from the current module scope. So if you are in crate::my_thing and pub use other_thing::Foo then anything that can access crate::my_thing can call crate::my_thing::Foo even though Foo is actually somewhere else.

So how does this tie into access and pub? Modules can access modules above them in the tree by name - but they can’t access modules beneath them or to the side of them in the tree without pub. Say you’ve built the following hierarchy (should look familiar)

crate
    prelude
    systems
        entity_render
        map_render
        (etc.)
   camera
   map
   (etc)

This gives you a few different ways to access things:

  • Anything in systems can get to prelude as crate::prelude. It can also get to it as super::prelude because “super” is “parent in the tree”. It doesn’t actually matter if prelude is public - you are going up the tree and have access (see below).
  • Down in entity_render you can still get to crate::prelude. If you want to use super, you’re stuck with the ugly super::super::prelude - because you are two levels down the tree.
  • If entity_render wants to access map_render - a sideways access - then things are trickier. You can get to crate::systems::map_render, or you can get to super::map_render. Because the access is to a “sibling”, entity_render cannot see anything that isn’t public.
  • If something in crate (main.rs) wants to access something from camera it can just use camera::. Because it’s going down the tree - it can’t access anything that camera didn’t mark as public.

Fun fact: if you remove the pub from pub mod prelude in your main.rs file, the program still compiles. I would recommend keeping the pub for two reasons:

  1. If you start writing libraries, the pub is necessary for anything that uses your library to access it. It’s a good idea to get into the habit of making your interface public and carefully controlling what it exports. (A library is just like an app, but rename main.rs to lib.rs and don’t write a main function. You can then import it via Cargo.toml with the syntax my_lib = { path = "/home/herbert/rust/my_lib" }. When your program gets really big, it’s common to start breaking things out into libraries - and this way you don’t have to remember to fixup your permissions.
  2. I think of it as a statement of intent. Marking it as public makes it really obvious that I intended to share the functionality. I think of pub as a warning flag - “this is shared, be nice!”

Anyway, I’ll see if I can clear this up a little. Pragmatic books generally don’t hit you with a huge theory dump - so I’ve tried to focus on giving a framework that works and having you use it - with some explanations along the way - and “learn by doing”. A really early draft spent 4-5 pages trying to explain this, and reducing it to short chunks with examples and gradually adding the more complex usages made things a lot easier to read. (Thanks Tammy - my editor. She did an amazing job of spotting the times I wandered into long-winded explanations like this one)

Hope that helps!

5 Likes

Anyway, I’ll see if I can clear this up a little. Pragmatic books generally don’t hit you with a huge theory dump - so I’ve tried to focus on giving a framework that works and having you use it - with some explanations along the way - and “learn by doing”. A really early draft spent 4-5 pages trying to explain this, and reducing it to short chunks with examples and gradually adding the more complex usages made things a lot easier to read. (Thanks Tammy - my editor. She did an amazing job of spotting the times I wandered into long-winded explanations like this one )

Thank you for this, this is great. The hierarchy coverage in particular helps make things a lot clearer.

It’s likely that I’m in the minority on this topic, so keeping the coverage thin makes sense; too much coverage and you lose momentum, so I understand the need to keep the focus tight.

Thanks again for walking me through this. I’m going to bookmark this thread for later re-reading as I get further into learning/using Rust.

2 Likes

Great post! I’ve bookmarked it so I can read it properly when I start reading the book :nerd_face:

1 Like