use_existing_variable
v0.4.0 Warning Fix Pattern Matching
When an expression duplicates the initializer of an existing final or const variable in the same scope, you should reference the variable instead. Repeating the expression creates a maintenance risk where only one copy gets updated during refactoring.
Why use this rule
Section titled “Why use this rule”Duplicated expressions are easy to introduce and hard to spot during code review. If the expression changes, you need to update every occurrence. Using the existing variable avoids this inconsistency and makes refactoring safer.
See also: Dart patterns
// Repeating an expression that is already stored in a variablevoid badPropertyAccess(String value) { final isOdd = value.length.isOdd; print(value.length.isOdd);}
// Repeating a method callvoid badMethodCall(List<int> list) { final copy = list.toList(); print(list.toList());}
// Duplicate in a second variable initializervoid badSecondVariable(String value) { final a = value.length.isOdd; final b = value.length.isOdd; print(b);}// Using the existing variablevoid goodReuse(String value) { final isOdd = value.length.isOdd; print(isOdd);}
// No variable exists for the expression (no lint)void goodNoVariable(String value) { print(value.length.isOdd); print(value.length.isOdd);}
// Different expression (isEven vs isOdd) — no lintvoid goodDifferentExpression(String value) { final isOdd = value.length.isOdd; print(value.length.isEven);}
// Non-final variable — value may have changedvoid goodNonFinal(String value) { var isOdd = value.length.isOdd; print(value.length.isOdd); isOdd = false;}
// Expression appears before the variable declarationvoid goodBeforeDeclaration(String value) { print(value.length.isOdd); final isOdd = value.length.isOdd; print(isOdd);}
// Inside a nested function (different execution context)void goodNestedFunction(String value) { final isOdd = value.length.isOdd; void inner() { print(value.length.isOdd); } inner();}
// Trivial expressions (literals, identifiers) are not flaggedvoid goodTrivial() { final x = 42; print(42);}
// Re-allocating a resource on purpose — no lintvoid goodReallocation(String file) async { final old = Database(file); await old.close(); final upgraded = Database(file); print(upgraded);}
// Re-running async work — no lintvoid goodRepeatedAwait() async { final first = await fetchValue(); final second = await fetchValue(); print(first + second);}Expressions that are never flagged
Section titled “Expressions that are never flagged”Source-text equality cannot distinguish “this value was already computed” from “this deliberately produces a fresh one”, so the rule skips expressions whose re-evaluation is observable:
| Expression | Why it is skipped |
|---|---|
Constructor calls (Database(file), List<int>.filled(10, 0)) |
Each evaluation allocates a new instance. Reusing the variable could hand back a closed or already-consumed resource. |
await expressions |
Re-running async work is a separate operation, and the earlier result may be single-use. |
Cascades ([]..add(1)) |
Each evaluation builds and mutates a distinct object. |
This matters most in tests that acquire, release, then re-acquire a resource — suggesting reuse there would be semantically wrong, not merely noisy.
Configuration
Section titled “Configuration”This rule is in the opinionated preset, so it is on with
preset: opinionated, or by name:
rules: use_existing_variable: trueTo turn it off again:
rules: use_existing_variable: falseTo keep the rule on but skip certain paths, use per-rule exclude.
Related rules
Section titled “Related rules”use_existing_destructuring— Add properties to an existing destructuring instead of accessing them directly.avoid_single_field_destructuring— Avoid destructuring a single field when direct property access is simpler.avoid_wildcard_cases_with_enums— Keep exhaustiveness checking by listing enum cases explicitly.prefer_switch_with_enums— Use a switch instead of an if-else chain over enum constants.