The Rust compiler ships with a number of useful lints on by default,
and many use Clippy to provide additional lints. It’s less well known
that the Rust compiler ships with some useful lints which are set to
allow
by default, meaning they don’t generate warnings. In this post,
I’ll explain what each of these lints does, and why it might be useful.
Where to Find These Lints
The lists of “allowed-by-default” lints can be found in the “Rustc book,” a guide to the Rust compiler produced by the Rust team. This page does a good job of explaining what the lints are, how to turn them on, and what effect they have on your code. What it doesn’t do is explain why these lints are present, or why they are turned off by default.
For code examples for each of these lints, refer to the Rustc Book.
The Lints
Anonymous Parameters
Rust 2015 allowed parameter names to be omitted in trait definitions, in a manner similar to the ability in C or C++ to omit parameter names for function prototypes in header files. However, it was considered poor style, and support for it was removed with Rust 2018.
Resolving this lint is easy. If you don’t want to have to name the parameters, you can
use the name _
, which is Rust’s standard name for ignoring values. However, the
purpose of this lint is to encourage you to provide useful, meaningful names for the
parameters.
Box Pointers
Before Rust offered a mechanism to change the default allocator, one had to implement
their own version of Box
which used a different underying allocator. In that situation,
it was desirable for the compiler to flag if you still used the standard Box
type, as
that would not use the custom allocator you intended.
Today, Rust programs can freely change the default allocator, removing the common need
for this lint. Otherwise, it’s useful if you wish to disallow heap allocation, although
there remain other types (like Vec
) which allocate data on the heap, so this lint would
be useful, but not sufficient, for ensuring no heap allocations occur.
Elided Lifetime in Path
It is often desirable to omit specification of lifetimes in places where there do not exist meaningful constraints against them. To support this, and make writing Rust programs simpler, Rust permits elision of lifetimes, and will apply a set of rules for filling in lifetime information.
However, the elision of lifetimes comes with a tradeoff, as it may no longer be clear when a data type contains references. Given that references must obey a restriction of aliasing XOR mutability, not knowing a type contains references may lead one to writing code which violates those restrictions unintentionally, and is therefore rejected by the compiler.
This lint disallows lifetime elision in paths, requiring consistent clarity on the presence of borrowed data. It trades off clarity of syntax for clarity of semantics, and its application is a matter of taste.
Missing Copy Implementations
In general, it is considered good Rust practice to implement common traits like Copy
for types which are able to implement them. This recommendation is enshrined in the
Rust API guidelines, and is intended to make types provided by Rust crates usable
in as wide a variety of contexts as possible.
This lint helps make sure you don’t forget to implement Copy
, one of the simpler
and more useful common traits, when possible.
However, there are a couple important notes to make. First, there are types which
one may not want to implement Copy
for. For example, a type which is designed
to generate monotonically increasing ID numbers probably should not be copied,
lest you end up with two generators generating duplicate IDs. Second, there is a
cost in compile times for generating trait implementations, and so removing
excess trait impls may be desirable for reducing the compilation time of a crate.
In cases where a particular type ought not to be copied, or where the cost of deriving
excess traits is too high, you may assign an item attribute which selectively sets this
lint to allow
. Ideally, this would be accompanied by a comment explaining the
reasoning behind the exception.
Missing Debug Implementations
The same that could be said for the last lint may be said about this one. Debug
is a common and useful trait that may, as the name suggests, be used to debug
Rust programs by viewing the internals of the types for which it is implemented.
However, there are some types which ought not to implement Debug
, the most
notable of which are any types which contain secret data. If a type representing
a cryptographic key implements Debug
, it may end up accidentally being printed
to a log or some other location, where an attacker may be able to read it.
Debug
, same as Copy
, also comes with a compilation-time cost which may not
be desirable. This tradeoff ought to be assessed contextually.
Missing Docs
Rust makes it easy to create API documentation using the rustdoc
tool, and
ideally all publicly-exposed parts of a crate’s API will receive some degree of
documentation. This lint helps ensure that, although it says nothing about the
quality of the documentation provided.
Consider this lint an often necessary but never sufficient part of writing high-quality documentation for Rust crates.
Single Use Lifetimes
If a lifetime appears in only one places, there’s a question of why it’s there at all. The goal of well-written lifetime constraints in functions or struct definitions is to make clear what the constraints are, by eliminating lifetimes which do not need to be explicitly named. This lint is designed to catch this case, making it easier to write expressive lifetimes.
Trivial Casts
Some casts aren’t necessary, and could instead be replaced with type coercion, possibly or optionally with type ascription to specify the new coerced type, which is considered better style. This lint catches such unecessary casts.
Trivial Numeric Casts
Similarly, some casts between numeric types aren’t necessary, and will be flagged by this lint.
A Useful Supplementary Lint
Skip this content.Clippy offers a useful lint called “cast lossless”,
which flags uses of as
-conversions which could be replaced by generally
clearer and safer Into
/From
-conversions which, unlike as
-conversions,
are guaranteed to be lossless.
Unreachable Pub
The use of pub
in Rust is to declare an item as public, but if the item’s
containing module is private, the item will itself be private, regardless
of the visibility specified on it directly. This lint helps to catch these
cases, which left unchecked may lead to confusion about visibility when
reading a program’s source code.
Unsafe Code
Unsafe code is an important part of Rust, but it’s not always desirable to write yourself. Writing correct unsafe code requires care and attentiveness, and may not be worth the effort for individual projects. To ensure no unsafe code is currently present, and limit its introduction in the future, use this lint.
Unused Extern Crates
Rust 2018 substantially reduced the need to specify external crates explicitly in source code, but there remain reasons to do so. If an extern crate is unused, this lint will catch and report it.
Unused Import Braces
This is a simple syntactic lint to eliminate braces in imports when used around a single name, as they aren’t necessary.
Unused Qualifications
This lint catches when a name is referenced with unnecessary qualification. If a path has already been imported, then it’s no longer necessary to use the entire path to items in that name.
Unused Results
Results are Rust’s core mechanism for reporting and handling errors, and as such it is strongly recommended to check the values of results when they’re returned.
If a situation exists where you feel confident that a result does not need to be checked, you can selectively turn off the lint, ideally with a comment explaining why you believe it’s acceptable not to check the results contained in the function.
Variant Size Differences
Enums are as large as their largest variant, so if an enum has one variant which is substantially larger than the rest, then all uses of the enum will end up with substantial amounts of wasted space. This lint catches this case, and offers recommendations to reduce the size of the large variant.
Conclusion
Not all of these will make sense for your code at all, or may not make sense for the moment. Rejecting missing documentation isn’t worthwhile when you’re just starting out and aren’t prepared to write documentation for code which is highly subject to change. Some of these are no longer as useful with modern Rust, or are only useful in limited contexts.
That said, lints like the unused copy or debug implementation lints are generally useful, even if you selectively ignore them in specific cases. As a default, deriving these traits for libraries is good. In binaries, you may not care as much.
Whether you use them or not, it’s useful to be aware that these lints exist!
Corrections
Skip this content.Anonymous Parameters Aren’t Allowed in Rust 2018
In the initial version of the post I incorrectly described the state of anomyous parameters in modern Rust. While such parameters are poor style in Rust 2015, they are not permitted at all in Rust 2018.
Bare Trait Objects Now a Warning in Both Editions
In the initial version of this post I incorrectly described the current state of the bare trait object lint as being allowed by default. In fact, this lint is now set to warn by default in both Rust 2015 and 2018, and has therefore been removed from this post.