Lints¶
The Gossamer linter ships 56 day-one checks. Each has a short
identifier suitable for #[lint(allow(...))] and a long-form
explanation available via gos lint --explain <id>. This page
is auto-generated from gossamer-lint; hand edits are
overwritten on the next run of cargo xtask docs-lints.
unused_variable¶
Declares a let binding whose name is never read.
Prefix the name with _ to silence explicitly (e.g. _tmp)
when the binding is intentional but unused.
unused_import¶
A use declaration whose imported name is never referenced.
Remove the import or reference the name in the file.
unused_mut_variable¶
A binding marked mut that is never reassigned. Drop the mut
keyword.
needless_return¶
return expr at the tail of a block is the same as writing expr
by itself. Prefer the tail form for symmetry with the rest of
the expression language.
needless_bool¶
if cond { true } else { false } is the same as cond. The
inverted form is !cond.
comparison_to_bool_literal¶
x == true reads worse than x, and x == false reads worse
than !x. Drop the literal.
single_match¶
A match with a single arm reads better as if let PATTERN = ....
Single-arm match is almost always a half-written exhaustive match.
shadowed_binding¶
A let binding in the same block shadows an earlier one. Rename
one of them to make the data flow obvious.
unchecked_result¶
let _ = expr? silently discards an error. Either handle the
Err branch explicitly or propagate with ? so the caller sees it.
empty_block¶
An empty {} block is almost always a mistake. Add an explicit
() tail if the block is intentional.
panic_in_main¶
panic! inside main aborts without a clean exit code. Return a
Result from main and use ? so the error propagates.
redundant_clone¶
Calling .clone() on a literal or already-copied value is
redundant. Drop the call.
double_negation¶
!!x collapses to x when x: bool. If the double negation is
intentional for truthiness coercion, use an explicit cast.
self_assignment¶
Assigning a variable to itself does nothing. The statement is usually the residue of a refactor - remove it.
todo_macro¶
todo() and unimplemented() are placeholders, not shippable
expressions. Implement the branch before merging.
i64_only_container_family¶
std::collections::{queue,stack,deque,heap,ordered_*} hold
i64 only and return a new container from every mutator, which
duplicates Queue, Stack, Deque, MinHeap, BTreeSet, and
BTreeMap at a narrower element type.
Deliberately not auto-fixable: the two families differ in more
than spelling. peek answers 0 on an empty container where the
type answers None, so a mechanical rewrite would keep
type-checking and change what an empty container means.
no_effect_statement¶
An expression statement that only computes - a path, a literal,
arithmetic over them - and hands its value to nothing.
A newline followed by -, *, or & starts a NEW statement, so
a continuation meant as part of the line above becomes one of
these. Join it to that line (end the previous line with the
operator, or parenthesise), or bind what it computes.
A builtin method that answers a new value and writes nothing -
v.dedup(), s.trim(), s.to_uppercase() - is reported the same
way when its result is dropped: the receiver is unchanged, so bind
the result (v = v.dedup()).
bool_literal_in_condition¶
if true { ... } / while false { ... } - the branch is
decided at compile time. Drop the control-flow construct.
let_and_return¶
let x = expr; x at the tail of a block is just expr. Drop
the needless binding.
collapsible_if¶
if a { if b { ... } } can be combined into
if a && b { ... }. Easier to scan.
if_same_then_else¶
Both branches of the if are identical. Drop the branch and
keep the body once.
needless_else_after_return¶
if cond { return X } else { Y } - the else is unreachable
fall-through. Un-nest the else body.
self_compare¶
Comparing a value to itself is always true (for ==, <=,
>=) or false (for !=, <, >). Use the constant.
identity_op¶
x + 0, x - 0, x * 1, x / 1 all equal x. The operation
adds nothing but noise.
unit_let¶
let x = () binds the unit value, which is almost never useful.
Drop the let.
float_eq_zero¶
Equality against a nonzero float literal is almost never what you
want - floating-point arithmetic rarely produces the exact bit
pattern. Compare (x - y).abs() < eps with an explicit tolerance.
Exact zero is exempt: a guard such as if d == 0.0 before a
division tests the one value that matters exactly.
empty_else¶
else {} adds no information. Drop the else and let the if
stand alone.
match_bool¶
match b { true => ... false => ... } is an if in disguise.
Rewrite as if b { ... } else { ... }.
needless_parens¶
(x) without a trailing comma is a needless pair of parens -
x reads the same. (x,) is a one-tuple and means something
different. Closure parameters follow the same rule: write |t|,
not |(t)|.
manual_not_equal¶
!(a == b) is just a != b. Prefer the direct operator.
nested_ternary_if¶
Three or more if / else if layers that all test one value
against a pattern are a match written the long way. Rewrite
them as match on that value. A chain whose arms test
unrelated conditions, or compare with < and >, is left
alone: match would only move those into guards.
absurd_range¶
A literal range whose lower bound exceeds its upper bound is empty. Swap the bounds or double-check the intent.
string_literal_concat¶
"a" + "b" can be written directly as "ab". Let the
source reflect the final value.
chained_negation_literals¶
-(-x) is x. The extra unary does nothing.
if_not_else¶
if !cond { A } else { B } scans better as if cond { B }
else { A }. Flip the branches and drop the !.
empty_string_concat¶
Concatenating an empty string literal is a no-op. Drop the
"" + or + "".
println_newline_only¶
println("") already writes a newline. Don't pass "\n"
and don't call it twice to emit a blank line.
match_same_arms¶
Two match arms share the same body. Either collapse them with
| alternation or extract the shared body into a helper.
manual_swap¶
Three consecutive statements let tmp = a; a = b; b = tmp swap
two bindings via a temporary. Prefer a destructuring assignment
once the language supports it, or at minimum document why the
swap is needed.
consecutive_assignment¶
Two back-to-back assignments to the same place - the earlier value is dead before it's read. Drop the first or consolidate the logic into one statement.
large_unreadable_literal¶
Integer literals of five or more digits are easier to scan with
_ as thousands separators: 1_000_000 instead of 1000000.
redundant_closure¶
|x| f(x) is a closure that just forwards to f. Pass f
directly.
empty_if_body¶
An if cond { } else { body } is the same as if !cond { body }.
Invert the condition and drop the empty branch.
bool_to_int_match¶
match b { true => 1, false => 0 } is an if in disguise that
happens to return an integer. Prefer if b { 1 } else { 0 }.
fn_returns_unit_explicit¶
fn f() -> () { ... } is the same as fn f() { ... }. The
explicit -> () is noise.
let_with_unit_type¶
let _: () = expr annotates the binding with the unit type. If
expr was going to return () anyway, the annotation is noise.
If it wasn't, the annotation forces a coercion - use a plain
statement instead.
useless_default_only_match¶
match x { _ => expr } always runs expr - the match adds
nothing. Drop the match (and add let _ = x if evaluating
the scrutinee has side effects).
unnecessary_parens_in_condition¶
if (cond) { ... } wraps the condition in a single-tuple
expression. Drop the parens: if cond { ... }.
pattern_matching_unit¶
match () { ... } has exactly one reachable arm. Drop the match
and run the body directly.
panic_without_message¶
panic() with no argument leaves the post-mortem with nothing
to render. Always pass a brief explanation.
empty_loop¶
loop {} with no body busy-waits forever at 100% CPU. Add a
break, a continue, or replace with a real wait primitive.
fill_loop¶
A for _ in 0..n { xs.push(value) } loop that pushes the same
literal every iteration is the repeat literal spelled long:
let xs = [value; n] builds the identical vec in one expression
(byte-packed for bool / u8 elements).
substring_byte_scan¶
s.substring(i, i + 1) allocates a String per step of a scan.
substring takes byte offsets, so s.byte_at(i) reads the same
position directly as an i64: compare with byte literals
(s.byte_at(i) >= b'0'), do arithmetic on it
(s.byte_at(i) - b'0'), and render with s.byte_at(i) as char.
s[i] is not that byte: indexing counts Unicode scalars, so it
yields the char at character index i.
non_camel_case_types¶
A type, trait, enum variant, associated type, or type parameter
whose name is not UpperCamelCase, as Rust spells them: Point,
HttpServer, KeyA. A foreign type keeps its C spelling.
non_snake_case¶
A function, method, parameter, local, field, or module whose name
is not snake_case, as Rust spells them. A single name in a pattern
matches a variant, struct, or const it names and binds otherwise, so
a binding written Width reads as a variant it is not. A foreign or
#[export] function and a #[repr(C)] struct's fields keep their C
spelling.
non_upper_case_globals¶
A const or static whose name is not SCREAMING_SNAKE_CASE, as Rust
spells them. A single name in a pattern matches the const it names,
so limit => compares against a const limit rather than binding;
the upper case spelling keeps that visible. A foreign static keeps
its C spelling.