The extern crate keyword, once a staple of Rust, is largely a relic of the past, even though the compiler still understands it.
Let’s see how this plays out in practice. Imagine you’re building a small Rust project and want to use the rand crate for random number generation.
Here’s what a main.rs file might have looked like before Rust 2018:
// main.rs
extern crate rand; // Explicitly declare the use of the rand crate
use rand::Rng;
fn main() {
let mut rng = rand::thread_rng();
let n: u32 = rng.gen_range(1..101);
println!("Your random number is: {}", n);
}
The extern crate rand; line was the compiler’s signal that you intended to link against the rand crate. It was a declaration of dependency at the crate level. You had to do this before you could use anything from rand.
Now, let’s look at the same functionality in a Rust 2018 (and later) project:
// main.rs
// No `extern crate rand;` needed here!
use rand::Rng; // This is all you need to bring rand into scope.
fn main() {
let mut rng = rand::thread_rng();
let n: u32 = rng.gen_range(1..101);
println!("Your random number is: {}", n);
}
The extern crate line is gone. Why? The Rust 2018 edition introduced a simplified module system that implicitly handles crate declarations. When you add rand to your Cargo.toml file (which you still do, as this is how Cargo knows to download and build the dependency), the compiler automatically makes the crate available for use statements.
# Cargo.toml
[package]
name = "my_random_app"
version = "0.1.0"
edition = "2021" # Or "2018"
[dependencies]
rand = "0.8.5" # The dependency is declared here
The use rand::Rng; statement now does double duty: it brings the Rng trait into scope and implicitly declares that the rand crate itself is a dependency that should be linked. This unification makes the code cleaner and reduces boilerplate. The module system now infers the crate linkage from the use statement, provided the dependency is listed in Cargo.toml.
This change was part of a broader effort in Rust 2018 to improve the usability and expressiveness of the language, particularly around how modules and dependencies are managed. Before 2018, you might have also seen extern crate self; or extern crate core; in certain contexts, which were also removed for similar reasons of simplification and implicit handling. The compiler now understands that self refers to the current crate and core is a fundamental, always-available prelude.
The mental model shifts from explicitly declaring crate availability to simply declaring what items you want to bring into your current scope. Cargo handles the underlying linking. This means that when you see use some_crate::SomeItem;, you can assume that some_crate is a dependency that Cargo will resolve and link, without needing an explicit extern crate declaration.
The most surprising true thing about this transition is that extern crate is still valid syntax in Rust 2018 and later editions. If you were to compile the first main.rs example with a 2018 edition compiler, it would work perfectly fine. The compiler simply treats extern crate rand; as a redundant declaration that it can ignore because it has already inferred the dependency from the use statement and Cargo.toml. This backward compatibility means old codebases could gradually migrate without immediate breakage, though modern code should omit it.
The next concept you’ll likely encounter is understanding how to organize your own code into modules using mod and how these internal modules relate to external crate dependencies.