Item 23: Avoid wildcard imports
Rust's use
statement pulls in a named item from another crate or module and makes that name available for
use in the local module's code without qualification. A wildcard import (or glob import) of the form use somecrate::module::*
says that every public symbol from that module should be added to the local namespace.
As described in Item 21, an external crate may add new items to its API as part of a minor version upgrade; this is considered a backward-compatible change.
The combination of these two observations raises the worry that a nonbreaking change to a dependency might break your code: what happens if the dependency adds a new symbol that clashes with a name you're already using?
At the simplest level, this turns out not to be a problem: the names in a wildcard import are treated as being lower priority, so any matching names that are in your code take precedence:
use bytes::*;
// Local `Bytes` type does not clash with `bytes::Bytes`.
struct Bytes(Vec<u8>);
Unfortunately, there are still cases where clashes can occur. For example, consider the case when the dependency adds a new trait and implements it for some type:
#![allow(unused)] fn main() { trait BytesLeft { // Name clashes with the `remaining` method on the wildcard-imported // `bytes::Buf` trait. fn remaining(&self) -> usize; } impl BytesLeft for &[u8] { // Implementation clashes with `impl bytes::Buf for &[u8]`. fn remaining(&self) -> usize { self.len() } } }
If any method names from the new trait clash with existing method names that apply to the type, then the compiler can no longer unambiguously figure out which method is intended:
let arr = [1u8, 2u8, 3u8];
let v = &arr[1..];
assert_eq!(v.remaining(), 2);
as indicated by the compile-time error:
error[E0034]: multiple applicable items in scope
--> src/main.rs:40:18
|
40 | assert_eq!(v.remaining(), 2);
| ^^^^^^^^^ multiple `remaining` found
|
note: candidate #1 is defined in an impl of the trait `BytesLeft` for the
type `&[u8]`
--> src/main.rs:18:5
|
18 | fn remaining(&self) -> usize {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: candidate #2 is defined in an impl of the trait `bytes::Buf` for the
type `&[u8]`
help: disambiguate the method for candidate #1
|
40 | assert_eq!(BytesLeft::remaining(&v), 2);
| ~~~~~~~~~~~~~~~~~~~~~~~~
help: disambiguate the method for candidate #2
|
40 | assert_eq!(bytes::Buf::remaining(&v), 2);
| ~~~~~~~~~~~~~~~~~~~~~~~~~
As a result, you should avoid wildcard imports from crates that you don't control.
If you do control the source of the wildcard import, then the previously mentioned concerns disappear. For example, it's common
for a test
module to do use super::*;
. It's also possible for crates that use modules primarily as a way of dividing
up code to have a wildcard import from an internal module:
mod thing;
pub use thing::*;
However, there's another common exception where wildcard imports make sense. Some crates have a convention that common items for the crate are re-exported from a prelude module, which is explicitly intended to be wildcard imported:
use thing::prelude::*;
Although in theory the same concerns apply in this case, in practice such a prelude module is likely to be carefully curated, and higher convenience may outweigh a small risk of future problems.
Finally, if you don't follow the advice in this Item, consider pinning dependencies that you wildcard import to a precise version (see Item 21) so that minor version upgrades of the dependency aren't automatically allowed.