The Great Module Adventure Continues

The informal modules WG met today and successfully narrowed down to a single core proposal, with two variants! The lang team and stakeholders on the WG are all on board with this overall direction (the first time we’ve reached total consensus in this group.)

Proposal

The basic proposal is “Java-style imports”:

  • Fully qualified paths begin with one of:
    • a crate name
    • crate (for the current crate)
    • self (for the current module)
    • super (for the parent module)
  • use statements require fully qualified paths (in Rust 2018)
  • All crates provided by Cargo.toml, --extern, or the standard distribution are treated as part of the prelude for the crate.
    • That means that within items you can freely reference e.g. std::mem::transmute without a leading :: or a use std.
    • As with other names in the prelude, these names can be shadowed by local definitions; we will lint such shadowing.
  • In items, you can use a leading :: to signify a fully qualified path, though it’s almost never necessary.

Example

Here’s some code taken from the wild. With this proposal, I was able to remove several of the use statements in favor of direct references.

use crate::{
    reactor::Handle,
    PollEvented,   
};
use std::{
    io::{self, Read, Write},
    net::{self, SocketAddr, Shutdown},
    time::Duration,
}
use bytes::{Buf, BufMut};
use futures::{Future, Poll, Async};
use iovec::IoVec;
use tokio_io::{AsyncRead, AsyncWrite};

pub struct TcpStream {
    // note: reference to `mio` which is in Cargo.toml
    io: PollEvented<mio::net::TcpStream>,
}

// no need to import `fmt`
impl std::fmt::Debug for TcpStream {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        self.io.get_ref().fmt(f)
    }
}

Leading-:: variant

An alternative is to shorten the syntax for crate-local qualified paths from leading crate:: to just leading ::, i.e.:

use ::{
    reactor::Handle,
    PollEvented,   
};
use std::{
    io::{self, Read, Write},
    net::{self, SocketAddr, Shutdown},
    time::Duration,
}
use bytes::{Buf, BufMut};
use futures::{Future, Poll, Async};
use iovec::IoVec;
use tokio_io::{AsyncRead, AsyncWrite};

In this variant, a leading :: signifies “local crate root” rather than “fully qualified path”.

Assessment/rationale

  • Uniform behavior in top-level and children modules. The way that you bring items into scope, no matter where they come from, is the same in all modules of a crate. The “cliff” between top-level and children modules is eliminated: std is available everywhere, and there’s no confusion between absolute and relative paths for use in the top level.
  • Unambiguous use paths. The paths you write in use are always fully-qualified: by looking at the path, with no other information, you know immediately where the “root” is (whether in this crate or external).
  • Partial “1path” property. All paths that work in a use also work in items and have the same meaning (modulo shadowing, which is linted). The reverse is true for all crate-rooted paths, but not for relative paths.
    • This is a very common setup in programming languages, where “import” statement take fully qualified paths, actual code refers to “anything in scope”, and the former is a subset of the latter.
  • Transition. This approach does not involve fallback logic (aside from what we already have with the prelude). Migrating to Rust 2018 will involve adding a leading crate:: to internal paths, dropping leading :: in items, and (optionally) dropping extern crate declarations. All of which will be facilitated by rustfix.
    • Existing code snippets from blog posts and StackOverflow are very likely to work without modification, because most examples import only from external crates, where the syntax remains unchanged.
  • Ergonomics. Referencing items from the current crate in absolute paths is slightly more verbose. Referencing items from external crates in code is slightly less verbose. Fewer use statements are needed overall (since crate names are automatically available).
  • Aesthetics. Neither variant involves adding a new sigil or dramatically altering the way paths look.

Next steps

At this point, the relevant teams feel positive about this proposal in abstract; it’s the first time we’ve reached full agreement across this set of stakeholders.

We would like to implement this proposal (including the variant as an option) and to start gaining experience with it as a community ASAP, together with a couple other Rust 2018 features that haven’t gotten sufficient testing yet. @Manishearth has been working on setting up infrastructure to make it easier to try out Rust 2018 as a whole; expect further announcements here soon!

30 Likes