Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Learning Rust – Day 4 – Understanding Modules (geekabyte.io)
61 points by dade on May 12, 2022 | hide | past | favorite | 33 comments


After I was comfortable enough in Rust to start writing reasonable projects, I soon encountered modules as a necessary subject, and it confused me so much. I decided to take a couple hours to read docs, play around with some drop-dead simple code that would organize code into modules as either `mod foo { }` blocks, single file modules (foo.rs) or modules with their own submodules, organized by directory (foo/mod.rs with foo/bar.rs).

It pretty much instantly became obvious how to use them, and in retrospect, I don't know why it was so confusing. I think it's a good example of how important it is to [a] apply deliberate practice/learning and [b] have empathy for others that haven't (yet) progressed to our own level of understanding.


Note that the "new" way to do modules with your file hierarchy is to have foo.rs instead of foo/mod.rs. foo.rs is at the same level as folder foo, foo/mod.rs is gone, and everything else is the same.

Not sure whether this is truly better, but it does make it much easier to search for the file containing the module foo -- with the old way, you would have one mod.rs per module which quickly got painful when searching.


Only for simple modules, if they get complex enough, one will quickly use the "old" approach with multiple files.


I had exactly the same experience. It's a simple system, but felt very confusing at first.


Modules confuse me greatly. I was trying to have both a lib folder and a main.rs and I couldn’t figure it out in 15-30 minutes, so I gave up and just put the module in src as a sibling to main.rs while I got stuff done - come to think of it, I’d like to revisit that.


The most common confusion with this is that when importing library code from your binary, you use `crate_name::...` as opposed to `crate::...`, as they are separate. You also don't have to declare the lib.rs as a module from the binary, and generally all your `mod` statements will reside in your lib.rs


Hey, your comment made me realize I'm an idiot and I was trying to make a lib folder at the sibling level to the src folder. That plus your tip about how to organize the files and everything makes sense now. Thanks!


I haven't seen this as being discouraged. Rust does more things differently than most newer languages so the learning curve is there but the documentation is fantastic if you have the time or patience. I say this as someone who has stumbled my way through to competence/expertise on some good newer languages (zig, nim, go without RTFM very much, but I can't do that with Rust.

But there's also a relatively early moment when you get to a local maxima and suddenly it all starts coming together.


honest question, what necessitated Rust's choice of using such a different module system? Every other modern language seems to roughly map directory to module (go/python etc), or explicitly declare a folder as a module (java). C/C++ is the outlier, effectively using file as a module.

Why is Rust so different on this seemingly boring topic?


I don't think it's "so different". Lets look at rust v some other common languages:

rust:

top-level for a crate: lib.rs

module: $modulename.rs or $modulename/mod.rs or a `mod $modulename` block in lib.rs

sub_module: $parent/$sub_module.rs or $parent/$sub_module/mod.rs or a `mod $sub_module` block in $parent/mod.rs

python:

top-level: have the right directory or file in python path

module: $module_name.py or $module_name/__init__.py

sub_module: $parent/$submodule.py or $parent/$submodule/__init__.py

ruby:

top level: have the right file or directory in import path

module: $libname.rb with `module $libname` block

submodule $libname/$submodule.rb with `module $submodule` block

go:

top-level: somewhere in gopath have dir $module that declares `package $module` (convention, you could have a different package name)

module: all files in $module_name that declare `package $module_name`

module: $moddir/$submodule where module files declare `package $submodule`

It's not wildy different. Whats this FUD about?


C and C++ aren't the outlier, because many projects use translation units as base_os_arch.extension.

And in what regards C++, we have proper modules now, which also don't map into a specific file.

Java now has a mix of packages and modules, because not all packages should be public to start with.

Ada introduced a similar concept in 1983.

C++ modules builds on similar ideas, with module partitions.

Then we have plenty of other less mainstream languages.


I kind of like how it's relatively explicit. Compare to say C#'s namespaces are anywhere, as long as the build tool sees the file. I can only imagine how much work the development extensions/tools are and it's probably a large part of why, for example, the .Net/C# extension for VS Code feels so slow, quirky and broken compared to other languages, including rust.

This isn't a knock on the C# language, just that I can understand the explicit nature and how it makes the tooling simpler and more consistent.

Also a recent example that is even more explicit is Deno's module system where you tend to explicitly specify a given file/url. It irks me to no end that using this behavior with TypeScript in Node is so broken by comparison (can't reference files with extension).


Make no mistake, the C# VSCode extension is "slow" and sometimes break is simply because Microsoft WON'T allow it to be better than Visual Studio proper. It's as simple as that. Take the dotnet hot reload debacle for instance.


Probably the influence of the ML module system, where modules are per-file and module interfaces are in a separate file.


Rust's newer scheme is basically the same way it works by convention in Elixir (a more modern language than Golang or the others you listed).

