Skip to content

Latest commit

 

History

History
166 lines (136 loc) · 6.84 KB

0000-no-more-extern-crate.md

File metadata and controls

166 lines (136 loc) · 6.84 KB
  • Feature Name: immediately-usable-extern-crates
  • Start Date: (fill me in with today's date, YYYY-MM-DD)
  • RFC PR: (leave this empty)
  • Rust Issue: (leave this empty)

Summary

This RFC reduces redundant boilerplate when including external crates. With this change, projects using Cargo (or other build systems using the same mechanism) will no longer have to specify extern crate: dependencies added to Cargo.toml will be automatically useable. We continue to support extern crate for backwards compatibility with the option of phasing it out in future Rust epochs.

Motivation

One of the principles of Rust is that using external crates should be as easy and natural as using the standard library. This allows the standard library to be kept small, and allows mature, standard solutions to be developed by the community.

Currently, however, external crates must be specified twice: once in a build system such as Cargo and again in the source code using extern crate. When external dependencies are conditional (cfg) upon feature flags or the target platform, the conditional logic must appear in both Cargo.toml and the extern crate declarations.

This duplication causes unnecessary effort and results in one more opportunity for mistakes when working with conditionally-enabled dependencies. Allowing the omission of the redundant extern crate syntax contributes to the roadmap goals of improving Rust's ergonomics and providing easy access to high-quality crates.

Guide-Level Explanation

When you add a dependency to your Cargo.toml, it is immediately usable within the source of your crate. For example, imagine that you needed to print a random character. You'd start by adding the rand crate to your Cargo.toml:

# Cargo.toml:
name = "my_crate"
version = "0.1.0"
authors = ["Me" <[email protected]>]

[dependencies]
rand = "0.3"

And then you can immediately use the crate:

// src/main.rs:
use rand;

fn main() {
    let c: char = rand::random();
    println!("A random character: {}", c);
}

Alternatively, we can use just the specific function we need:

use rand::random;

fn main() {
    let c: char = random();
    println!("A random character: {}", c);
}

Reference-Level Explanation

External crates can be passed to the rust compiler using the --extern CRATE_NAME=PATH flag. For example, cargo build-ing a crate my_crate with a dependency on rand results in a call to rustc that looks something like rustc --crate-name mycrate src/main.rs --extern rand=/path/to/librand.rlib ....

When an external crate is specified this way, it will be automatically available to any module in the current crate through use statements or absolute paths (e.g. ::rand::random()). It will not be automatically imported at root level as happens with current extern crate. None of this behavior will occur when including a library using the -l or -L flags.

We will continue to support the current extern crate syntax for backwards compatibility. extern crate foo; will behave just like it does currently. Writing extern crate foo; will not affect the availability of foo in use and absolute paths as specified by this RFC.

Additionally, items such as modules, types, or functions that conflict with the names of implicitly imported crates will result in a warning and will require the external crate to be brought in manually using extern crate. Note that this is different from the current behavior of the implicitly-imported std module. Currently, creating a root-level item named std results in a name conflict error. For consistency with other crates, this error will be removed. Creating a root-level item named std will prevent std from being included, and will trigger a warning.

It will still be necessary to use the extern crate syntax when using #[macro_use] to import macros from a crate. This is necessary in order to prevent macros from being automatically brought into scope and changing the behavior of existing code. However, as specified in RFC 1561, macros 2.0 will no longer require #[macro_use], replacing it with normal use declarations, for which no extern crate is required.

One remaining use case of extern crate syntax is for aliasing, i.e. extern crate foo as bar;. In order to support aliasing, a new "alias" key will be added to the Cargo.toml format. Users who want to use the rand crate but call it random instead can now write rand = { version = "0.3", alias = "random" }.

When compiling, an external crate is only linked if it is used (through either extern crate, use, or absolute paths). This prevents unused crates from being linked, which is helpful in a number of scenarios:

  • Some crates have both lib and bin targets and want to avoid linking both bin and lib dependencies.
  • no_std crates need a way to avoid accidentally linking std-using crates.
  • Other crates have a large number of possible dependencies (such as the current Rust Playground), and want to avoid linking all of them.

In order to prevent linking of unused crates, after macro expansion has occurred, the compiler will resolve use, extern crate, and absolute paths looking for a reference to external crates or items within them. Crates which are unreferenced in these paths will not be linked.

Alternatives

  • Don't do this.
  • Specify external dependencies using only extern crate, rather than only Cargo.toml, by using extern crate foo = "0.2"; or similar. This would require either Cargo or rustc to first scan the source before determining the build dependencies of the existing code, a system which requires fairly tight coupling between a build system and rustc, and which would almost certainly interact poorly with third-party build systems.

Unresolved questions

  • What interactions does this have with future procedural macros?
  • Should we lint/warn on local items shadowing implicitly imported crates? It seems like a useful warning, but it's also a potential backwards-compatibility hazard for crates which previously depended on a crate, didn't import it with extern crate, and had a root-level item with an overlapping name (although this seems like an extreme edge case).
  • extern crate foo has linking side effects even if foo isn't visibly used from Rust source. After this change, use foo; would have similar effects. This seems potentially undesirable-- what's the right way of handling crates which are brought in only for their side effects?