Dart code leaks risk in three places: nullable values, thrown exceptions, and
unawaited futures. df_safer_dart turns all three into typed values the
compiler tracks for you — so the noisy parts of your code disappear and the
intent stays.
import 'package:df_safer_dart/df_safer_dart.dart';
import 'dart:convert';
Async<Option<String>> notificationSound(int userId) =>
fetchUserData(userId) // Async<String>
.map((body) => UNSAFE(() => jsonDecode(body))) // Async<dynamic>
.map((data) => getFromMap<Map>(data, 'config') // -> Option chain
.flatMap((c) => getFromMap<Map>(c, 'notifications'))
.flatMap((n) => getFromMap<String>(n, 'sound')));No try / catch. No await. No if (x != null). If anything throws or a key
is missing, the chain short-circuits and the failure arrives at the end with
the original stack trace intact.
| Type | Variants | What it replaces |
|---|---|---|
Option<T> |
Some<T> ∣ None<T> |
nullable T?, null checks |
Result<T> |
Ok<T> ∣ Err<T> |
thrown exceptions, try / catch |
Resolvable<T> |
Sync<T> ∣ Async<T> |
the T vs. Future<T> split |
They share a common Outcome<T> interface — map, flatMap, fold,
unwrap, named work uniformly across all three. Sync stays sync;
asynchrony only appears when something genuinely is async.
dart pub add df_safer_dart
dart pub add --dev custom_lint df_safer_dart_lintsEnable the lint plugin in analysis_options.yaml:
analyzer:
plugins:
- custom_lintThe companion df_safer_dart_lints
plugin makes the safety rules non-negotiable. A few examples of what it catches:
- Dropping an
Outcomeon the floor. - Declaring a
Future<Outcome<T>>(useAsync<T>instead). - Calling
.unwrap()outside anUNSAFE(() => ...)block. async/awaitinside a function marked@noFutures.
You don't have to remember the rules. The analyzer remembers them for you.
Every Err carries a list of breadcrumbs. Tag any step with .named(label)
and you'll know exactly where a failure originated:
parseInt('not-a-number')
.named('parse')
.map((n) => n * 2)
.named('double');
// → Err(FormatException..., breadcrumbs: ['parse'])Every guarantee is backed by an abuse test in
test/hardening_test.dart:
- 10,000-deep nested outcomes flatten without blowing the stack.
- Throws inside
map/flatMapare absorbed — they never escape the pipeline. - Stack traces survive every transformation, including type changes.
- Debug and release behave identically — no
assert(false)divergence. - Numeric coercions return
NoneforNaN/±Infinityinstead of throwing. - Concurrency primitives (
TaskSequencer,SafeCompleter) drain iteratively under reentrant bursts.
- ARTICLE.md —
tutorial-style walkthrough of
Option,Result, andResolvablewith worked examples. - example/lib/example.dart — the JSON-fetch pipeline above, runnable end to end.
🔍 For more information, refer to the API reference.
This is an open-source project, and we warmly welcome contributions from everyone, regardless of experience level. Whether you're a seasoned developer or just starting out, contributing to this project is a fantastic way to learn, share your knowledge, and make a meaningful impact on the community.
- Find us on Discord: Feel free to ask questions and engage with the community here: https://discord.gg/gEQ8y2nfyX.
- Share your ideas: Every perspective matters, and your ideas can spark innovation.
- Help others: Engage with other users by offering advice, solutions, or troubleshooting assistance.
- Report bugs: Help us identify and fix issues to make the project more robust.
- Suggest improvements or new features: Your ideas can help shape the future of the project.
- Help clarify documentation: Good documentation is key to accessibility. You can make it easier for others to get started by improving or expanding our documentation.
- Write articles: Share your knowledge by writing tutorials, guides, or blog posts about your experiences with the project. It's a great way to contribute and help others learn.
No matter how you choose to contribute, your involvement is greatly appreciated and valued!
If you're enjoying this package and find it valuable, consider showing your appreciation with a small donation. Every bit helps in supporting future development. You can donate here: https://www.buymeacoffee.com/dev_cetera
This project is released under the MIT License. See LICENSE for more information.