The older mod.rs scheme seems like an unneeded special case to me, but even that isn't exactly alien compared to what other popular programming languages have done before.


> Every other modern language seems to roughly map directory to module (go/python etc) [...]. C/C++ is the outlier, effectively using file as a module.

Actually, no, Python uses files as modules.


Or a directory containing an __init__.py file inside,. describing which modules to import when the directory is imported.


That's a package, not a module.


In Python speak yes, in CS speak, it depends on which language we are talking about.

I guess if you want to be pedantic in regards to Python, yes you're right.


Java does not require a relation between filesystem folders and modules (aka packages). Some IDE's enforce such a relationship which is unfortunate.


Fair, I was actually referring to the typical maven module pattern rather than packages. You can split the same java package across multiple maven modules.


It does, try to use Java command line directly and you will see.


Java does not have folders as modules. See also https://www.oracle.com/corporate/features/understanding-java... for the actual modules. Packages (namespaces) are not modules.

For a programming language to get modules right (so that they actually give you modularity in big projects) is very difficult.

I had a website with a two-character domain name that talked about good module systems, but I can't remember it. Anyway, there they said basically that with libraries, what you want is your library to be able to split up into several abstract modules, whose names and exports are part of the API (!). Inside each module, you have functions, data structures etc. So you'd have library libtcp having modules "auth", "transport" and "data_link" or whatever. Those in turn depend on symbols from other modules in this library or another library. The point is there's always the extra module layer in-between. A lot of languages don't have this extra layer, and not having it is a grave mistake for big projects.

C has no modules or namespaces. That's obviously bad.

C++ has namespaces (kinda what Java calls "packages"), but no modules in the meaning above, and no formal libraries. That makes the whole thing a mess with global state of other libraries intermingling with your library etc. I've had a long-time embedded C++ programmer friend stop programming C++ entirely because he just couldn't take it anymore how unrelated crap you don't care about fucks up your own library just because you #included something and that #included something you don't care about. Obviously, don't design a programming language like that. (they are trying to introduce proper modules into C++ now, but imo it's too late)

So the summary why Rust does it how it does: Because to do it this way it makes good programs.

Rust's "mod" statement defines a module. Naturally, that means that by default you have nothing from before in that module. It does NOT automatically import (or for that matter, export) things (that would destroy modularity), but if you want to you can do "use super::foo" to get some function from the parent module if you absolutely have to, or "use libx::auth" to get some module from some other external library, or "use crate::auth" to get some other module from your own crate (crate means library or program).

If you can have one module nesting, it's nice to also have multiple module nestings, a module defined inside a module.

You need to have a root module. And that's either lib.rs or main.rs, depending on whether you mean the library or the main program.

Other than that, the "mod" statement is pretty similar to how Python does it: if you say "mod x", it will try to find x.rs, and if that's not there x/mod.rs.

Python would be: "import x", it will try to find x.py, and if that's not there x/__init__.py.

The Rust extension is that you can also write mod x { ... contents here } and that's a neat gimmick.

To say that a module system is mundane (like the article says) ignores that a lot of progress in system design scalability in the past >30 years came from improvements in modularity. It is anything but mundane in big programs. There are entire programming languages that are named after their module capability (for example Modula)


They aren't trying to introduce modules in C++, they are already here in VC++, and GCC already has partial support.

It is clang that it is dragging behind ISO C++20 support.

Apparently all the leachers see no value improving upstream.


Agree. Module is a weak design of Rust.


IMHO you should keep your src folder flat and use multiple crates in a workspace for hierarchy instead of nested modules. People forget that the compilation unit of Rust is a crate, not a source file.


I think the most surprising thing about Rust modules is that you need to """pre-declare""" them, especially since Rust doesn't really use function prototypes.

So when you try to move a module into its own file, you need to keep the `mod foo` in the original file.


Ada,

    with foo; -- Import
    use foo;  -- open namespace
Seems familiar.


importing a module is the most fustrating part of writing rust code next to writing a multiple-spawn tasks.

Can someone help me how to import a module in lib/m.rs from lib.rs ? I've never seen someone asked about it.


Folder structure

    src/lib.rs
        lib/
            mod.rs
            m.rs
In lib.rs

    use lib::m;
In lib/mod.rs

    pub mod m;
You need the mod.rs file in subdirectories.


Rust has two distinct semantics surrounding how items can be "in scope".

First, the `mod` keyword takes a module that the current file/scope can see and makes it available within that file/scope as if it had been defined there. This is necessary to make the module's items accessible, i.e., specifiable by their path within the module, in the current file/scope.

Then the `use` keyword takes an already-accessible member of a named scope -- a module, a type, etc -- and makes it usable by name (i.e., without scoping on each use) in the current scope.


Der Weg ist das Ziel!


The way is the goal!




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: