Assistance structuring program into files and directories

Hello!

I am new to rust. I have read the relevant section of the crab book several times, but still cannot figure this out. I want to separate my program into multiple files. This seems really straightforward and simple, but I have have been really struggling with this and trying many things. I think that this is what I want.

I want to have /main.rs for the main logic of the program.

I want to have a module that contains functions used by the program, where those functions are not attached to any struct. I would like to call this module wsl. I assume that this would be /wsl.rs, /lib.rs, /wsl/wsl.rs, /wsl/lib.rs, or something along those lines. I have found at least one variant that works for this, so then I can call my method as wsl::function().

To me logically, the structs used by that wsl module belong in a subdirectory related to that module name. For example, if the methods are in /wsl/lib.rs, then the types would go in /wsl/types/.rs, or /wsl/.rs, or something like that. The module and the program should be able to use these structs. No matter what I do, rust cannot seem to find these structs.

So I want to have a wsl:: path with methods and use wsl::typename or wsl::scope::typename for my types.

I am looking for suggestions for:

-whether I am thinking about this wrongly
-where to put the files and what to name them
-specific code to put in each file to make this work

It all works fine if I put everything in the same file or separate files in a directory without nesting, but I do not consider that to be very good practice, although rust seems to suggest this approach. I do not intend to complain, and in fact I like many things about rust, but some of these little details are incredibly frustrating and the examples do not provide great coverage of what I consider to be some of the most common use cases for programmers, such as this one. Also, I may be misinterpreting, but it seems odd, confusing, and risky that rust appears to use the file path to determine module names rather than using anything in the code itself. I would like to specify my module names in code, whether they match file paths or not.

Thanks & regards,

-Roberto

By default, Rust will only ever read the root source file (main.rs). Any other file to be read needs to be mentioned in exactly one mod statement, which defines a module with the contents of that file. A mod wsl; statement inside main.rs, for example, tells the compiler to read wsl.rs as the module crate::wsl.

Type names are always relative to the current module unless they start with :: or crate::, and you can use super:: to move up a level (like .. in a filesystem). As this can produce some over-long names; use statements let you register a local alias for particular modules or types.


// main.rs
mod wsl;  //defines module crate::wsl, read from wsl.rs

pub struct InMain;

fn example() {
    let _ = InMain;
    let _ = wsl::InWsl;
    let _ = wsl::scope::InWslScope;
}
// wsl.rs
mod scope;  //defines module crate::wsl::scope, read from wsl/scope.rs

pub struct InWsl;

fn examples() {
    let _ = crate::InMain;
    let _ = super::InMain;
    let _ = InWsl;
    let _ = scope::InWslScope;
}
// wsl/scope.rs
pub struct InWslScope;

fn examples() {
    let _ = crate::InMain;
    let _ = super::super::InMain;
    let _ = super::InWsl;
    let _ = crate::wsl::InWsl;
    let _ = InWslScope;
}

NB: There are several possible source files the compiler will look for when it finds a mod statement. I've described the one I use, which I believe is current best practice.

4 Likes

Thank you! This is very helpful, I think much more clear than the book for this specific common use case. I will give it a try and report results.

1 Like

I ave seen this question come up so many times in my first year of Rust. It confused me when I started out. I still don't know how to put what where when I want to do it on a new project. Always have to crib from what I have done before.

I'm sure the module/file system is just fine, but somehow the story of how it all works in the documentation is not working for many people.

1 Like

I don't know if this is best (maybe it is too explicit?), but it works. I was able to eliminate scope and put everything in the wsl path, which is preferable for me. I am open to specific suggestions for improving this and/or my understanding.

These are my files:

/main.rs
/wsl/invocable.rs
/wsl/invocablecategory.rs
/wsl/invocablecategorylist.rs
/wsl/invoker.rs
/wsl.rs

/main.rs // uses functions in /wsl.rs and types in /wsl/invocable.rs, /wsl/invocablecategory.rs, and /wsl/invocablecategorylist.rs.

mod wsl;
use crate::wsl::invocablecategory::InvocableCategory;
use crate::wsl::invocablecategorylist::InvocableCategoryList;
use crate::wsl::invoker::Invoker;

/wsl.rs // uses these four "nested" types in corresponding files /wsl/*.rs

pub mod invocable;
pub mod invocablecategory;
pub mod invocablecategorylist;
pub mod invoker;

/wsl/invocable.rs

No changes

/wsl/invocablecategory.rs

use crate::wsl::invocable::Invocable;

/wsl/invocablecategorylist.rs // uses one "nested type" and methods from /wsl.rs

use crate::wsl::invocablecategory::InvocableCategory;
use crate::wsl::get_config_file_path;

/wsl/invoker.rs

No changes

I think that cargo should definitely have a "new" template for a structure for this. I will definitely be cutting and pasting rather than trying to understand how it works.

2 Likes

Aside: Use triple backticks to get code formatting:

```
like this
```

Some of the more advanced forms of use might make your setup a little more ergonomic:

// in wsl.rs:
pub use invocablecategory::InvocableCategory;
pub use invocablecategorylist::InvocableCategoryList;
pub use invoker::Invoker;
    // etc...

// in main.rs:
use wsl::{
    InvocableCategory,
    InvocableCategoryList,
    Invoker
};

