# Idiom lints
## Structure
* Tracking issue
* Code example that triggers the lint
* Code example that fixes it
* Migration supported
* Current state
* Rationale in a nutshell
* Concerns folks have raised
* Proposed resolution
## Terminology
* **Migration**: Hard error in Rust 2021, warning in earlier editions
* **Idiom lint**: Deny-by-default in Rust 2021, warning in earlier editions
Lint Groups https://doc.rust-lang.org/nightly/rustc/lints/groups.html
Specifically, the `rust-2018-idioms` group consists of these:
- bare-trait-objects
- elided-lifetimes-in-paths
- ellipsis-inclusive-range-patterns
- explicit-outlives-requirements
- unused-extern-crates
## BARE_TRAIT_OBJECTS
Tracking issue: https://github.com/rust-lang/rust/pull/81244, https://github.com/rust-lang/lang-team/issues/65
* Code example that triggers the lint
```rust=
let x: Box<std::fmt::Debug>;
```
```rust
fn process(iter: &mut Iterator<Item = i32>)
```
* Current status: warn
* Code example that fixes it
```rust
let x: Box<dyn std::fmt::Debug>;
```
```rust
fn process(iter: &mut dyn Iterator<Item = i32>)
```
* Migration supported
```text
--> src/main.rs:2:12
|
2 | let x: Box<std::fmt::Debug>;
| ^^^^^^^^^^^^^^^ help: use `dyn`: `dyn std::fmt::Debug`
|
```
* Known issues
- Not detecting `dyn`s needed for associated items (upgrading `Foo::bar` to `<dyn Foo>::bar`) https://github.com/rust-lang/rust/issues/65371
* Rationale in a nutshell
- Many, many people get confused by the difference between traits and types without this. (Past-Scott thought that trait generics existed because it *looks* like you can pass a trait to a generic parameter. That confusion disappears if there's an error saying "that's a trait", and pointing to documentation about trait objects.)
- `Foo<a + b>` currently gets treated as `Foo<dyn a + b>`, leading to confusing errors (https://github.com/rust-lang/rust/pull/77502#discussion_r499172677), making it harder to suggest `Foo<{a + b}>`.
- `Animal::speek` currently needs to look for `<dyn Animal>::speek`, leading to confusing errors (https://github.com/rust-lang/lang-team/issues/65#issuecomment-766453773), making it harder to suggest `Animal::speak`.
- `let x: u32 = <Default>::default();` sends the compiler off on a tirade about object safety (https://github.com/rust-lang/rust/issues/51077), but if `dyn` was required this could just say "don't put traits in `<>`s".
- rustc could steer people towards *either* `dyn` or `impl`.
* Proposal: soft deprecation; edition error
## ELIDED_LIFETIMES_IN_PATHS
Maybe the in-band lifetimes tracking issue? https://github.com/rust-lang/rust/issues/44524
* Code example that triggers the lint
```rust=
pub fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {}
```
```rust=
pub fn fmt(&self) -> Ref<T> {}
```
* Code example that fixes it
```rust=
pub fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {}
```
```rust=
pub fn fmt(&self) -> Ref<'_, T> {}
```
* Migration supported
```
3 | fn make_foo(x: &i32) -> Foo {
| ^^^- help: indicate the anonymous lifetime: `<'_>`
```
* Current state
* Not enabled by default in 2018: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=0f5b391efe0e8d73eede6028678245ee
* Rationale in a nutshell
* It's important to know when lifetimes are tied together, because of the large impact that has on how the values can be produced and used.
* It's important to know when types contain lifetimes because that affects what you can do with them even when `.to_owned()`ing them or trying to put them into `Box`es.
* These are required in other places, like `impl` blocks, so requiring them everywhere is more consistent.
* Concerns folks have raised
* eddyb: This is important for "lifetime relationships", not for lifetimes themselves https://github.com/rust-lang/rust/issues/44524#issuecomment-422384048
* But Niko disagrees: https://github.com/rust-lang/rust/issues/44524#issuecomment-422505328
* Possibilities/Conversation Topics:
* Would be nice to have the "you could use `'_` here" lint to go along with this, as otherwise it may result in excess named lifetimes.
* Should it split to parameters vs return type? It might be expedient to move the parts along separately...
* What's the balance between what's in code vs what's in rustdoc here?
* Proposed resolution
- split the lint:
- participating in lifetime elision
- not participating in lifetime elision
* Notes
* Reasons in favor to at least warn
* nikomatsakis finds it really helpful to see where lifetimes are; very helpful esp. when people come with questions about why they're getting an error
* joshtriplett would like the reminder that the type has a lifetime, and wants to be required to acknowledge that. joshtriplett would also like to see that when reading other people's code.
* It's nice for the error messages to have something to point at.
* Reasons against
* it's annoying, cramertj hates writing `&mut Context<'_>` because he has to write it so often and knows the type very well
* as a newcomer, definitely useful; as an experienced user, not so much
* maybe address with a simpler syntax, if we plan to address? (e.g., something easier to type)
* completely hypothetical syntaxes: `&mut Formatter<'>`, or `&mut Formatter'`
* IDE usage may help, but do we want to rely on that? That only addresses *typing* it, in any case, and seeing it is also a kind of problem
* Random thought:
* Is there a material difference between the `'_` in `Foo<'_>` vs `&Foo<'_>`?
* Is it "covered" by the outer lifetime in some sense?
* Spitting the lint
* 1. `fn foo(x: Ref<i32>) -> Ref<u32>`
* Argument is participating in lifetime elision too, not just the return position.
* 2. `fn foo(x: Ref<i32>)` -- want it because it's kind of unclear if this is borrowed or not
* 3. `fn foo(x: &Ref<i32>)` -- feels 'covered' by the &, less important
* 4. `fn foo(x: &mut Ref<i32>)` -- may store to it, feels more important
* Group 1 and 2 together as `elided_lifetimes_in_paths`
* But 2 isn't tying lifetimes together, so Scott's not convinced that they're the same category.
* Group 3 as .. `elided_lifetimes_in_arguments` ("elided trivial lifetime"?)
* Settings:
* elided-lifetimes-in-paths
* elided-lifetimes-in-arguments
* Argument about splits
* Figuring out the right place to split is hard, need something we can explain to people if they are treated differently
* "Always need to show except in the case where it's covered by a reference (in a fn argument list)"
## ELLIPSIS_INCLUSIVE_RANGE_PATTERNS
* Tracking issue ???
* Current status: warn
* Code example that triggers the lint
```rust=
match foo {
'a' ... 'z' => 22,
_ => 44,
}
```
* Code example that fixes it
```rust=
match foo {
'a' ..= 'z' => 22,
_ => 44,
}
```
* Migration supported: :green_heart:
* Proposal: soft deprecation; edition error
* Why?
* Reclaim `...`
* Been quite a while
* Could deny-by-default on 2018
* Teaches people not the thing to use in a stronger way
* Doing on the new edition has a similar effect, but on a slower timescale
## EXPLICIT_OUTLIVES_REQUIREMENTS
* Tracking issue: ???
* Code example that triggers the lint
```rust
struct SharedRef<'a, T>
where
T: 'a,
{
data: &'a T,
}
```
* Code example that fixes it
```rust
struct SharedRef<'a, T>
{
data: &'a T,
}
```
* Migration supported
* Current state: Allow
* Rationale in a nutshell
* Having a reference with lifetime `'a` to an item implicitly infers that the item itself lives for at least `'a`.
* Concerns folks have raised
* Some false positives: https://github.com/rust-lang/rust/issues/54630
* Does the bound still show in rustdoc?
* With an old MSRV, maybe people would want this even if it's not needed on current versions?
* Discussion topic:
* Is this even an edition thing? How about everywhere?
* Proposed resolution
* ~~Warn-by-default in new edition? Or just in all of them?~~
* Not for edition 2021, so worry about it on the tracking issue later.
## UNUSED_EXTERN_CRATES
* Tracking issue: ???
* Code example that triggers the lint
```rust,compile_fail
extern crate proc_macro;
```
* Code example that fixes it
```rust,compile_fail
//extern crate proc_macro;
```
* Current state: Allow
* Rationale in a nutshell
* extern crate isn't needed any more with the new path system
* Concerns folks have raised
* https://github.com/rust-lang/rust/issues/53964
* https://github.com/rust-lang/rust/issues/44294
* Proposed resolution
* In favor of warning *if we know it will work* to just delete it
* This implies cannot warn on Rust 2015, so edition related in this way
* Question from Niko:
* Does this work in 2015?
* Answer: some changes were backported to 2015, but not this
# Other lints not in `rust-2018-idioms`
These were added to https://github.com/rust-lang/rust/issues/80165 at some point, but were not considered idiom lints when the group was made for the previous (2018) edition.
## UNREACHABLE_PUB
* Tracking issue: ???
* Code example that triggers the lint
```rust
mod m {
#[allow(unused)]
pub struct Item;
}
```
* Code example that fixes it
```rust
mod m {
#[allow(unused)]
struct Item;
}
```
* Migration supported
* Current state: Allow
* Rationale in a nutshell
* Items that are not publically exported should not be marked as such
* Concerns folks have raised
* False positives: https://github.com/rust-lang/rust/issues/64762, https://github.com/rust-lang/rust/issues/52665
* This is commonly used for sealed stuff, no?
* Generally we feel we would be ok to have an allow if it's "worth a comment"
* Proposed resolution
* Soft deprecation, but not an edition issue
## MACRO_USE_EXTERN_CRATE
* Tracking issue: ???
* Code example that triggers the lint
```rust
#[macro_use]
extern crate serde_json;
fn main() {
let _ = json!{{}};
}
```
* Code example that fixes it
```rust
fn main() {
let _ = serde_json::json!{{}};
}
```
or
```rust
use serde_json::json;
fn main() {
let _ = json!{{}};
}
```
* Migration supported
* Current state: Allow
* [Fix works in Rust 2015](https://play.rust-lang.org/?version=stable&mode=debug&edition=2015)
* Rationale in a nutshell
* Since Rust 2018, the preferred way to refer to macros from third-party crates is through their path.
* Concerns folks have raised
* Undecided whether this is actually the way to go (though with pub macro_rules, that may no longer be the case): https://github.com/rust-lang/rust/issues/52043
* Proposed resolution
* Not edition specific, can soft deprecate later if we want
## CLASHING_EXTERN_DECLARATIONS
```rust
mod m {
extern "C" {
fn foo();
}
}
extern "C" {
fn foo(_: u32);
}
```
- Current state:
- Warn-by-default
- Comments:
- Is there a legit use for this?
- We believe no, basically UB at the llvm level
- You can't really control which signature gets selected
- If there are multiple valid ways to call it, you can use `...`
- maybe you can use `link_name`
- The lint does not fire if the arguments are the same, so you can declare twice with compatible signatures
- One possible use case might be `Option<&T>` and `*const T`
- these could be distinct lints
- Motivation:
- This is a soundness hole
- We can't detect this 100% (most notably cross crates)
- Proposal:
- Bug fix, phase in
- if somebody raises a use during deny-by-default phase, then we can revisit
## CONST_ITEM_MUTATION
```rust
const FOO: [i32; 1] = [0];
fn main() {
FOO[0] = 1;
}
```
- Current state:
- Warn-by-default
- Questions
- Is this actually common?
- It comes up on Discord decently often
- People want statics
- `{FOO}[0] = 1` wouldn't lint?
- Comments
- Not UB but almost certainly long
- "Has defined semantics but is almost guaranteed to be wrong"
- Proposed resolution
- Not edition but an interesting question
- Might fit "Deny by Default" in a similar way to overflowing literals (see above category for overflowing literals)
## TYPE_ALIAS_BOUNDS
```rust
type SendVec<T: Send> = Vec<T>;
// T: Iterator is required here
type SendVec<T: Iterator> = Vec<T::Item>;
// you can rewrite to
type SendVec<T> = Vec<<T as Iterator>::Item>;
// common case was `T`...
type SendVec<T> = Box<T>;
```
- Current state
- warn-by-default
- Questions:
- Do we need to know what this will do in order to turn this into an error?
- Comments:
- Don't want to flip-flop on semantics. If we deny now, only to relatively soon allow this, are we causing confusion?
- Also, we don't know what to implement in next edition, and it's not clear that having people remove bounds is actually putting us in a better position for the next edition.
- Proposed resolution
- Leave it for now
## Future Compatibility Warnings
Sense:
* These are mostly bug fixes that should apply independent of edition, we should just do this
* Someone might want to look in and see if there is some particular case to champion--
* would be looking for a case that is not actually a bug fix, sort of like bare traits, but more of a deprecation, and hence a candidate for edition error
Meeting conversation:
- Should we flip these for the edition because we can?
- Should we not flip them because they need to happen everywhere?
- Do we want a case-by-case decision so we can do the `cargo fix`-able ones?
### WHERE_CLAUSE_OBJECT_SAFETY
* Tracking issue: https://github.com/rust-lang/rust/issues/51443
* Concerns:
* Need review to decide if the fix is appropriate
* Has notable impact on the issue over time
### SAFE_PACKED_BORROWS
```rust
#[repr(packed)]
pub struct Unaligned<T>(pub T);
pub struct Foo {
start: u8,
data: Unaligned<u32>,
}
fn main() {
let x = Foo { start: 0, data: Unaligned(1) };
let y = &x.data.0;
}
```
### CENUM_IMPL_DROP_CAST
```rust
enum E {
A,
}
impl Drop for E {
fn drop(&mut self) {
println!("Drop");
}
}
fn main() {
let e = E::A;
let i = e as u32;
}
```
### COHERENCE_LEAK_CHECK
```rust
trait SomeTrait { }
impl SomeTrait for for<'a> fn(&'a u8) { }
impl<'a> SomeTrait for fn(&'a u8) { }
```
### CONST_EVALUATABLE_UNCHECKED
```rust
fn test<T>() {
let _ = [0; foo::<T>()];
}
```
### ILLEGAL_FLOATING_POINT_LITERAL_PATTERN
```rust
let x = 42.0;
match x {
5.0 => {}
_ => {}
}
```
### INDIRECT_STRUCTURAL_MATCH
```rust
struct NoDerive(i32);
impl PartialEq for NoDerive { fn eq(&self, _: &Self) -> bool { false } }
impl Eq for NoDerive { }
#[derive(PartialEq, Eq)]
struct WrapParam<T>(T);
const WRAP_INDIRECT_PARAM: & &WrapParam<NoDerive> = & &WrapParam(NoDerive(0));
fn main() {
match WRAP_INDIRECT_PARAM {
WRAP_INDIRECT_PARAM => { }
_ => { }
}
}
```
### LATE_BOUND_LIFETIME_ARGUMENTS
```rust
struct S;
impl S {
fn late<'a, 'b>(self, _: &'a u8, _: &'b u8) {}
}
fn main() {
S.late::<'static>(&0, &0);
}
```
### MUTABLE_BORROW_RESERVATION_CONFLICT
```rust
let mut v = vec![0, 1, 2];
let shared = &v;
v.push(shared.len());
```
### NONTRIVIAL_STRUCTURAL_MATCH
```rust
#[derive(Copy, Clone, Debug)]
struct NoDerive(u32);
impl PartialEq for NoDerive { fn eq(&self, _: &Self) -> bool { false } }
impl Eq for NoDerive { }
fn main() {
const INDEX: Option<NoDerive> = [None, Some(NoDerive(10))][0];
match None { Some(_) => panic!("whoops"), INDEX => dbg!(INDEX), };
}
```
### PRIVATE_IN_PUBLIC
```rust
struct SemiPriv;
mod m1 {
struct Priv;
impl super::SemiPriv {
pub fn f(_: Priv) {}
}
}
```
### PROC_MACRO_DERIVE_RESOLUTION_FALLBACK
```rust
// foo.rs
#![crate_type = "proc-macro"]
extern crate proc_macro;
use proc_macro::*;
#[proc_macro_derive(Foo)]
pub fn foo1(a: TokenStream) -> TokenStream {
drop(a);
"mod __bar { static mut BAR: Option<Something> = None; }".parse().unwrap()
}
// bar.rs
#[macro_use]
extern crate foo;
struct Something;
#[derive(Foo)]
struct Another;
fn main() {}
```
### UNINHABITED_STATIC
```rust
enum Void {}
extern {
static EXTERN: Void;
}
```
### UNSUPPORTED_NAKED_FUNCTIONS
```rust
#[naked]
pub fn f() -> u32 {
42
}
```