avoid_unrun_task
This rule flags a Task, TaskEither, IO, IOEither, TaskOption or IOOption that is evaluated as a statement and then discarded, without .run() ever being called on it.
Why use this rule
Section titled “Why use this rule”fpdart’s lazy types are descriptions of work, not the work itself. TaskEither.tryCatch(...) builds a plan; nothing executes until .run(). Dropping the value therefore does not merely waste a result — it skips the operation entirely. No request is sent, no row is written, no exception is thrown.
That silence is what makes this worse than a discarded Future, which at least ran. Here the program compiles, the types check, the test that mocks the repository still passes, and in production the save simply never happens.
This is the fpdart counterpart to the SDK’s unawaited_futures, and the one mistake in this family that the type system cannot catch on its own.
Either and Option are deliberately not reported: they are already-computed values, so discarding one wastes a result but never skips an effect.
See also: fpdart: Task, unawaited_futures
save returns a TaskEither, which is a description of the write. Dropping
it means no row is ever written — and nothing says so:
class Failure { const Failure(this.message);
final String message;}
class User { const User(this.id);
final String id;}
class UserRepository { TaskEither<Failure, Unit> save(User user) => TaskEither.of(unit);}
void persist(UserRepository repository, User user) { repository.save(user); // never runs}Run it, and await the result:
Future<void> persist(UserRepository repository, User user) async { await repository.save(user).run();}Or return the pipeline and let the caller own running it — often the better answer, since the caller is the one that can report the failure:
TaskEither<Failure, Unit> persist(UserRepository repository, User user) => repository.save(user);A discarded step inside a chain
Section titled “A discarded step inside a chain”The same mistake hides mid-pipeline, where a step is built and then not joined on:
Future<void> checkout(UserRepository repository, User user) async { repository.save(user).map((_) => 'saved'); // still never runs await Future<void>.delayed(Duration.zero);}Chain it into the pipeline that is run:
Future<void> checkout(UserRepository repository, User user) async { await repository.save(user).map((_) => 'saved').run();}Known limitations
Section titled “Known limitations”Only a discarded statement is reported. A value assigned to a variable is assumed to be run later, since the rule does not track what happens to it afterwards.
Options
Section titled “Options”many_lints: rules: avoid_unrun_task: additional_types: - LazyOperation ignore_cascades: truerules: avoid_unrun_task: additional_types: - LazyOperation ignore_cascades: true| Option | Type | Default | Description |
|---|---|---|---|
additional_types |
list of strings | [] |
Extra type names to treat as lazy, for projects that wrap fpdart’s types in their own |
ignore_cascades |
bool | false |
Skip a lazy value discarded as the target of a cascade |
Configuration
Section titled “Configuration”This rule is in the core preset, so it is on with preset: core,
preset: recommended or preset: opinionated.
To turn it off:
rules: avoid_unrun_task: falseTo keep the rule on but skip certain paths, use per-rule exclude.
Related rules
Section titled “Related rules”prefer_task_either_over_try_catch— A repository’s failures belong in its signature, not in a try/catch.avoid_ad_hoc_left_type— A pipeline only composes when every step shares one error type.avoid_bare_await_in_do— Awaiting a raw Future inside a Do block escapes the block’s tracking.avoid_chain_first_swallowing_failure— chainFirst turns a failing effect into success.