gear idea
Possible Rust

Learning what’s possible in Rust.
Jump to Navigation

Rust Lints You May Not Know

Jun 21st, 2020 · Pattern · #lints · By Andrew Lilley Brinker

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.

note 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!

file 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.

Andrew Lilley Brinker


Andrew works on software supply chain security by day and writes educational Rust posts by night. He started Possible Rust in 2020 and can usually be found on Twitter.

Possible Rust succeeds when more people can join in learning about Rust! Please take a moment to share, especially if you have questions or if you disagree!

Share on Twitter

Discussions