Closed
Description
What it does
Warn if type parameters are named identically but ordered inconsistently in type definitions and impl-blocks. This is a cousin to the new inconsistent_struct_constructor
lint. With literal constructors, the order of variables does not matter (Foo { a, b }
is the same as Foo { b, a }
). Type parameters, on the other hand, are never referred to by name, but only by position, which is exactly reversed to the behavior of struct constructors. For example:
struct Foo<A, B> {
a: A,
b: B,
}
// Potential warning: `B` does not refer to `B` in the struct-definition, because of position.
impl<B, A> Foo<B, A> {
fn type_names(&self) {
let name_a = std::any::type_name::<A>();
let name_b = std::any::type_name::<B>();
println!("A: {}, B: {}", name_a, name_b);
}
fn values(&self)
where
A: std::fmt::Debug,
B: std::fmt::Debug,
{
println!("a: {:?}, b: {:?}", self.a, self.b);
}
}
fn main() {
let foo = Foo { a: 0, b: "foo" };
// Prints 'A: &str, B: i32'
foo.type_names();
// Prints 'a: 0, b: "foo"'
foo.values();
// Wait, what? A `i32` valued as "foo" ?
}
Categories (optional)
- Kind: Possibly
clippy::correctness
, because getting this wrong may cause you to refer to the wrong type parameter.
What is the advantage of the recommended code over the original code
- Naming the type parameters consistently avoids confusion between type definition and type implementation.
Drawbacks
None.
Example
// Note::
struct Foo<A, B> {
// ...
impl<B, A> Foo<B, A> {
// ^--|- This type parameter refers to `Foo::A`, but is named `B`.
// ^- This type parameter refers to `Foo::B`, but is named `A`
Could be written as:
impl<A, B> Foo<A, B> {