Search code examples
filerustmodulerust-crates

How do I split my Rust program into many files?


I'm trying some Rust, and my program is getting somewhat long. I now want to split my program into many files -- how do I most easily do this?

The Rust book chapter I get when trying to Google this has many pages about packages, crates, and modules, and seems to never get to the point -- isn't there a simpler way to do this?


Solution

  • When you start writing Rust, you get going for a bit, and then your main.rs or lib.rs gets a little long, and you want to split it into many files. However, the Rust book starts talking about crates and modules and packages and spends pages and pages on things like "inline modules" which don't help you at all, and most tutorials on the web then seem to repeat this same appraoch.

    But, you are an experienced programmer. You've shipped some code in Java and C++ and Javascript and maybe Haskell or Erlang or ML if you're lucky. All you want to do is split main.rs into multiple files and keep going. What do you do?

    Cheat Sheet

    Your src/ directory with its main.rs or lib.rs file is called a crate. A crate is the translation unit for the Rust compiler. However, "modules" are something smaller than a crate. In general, each source file is its own module, and they all need to be referenced from the main file (main.rs for programs, lib.rs for libraries.) Your Cargo.toml file doesn't care about this -- it is all defined from the main file.

    Each file that's a peer of the main file is a module of its own. You need to reference/define it as part of the crate by using the mod filename; construct (don't include the .rs bit in the module name.) You then reference symbols inside the file with the module name as prefix: let x = filename::function();. Finally, all symbols (types, functions, even fields within structs) need to be made public with pub to be visible outside the module.

    Simple Example

    // main.rs
    
    mod somefile;
    
    fn main() {
        let mut x = somefile::make_thing();
        x.value = 3;
    }
    
    
    // somefile.rs
    
    pub struct Thing {
        pub value : usize;
        pub other : f32;
    }
    
    pub fn make_thing() -> Thing {
        return Thing {
            value : 3,
            other: 0.1415,
        };
    }
    
    
    Directory structure:
    
    myprogram/
        Cargo.toml
        src/
            main.rs
            somefile.rs
    

    Other Helpful Advice

    After defining the sibling module as used from main, with mod somefile; you can also use use to import names into your local namespace. use is only about namespacing/aliasing, though, it doesn't by itself import some other file/module. If you want, you can use somefile::*; to avoid having to add the somefile:: prefix, but you still need to add the pub declarations for all symbols you want to use. Personally, I've come to prefer the explicit module name, and then use shorter function names within the module itself to compensate, because risk of collision is smaller.

    Modules can also be named starting from the top of the crate, using use crate::path::modulename; which is helpful especially because modules can have paths (which are separated by double colons) based on subdirectories and/or inner module declarations inline in the files.

    Hopefully this was concise but enough to keep you going. This snippet is not the entire story -- now you can keep going a little further, until you need the full power of aliasing and module/crate separation, at which point, hopefully the Rust book will keep you going further!