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
_______________________________________________
Rust-dev mailing list
[email protected]
https://mail.mozilla.org/listinfo/rust-dev

Reply via email to