[rust-dev] Changes to .rc files, etc.

Brian Anderson banderson at mozilla.com
Sat Dec 1 02:21:04 PST 2012


Hey, Rusties.

I've recently made some significant changes to how Rust handles crate (.rc) files, directories and the ever-mysterious 'companion modules'.

In brief, there is no longer a special crate syntax. .rc files and .rs files are parsed the same, and in all ways interpreted identically, by the compiler. Rust no longer implicitly merges .rs files from special locations.

Overall things are simpler and should be less surprising, though expressing directory structure in Rust will be awkward in new ways, requiring attributes.

# What crate files were

Until now Rust has had a special, declarative syntax for crate (.rc) files. This was a sort of manifest, where important details about resources used by the crate (libraries, source files, syntax extensions) were available for quick inspection by build tools.

Crate files looked like this:

    // Attributes about this crate
    #[link(...)];
    #[etc];

    // The module *and* directory structure

    // module foo, loaded from foo.rs
    mod foo;

    // module bar, and directory bar
    mod bar {
        // module baz, sourced from bar/baz.rs
        mod baz;
    }

The crate file mapped out both the directory and module structure of the project, but it did some surprising things by convention to fill in the non-leaf nodes in the module heirarchy. First, given a crate file foo.rc, rustc would also look for foo.rs and treat that as the top-level 'crate' module (called a 'companion module'). The differences and relationship between the two was a consistent source of confusion.

Next, it would do a similar thing with modules that corresponded to file system directories. In the above example it would implicitly load ./bar.rs, if it existed, as the content of the module representing the directory `bar`.

This resulted in the following sort of project structure:

    myproject.rc
    myproject.rs
    foo.rs
    bar.rs
    bar/
        baz.rs
        quuz.rs

It places the implementation of mod `bar` outside of the directory containing its related submodules, `baz` and `quux`. While that is a tantalizingly logical way to lay out hierarchical modules, it turns out that's not usually what you want - `bar` and it's submodules make up a modular unit of code and want to be in the same directory on the file system.

Over time the distinction between .rc files and .rs files blurred and important metadata, such as `extern mod` statements, could appear in any source file. Cargo must parse the full source tree to discover crate dependencies. Most things that you might want to express about a crate can be done through attributes, making less of a need for a dedicated crate language.

Things were generally not feeling quite right, so we're just removing the crate language completely and seeing how that works out.

# So how does it work now?

Now Rust parses .rc files and .rs files the same. The `mod foo;` syntax works anywhere, loading a file with the module name plus `.rs`, in the same directory as the current source file. Rust no longer has any module syntax that also implies directory structure.

A typical crate without a directory hierarchy might look like so:

    #[link(name = "core",
           vers = "0.5",
           uuid = "c70c24a7-5551-4f73-8e37-380b11d80be8",
           url = "https://github.com/mozilla/rust/tree/master/src/libcore")];

    #[comment = "The Rust core library"];
    #[license = "MIT"];
    #[crate_type = "lib"];

    // External crate declarations
    extern mod std;
    extern mod zmq;

    // Imports
    use std::serialization;

    // Modules sourced from other files
    mod foo;
    mod bar;

    // Top level declarations, etc.
    trait Bazzable;

Adding directory structure to crates requires, for the time being at least, the use of the `path` attribute to explicitly load a module from a subdirectory. The recommended pattern is to load subdirectory modules from a file called `mod.rs`, located in the subdirectory.

    // module foo, loaded from foo.rs
    mod foo;

    // module bar, loaded from bar/mod.rs
    #[path = "bar/mod.rs"]
    mod bar;

The contents of `bar/mod.rs` are parsed for the module `bar`, and `bar/mod.rs` can declare further modules to load from the `bar` directory. File's are loaded relative to the current source file so, being already located in directory `bar`, `bar/mod.rs` can load additional submodules from the `bar` directory without using the `path` attributive.

The file name `mod.rs` is preferred for this purpose because, being a keyword, there is no chance it will collide with an actual module that you would want to load. This strategy does have the disadvantage that you will have many emacs buffers called `mod.rs<N>`.

Alternately, if you tend to only put code at the leaf nodes of the module tree and liked the old pattern then this will work:

    // Just a normal module
    mod bar {
        #[path = "bar/baz.rs"]
        mod baz;

        // Could be other stuff here
    }

Note that, even though `baz` is declared in a submodule, the path must explicitly specify the directory - `bar/baz.rs`. The parser doesn't try to do anything smart by assuming things in mod `bar` are in directory `bar`.

# Why have .rc files?

We're still using the .rc extension for crate source files because cargo identifies crates by file extension.

Anyway, that's the whole story. Have fun.

-Brian


The issues:

https://github.com/mozilla/rust/issues/2176
https://github.com/mozilla/rust/issues/1277


More information about the Rust-dev mailing list