Crate::Module::Function Organization Question

I'm new to rust and I'm an intermediate programmer, at best, in general. I'm wanting to learn how to separate my code into modules and functions, which I have never done before. My current understanding is that I have a main.rs and I should use lib.rs for my project modules . Then within the modules would be public functions etc. Does this sound correct? Any other pointers or clarifications to this? Is lib.rs a required name or just the standard?

mod my_module{
    mod sub_module{
        pub fn public_function() {}
        fn private_function() {}
    }
    mod sub_mod_two{
        pub fn pub_two() {}
    }
}

main.rs is only used by binary targets, i.e. when you want to compile a crate to an executable program. The main.rs file also needs to define a main function.

lib.rs is used by library targets, i.e. you want your crate to be imported by other code.

You can only compile one of the two targets at once, however you can have one crate which you can both compile to a binary and export as a library (by just defining both [lib] and [[bin]] sections in your crate's Cargo.toml).

Depending on which target you compile to, either the main.rs (binary) or the lib.rs (library) file will define the crate module in your code, which is the module that contains all other modules. But they both work the same way, as ordinary modules.

Then, as you said, you can define modules in your crate root module (crate). You can put all modules into one file, but of course that will get cluttered quickly. that is why you can move each module into its own .rs file, or into its own directory in the src file. Then you just need to declare the modules in the root module:

// lib.rs
pub mod m1;
pub mod m2;

and define the modules seperately:

src
├── lib.rs
├── m1.rs
└── m2.rs

Rust will compile m1 and m2 and you can reference them in your code by crate::m1 for instance.

Now if you also have nested modules in m1 for example, you can define m1 in a directory with a mod.rs file in it:

src
├── lib.rs
├── m1
│   ├── mod.rs
│   └── m1_1.rs
└── m2.rs

where m1/mod.rs looks like this:

pub mod m1_1;

and m1/m1_1.rs can contain the code that should go into module crate::m1::m1_1.
I made all modules public here, but you can of course also declare private modules by omitting the pub.

2 Likes

Hi @dkhayes117!

lib.rs is a required name that makes up the entrypoint for you crate as a library. Usually what I do is I keep main.rs as minimal as absolutely possible like this:

main.rs:

fn main() {
    crate_name::run();
}

Then you have a public run() function in your `lib.rs:

lib.rs:

pub fn run() {
    // Do stuff...
}

So logically you have a teeny-tiny binary target that uses your very own library where all of the logic really is.

The starting point for you library is the lib.rs file, but it can also include other modules and it's totally up to you how you structure those. Say you were writing an interpreter, you might have a parser module, and a vm module. Inside parser you might even have an ast module. Then your project structure could look like this:

src/
├── main.rs    // Your binary
├── lib.rs     // Your library
├── parser.rs  // Your parser module
├── parser/    // The parser module folder where submodules of parser go
│   └── ast.rs // The ast submodule of the parser module: parser::ast
└── vm.rs      // Your vm module

Now you have to declare you modules in your lib.rs file:

lib.rs:

mod parser;
mod vm;

pub fn run() {
    // Run my program
}

And you have to declare your submodule in the parser.rs file:

parser.rs:

mod ast;

At that point you can import your public functions and types from the different modules using a syntax like:

use crate::parser::parse;
use crate::parser::ast::MyLanguageAst;
use crate::vm::run_bytecode;

// Now I can call the parse function
parse();

// Use the MyLanguageAst struct
let my_ast = MyLanguageAst { ... };

// etc...

Also, definitely check out the Rust book's explanation of this stuff for a more detailed explanation:

https://doc.rust-lang.org/stable/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html

Note: There are two different ways to structure submodules on the filesystem. For instance, in the tree above you have the parser.rs module, and to make ast a submodule of parser we create a folder with the same name as parser but without the .rs extension and then we put our ast.rs file in that.

The other way to structure modules was indicated by @mankinskin above where you instead create a folder with the name of your module, and you put a mod.rs file inside of the folder to make up the module:

3 Likes

Where do you put your extern crate statements in this example? In the lib.rs or the main.rs?

You don't actually need extern crate anymore, since Rust 2018 edition. You can just use normal use statements to include the modules, functions, structs, etc.

1 Like

Oh really? It's all done through the Cargo.toml? That's awesome :slight_smile:

1 Like

Yeah, I guess so. :man_shrugging: I only got into Rust after 2018 edition, so I don't know exactly how it worked before, I just know that it isn't needed anymore. :slight_smile: Nice, clean, and intuitive now.

1 Like

My project is using some inline assembly that requires nightly to compile. If I put all of my inline in my lib.rs is there anything special needed to pre-compile it so I can use stable rust? Or do you know?

Sorry, I don't know anything about inline assembly. You could open a different topic, though. Somebody else probably knows.

1 Like

In that case, the whole project will have to use nightly.

It's impossible to compile crates with different versions of the compiler and then link them together. The reason is that the Rust ABI is not stable, so there's no guarantee that the linking would succeed.

  • Well, strictly speaking, it's impossible to do with Cargo. Of course, you can try and run the final stage (compiling binary crate) with stable Rust manually - but, unless you pin the nightly version to specific date, you can't be sure that this will always succeed, even if it succeeds now.

This might be so, but I know you can pre-compile assembly in a crate and use the crate to compile a project with stable rust. I know this because I am using the riscv crate which does this, I just don't know how.

Hmm, the Ring crate might be doing that. It's written partly in assembly. I don't know, though.

This isn't a deal breaker at this point, it would be nice to use stable instead of nightly though. If I determine I really need to use stable I will open a separate topic for the assembly pre-compiling. Thanks.

1 Like

@zicklag I'm still having trouble getting my lib.rs set up correctly.

my structure

src
├── cpu.rs
├── lib.rs
├── main.rs
├── pmp.rs
└── privilege.rs

lib.rs contents

#![no_std]
#![feature(asm)]

pub mod privilege;
pub mod pmp;
pub mod cpu;

when I try to build I get

error[E0463]: can't find crate for `test`

what did I forget?

I may have fixed this with a setting inside intellij IDE

File ->settings->Languages and Frameworks->Rust->Cargo->Compile all project targets if possible=false

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.