Lints

The Gossamer linter ships 50 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.

Code Identifier Default
GL0001 unused_variable warn
GL0002 unused_import warn
GL0003 unused_mut_variable warn
GL0004 needless_return warn
GL0005 needless_bool warn
GL0006 comparison_to_bool_literal warn
GL0007 single_match warn
GL0008 shadowed_binding warn
GL0009 unchecked_result warn
GL0010 empty_block warn
GL0011 panic_in_main warn
GL0012 redundant_clone warn
GL0013 double_negation warn
GL0014 self_assignment warn
GL0015 todo_macro warn
GL0016 bool_literal_in_condition warn
GL0017 let_and_return warn
GL0018 collapsible_if warn
GL0019 if_same_then_else warn
GL0020 redundant_field_init warn
GL0021 needless_else_after_return warn
GL0022 self_compare warn
GL0023 identity_op warn
GL0024 unit_let warn
GL0025 float_eq_zero warn
GL0026 empty_else warn
GL0027 match_bool warn
GL0028 needless_parens warn
GL0029 manual_not_equal warn
GL0030 nested_ternary_if warn
GL0031 absurd_range warn
GL0032 string_literal_concat warn
GL0033 chained_negation_literals warn
GL0034 if_not_else warn
GL0035 empty_string_concat warn
GL0036 println_newline_only warn
GL0037 match_same_arms warn
GL0038 manual_swap warn
GL0039 consecutive_assignment warn
GL0040 large_unreadable_literal warn
GL0041 redundant_closure warn
GL0042 empty_if_body warn
GL0043 bool_to_int_match warn
GL0044 fn_returns_unit_explicit warn
GL0045 let_with_unit_type warn
GL0046 useless_default_only_match warn
GL0047 unnecessary_parens_in_condition warn
GL0048 pattern_matching_unit warn
GL0049 panic_without_message warn
GL0050 empty_loop warn

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.

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.

redundant_field_init

Foo { x: x } is the same as the shorthand Foo { x }.

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

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.

manual_not_equal

!(a == b) is just a != b. Prefer the direct operator.

nested_ternary_if

Three or more nested if / else if layers are hard to skim. Rewrite as match on the discriminant.

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.