@2e71828 That approach did not work for me; I just got various compile errors (use of undeclared crate or module wsl, etc.) on everything I tried. The only variants on this approach that worked seemed to make the code longer.

Code archive here:

deliverystack/wink: wink rust command line tool to access Windows applications and features from cmd.exe and WSL bash.exe shells (github.com)

You need mod statements anyway, this is independent on whether you have any use statements or not.

1 Like

If you would like a clear explanation as to how modules and files and crates hang together here is a great Rust video tutorial episode on it all: Rust's Module System Explained! - YouTube

Clear as mud, that is :slight_smile:

Thanks for the link, there were some pointers there, but I think my issue may actually be with the way rust manages modules as files (though I think a better example in the book would help).

Thanks to everyone for all the help - I'm not sure how I learned to program before the Internet.

Please correct my misunderstandings. To summarize, I think that I cannot actually achieve what I want - to store separate types for a module in separate files.

I think I may have determined one major source for my confusion related to project structure, and I think the source is related to 20+ years with C# (similar to my initial inability to end a function with an expression rather than a statement; bare expressions are a bit visually jarring at first and result in compiler warnings when you include return or semicolon, but then you embrace them).

For me, the key to understanding rust project structure may have been the awareness that a rust module (.NET namespace?) corresponds directly to an .rs file, and an .rs file corresponds directly to a Rust module. This approach to file/scope management is different from languages where it is more common for a file to contain a single type. In Java and C#, a file (typically) corresponds to an individual type and a namespace (specified in code rather than by file path, though typically corresponding) can include multiple types implemented in any number of files. Those languages are older and more common in school and industry than rust. Therefore, I think it is important for the rust documentation and community to address this issue more directly.

Beyond my misunderstanding on the file issue, which led to lots of other issues, I think my problems are mostly around syntax, as I still don't really understand exactly how use/mod/crate::/self::/etc work. It's embarrassing, but I just try things until something compiles, and then try to replicate that elsewhere and clean up afterwards. Some of this context could be useful for improving the book, as could a better use case example like mine (I have never had a problem with a structure like the restaurant one from the book, which should also include a main() for usage, and I especially have never seen anything like those specific requirements).

It appears that it is not possible to define separate components of a module in separate files. For example, /mod.rs defines mod:: - I cannot have both /mod1.rs and /mod2.rs define components of mod::

So I think it is not really possible to have

/wsl.rs - library functions that define a path (wsl::slight_smile:
/wsl/type.rs - a single type in a file within that path (wsl::Type)

If I want wsl::Type to be a type, I need to put it in /wsl.rs.

What is possible:

/wsl/typeNscope.rs - defines a path (namespace/scope) that can contain one or more types, which requires wsl::typeNscope::TypeN (though I can alias this to something shorter).

Your analysis seems roughly correct as far as the intended behavior of Rust's module system. There are ways to make it do things differently1, but they're rarely used and will likely put off other Rust programmers.

The usual way to do this in Rust is with the pub use statement, which lets you export an alias as part of the local namespace. In your example, it'd look something like the code below.

Because wsl::type_a is a private module, the only way for external code to see into it is through the wsl::TypeA alias. This lets you define whatever functions and related types you need for implementing TypeA inside the wsl::type_a module without polluting the wsl namespace.

// main.rs
pub mod wsl;

fn example() {
    let _ = wsl::TypeA;
}

// ------------------
// wsl.rs
mod type_a;
pub use type_a::TypeA;

// ------------------
// wsl/type_a.rs
pub struct TypeA;

(1) See the include! macro and #[path] directive, for example.

1 Like

Just adding some notes in case someone else stumbles across this issue and makes their way to this thread.

find . -name *.rs -exec echo \; -exec ls {}  \; -exec cat {} \;

./src/main.rs
mod inv;                                    // define the crate::inv module from /inv.rs

fn main() {                                 // where rum is package name from cargo.toml
    let _: crate::MainType;                 // defined in /main.rs
    let _: self::MainType;                  // alternate
    let _: MainType;                        // alternate
    let _: self::mainmod::MainModType;      // defined in mainmod of /main.rs
    let _: crate::mainmod::MainModType;     // alternate
    let _: mainmod::MainModType;            // alternate
    let _: rum::LibType;                    // defined in /lib.rs
    let _: rum::libmod::LibModType;         // defined in libmod of /lib.rs
    let _: crate::inv::InvType;             // defined in /inv.rs
    let _: inv::InvType;                    // alternate

    // defined in /inv/invnestedtype.rs
    let _: crate::inv::invnestedtype::InvNestedType;
    // let _: InvNestedType;  // requires use crate::inv::invnestedtype::InvNestedType;
}

struct MainType {
}

mod mainmod {
    pub struct MainModType {
    }
}

./src/inv/invnestedtype.rs
// The line "mod invnesedtype;" in /inv.rs references this file, which becomes the crate::inv::invnestedtype module.

pub struct InvNestedType { // crate::inv::invnestedtype::InvNestedType
}

./src/lib.rs
pub struct LibType {        // rum::LibType
}

pub mod libmod {
    pub struct LibModType { // rum::libmod::LibModType;
    }
}
./src/inv.rs
// The line "mod inv;" in /main.rs references this file, which becomes the crate::inv module.

pub mod invnestedtype; // define crate::inv::invnestedtype path as invnestedtype module from /inv/invnestedtype.rs

pub struct InvType {
}

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.