The bad news is that the 2018 edition of Rust has pretty much settled on the worse of two module systems. The good news is that they have done so with the potential to unfuck this at some point in the future. In the meantime, what can you expect?

First, the bad news

We've chosen "anchored paths." This means that if you want to use any of your own code in your own program, you need to ask the Rust compiler nicely. Obviously, this is antithetical to every part of my being... But what it amounts to is that this code:

fn main() -> error::Result<()> {
    use application::Application;
    use config::*;

    let command = Command::from_args();
    let config = Config::new(&command)?;

    Application::new(config, command).run()
}

...becomes this code:

fn main() -> error::Result<()> {
    use crate::{
        application::Application,
        config::*,
    };

    let command = Command::from_args();
    let config = Config::new(&command)?;

    Application::new(config, command).run()
}

This is far from the only change in the Rust 2018 module system, but it's the one I have a problem with because it makes me do extra work for no conceivable benefit to me. Like a lot of the other 2018 changes, in theory, this one will make the language as a whole easier to learn because, going forward, module paths will work the same way at the crate root (i.e. main.rs and lib.rs) that they do elsewhere in your program.

That's great, I guess. Or at least it's great if you don't already get how the module system works. If you do, now you get to type the word "crate" a lot more, so hooray!

...incoherent muttering...

–Cranky website owner

I should note that I'm using nested paths in the above case to avoid typing "crate" any more than I have to, but you could also just write:

crate::application::Application;
crate::config::*;

It means the same thing.

Now the good news

The new module system works as advertised in the sense that it does away (mostly) with things like extern crate foo; and some of the more annoying aspects of submodules. Here's another look at main from above:

// Rust 2015

#[macro_use]
extern crate serde_derive;

extern crate chrono;
extern crate crossbeam;
extern crate dirs;
extern crate rand;
extern crate stopwatch;
extern crate structopt;
extern crate termcolor;
extern crate toml;
extern crate url;

mod application;
mod config;
mod download;
mod error;
mod fmt;
mod job;

fn main() -> error::Result<()> {
    use application::Application;
    use config::*;

    let command = Command::from_args();
    let config = Config::new(&command)?;

    Application::new(config, command).run()
}
Side note: despite looking a little bit super-generic, this is a real program.

Here's the same thing after applying the changes from the 2018 Edition module system:

// Rust 2018

#[macro_use]
extern crate serde_derive;

mod application;
mod config;
mod download;
mod error;
mod fmt;
mod job;

fn main() -> error::Result<()> {
    use crate::{
        application::Application,
        config::*,
    };

    let command = Command::from_args();
    let config = Config::new(&command)?;

    Application::new(config, command).run()
}

It's shorter. That's an improvement. You might also note, however, that I couldn't get rid of the #[macro_use] extern crate serde_derive; thing. I don't know if that's the whole story, but I tried switching to #[macro_use] use serde_derive; and that didn't work, so we may be stuck with this for the time being.

This only affects the crate root, since the only place you can use extern crate is at the crate root. Usage throughout the rest of your program is going to be pretty much the same, with the exception of the new requirement that you... Eh, I already went over that.

There's also a new wrinkle regarding submodules.

Submodule changes

The submodule changes are good, but they're also weird. In fact, it took me a while to figure this out, which is why I'm writing about it here.

Normally, a module is just a file referenced from your crate root. The main.rs file above is full of them: each of those mod foo; declarations is matched by a file named foo.rs somewhere in my src folder. This is pretty simple, and it hasn't changed.

However, in the 2015 Edition, when you want to add a submodule to a module, it's a bit of a hassle. There are several steps:

  1. Rename your foo.rs file to mod.rs
  2. Move mod.rs to a folder named foo
  3. Create other submodules in foo as foo/submodule_name_here.rs

Like some other things in Rust, this means changing your mind is painful and time consuming. Like some other things in Rust, the 2018 Edition focuses on cutting down on the pain and time required to change your mind.

What's changed?

In the 2018 Edition, creating submodules consists of the following steps:

  1. Create submodules as foo/submodule_name_here.rs

You no longer need to rename and then move your existing file; you only need to make the new folder and start adding stuff to it.

I wanted to note this because, initially, I made the folder and moved my existing module file to it without renaming it, since I had read that mod.rs was no longer required. Well, that's true, but that's not the only change. The important thing here is that you don't need to do anything to your existing module file at all, and in fact rustc expects you to leave it alone.

The one weird effect of this is that, now, you have both a file named foo.rs and a folder named foo on the same level of your source tree, which is very different as far as most programming languages are concerned. Still, given the effects on the process of transitioning from a flat module to a module with submodules, I think I can live with that.