What's the difference between 'extern crate' and write into cargo.toml

(Note: the following post uses edition 2018 syntax and rules to refer to items)

To understand what's going on, one needs to know there are two "places" Rust will look at when doing name resolution:

  1. the module specific namespace;

  2. a "global" context, called the prelude.

  • (where 1. has priority over 2.)

An example of 1. are the use ... imports, or items defined within the same module:

use ::log::info; // brings info! into the current module namespace

For 2., there are three main examples:

  • The standard prelude, this is the place that makes Box, Vec, String, Option, Some, etc. be available.

  • The "extern prelude". This is what makes external crates be callable by path, with an optional (but which I cannot recommend enough) leading :: to disambiguate.
    In the previous example, this is what made ::log be understood as the namespace of the extern crate log.

    • For 99.9% of the crates out there, this "extern prelude" is automagically populated with all the external crates specified in the Cargo.toml.

      In all but the few latest releases, for instance, proc_macro wasn't part of that prelude when using a proc-macro = true crate, hence the need (at "that" time) for extern crate proc_macro.

      • (Note that not writing it right now is an easy way to make your crate be less compatible with these "old" Rust versions for absolutely no reason, so I recommend to keep using extern crate proc_macro in such crates for a few more Rust releases. That being said, 1.45.0 has been a major breakpoint in the world of procedural macros, so you can directly require that Minimum Rust Version and not care about these questions).

      This is what leads to also requiring extern crate test or extern crate alloc.


Now, regarding macros, the only way to use macros from another crate used to be through a #[macro_use] extern crate ..., which brought the macros to a global prelude-like context: the macros were not namespaced!

This can be quite ergonomic for things like ::log's macros, but in practice ends up being quite restrictive.

So, the more modern way to pull these items is through standard use imports, but then these need to be repeated within each module that needs to refer to them, which is no different than what we have to do for non-macro items anyways!

Hence the pattern for crates to offer their own public prelude, which is one of the few things that are idiomatic to blob import: this way, copy-pasting that import line isn't that annoying.

A personal pattern I have been using for some of my crates is to have my own internal prelude:

//! src/utils/prelude.rs
#![allow(unused_imports)]

pub(in crate)
use ::core::{
    convert::{TryInto, TryFrom},
    iter::FromIterator,
    ops::Not,
};

#[cfg(feature = "alloc")]
pub(in crate)
use ::alloc::{
    boxed::Box,
    string::String,
    vec::Vec,
};

pub(in crate)
use ::anyhow::{
    bail,
    Result; // If second param is elided, it defaults to anyhow's
};

pub(in crate)
use ::log::{
    error, warn, info, debug, trace,
};

pub(in crate)
use crate::{
    // stuff from my own crate
    prelude::{*,
        // If my crate _exports_ a prelude, it makes sense for it to be a subset of the internal one
    },
};

and then have a use_prelude!(); macro at the root of my crate:

//! src/lib.rs
macro_rules! use_prelude {() => (
    #[allow(unused_imports)]
    use crate::utils::prelude::*;
)}

so that I can simply copy-paste that use_prelude!(); import in all my modules, end emulate my own custom prelude.

7 Likes