inline_result 2.0.0
inline_result: ^2.0.0 copied to clipboard
This package brings a Kotlin-like Result<T> to Dart, using extension types for zero-cost wrapping.
Inline Result π #
Welcome to Inline Result β your new best friend for functional error handling in Flutter/Dart. If you're coming from Android and miss Kotlin's slick Result
type, this package is here to save you from messy try/catch blocks and help you write safer, cleaner code.
This package brings a Kotlin-like Result<T>
to Dart, using extension types for zero-cost wrapping.
β¨ Features #
- β Functional Error Handling β Chain transformations without losing control.
- β Zero-Cost Wrapping β Uses Dart extension types, meaning no extra objects at runtime.
- β
Familiar API β Inspired by Kotlin's
Result<T>
, but Dart-friendly. - β
Safe & Readable β No more
null
checks or exceptions hiding in logs.
Why Inline Result? #
Flutter/Dart lacks a built-in, functional way to handle errors like Kotlin. With Dart Result, you get a familiar and robust API to:
- Avoid nested try/catch blocks
- Chain operations declaratively
- Embrace immutability and safer coding practices
β‘ Quick Comparison: Kotlin vs Dart #
Kotlin #
fun divide(a: Int, b: Int): Result<Int> {
return runCatching { a / b }
}
val result = divide(10, 2)
.map { it * 2 }
.getOrElse { -1 }
println(result) // 10
Dart #
Result<int> divide(int a, int b) {
return runCatching(() => a ~/ b);
}
final result = divide(10, 2)
.map((value) => value * 2)
.getOrElse((e, st) => -1);
print(result); // 10
ποΈ Whatβs Inlining & Why Should You Care? #
Kotlin's Result<T>
is an inline class, meaning it avoids extra allocations while wrapping values.
Dart doesnβt have inline classes, but extension types do something similar:
extension type Result<T>(T _value) { ... }
With this, your Result<T>
doesnβt create an extra objectβitβs just a wrapper at compile-time.
This means no native performance and runtime overhead. π
π οΈ Usage Examples #
β Basic Success & Failure #
final success = Result.success("Yay!");
final failure = Result.failure(Exception("Oops!"));
print(success.getOrNull); // "Yay!"
print(failure.getOrNull); // null
π Chaining with map and recover #
final result = runCatching(() => int.parse("42"))
.map((value) => value * 2)
.recover((_) => 0);
print(result.getOrThrow); // 84
π₯ Handling Failures Gracefully #
final result = runCatching(() => int.parse("NaN"))
.getOrDefault(-1);
print(result); // -1
π€ Future Extensions #
You can easily handle asynchronous operations with our Future
extensions. Convert a Future<T>
into a Future<Result<T>>
to chain transformations and error handling without verbose try/catch blocks.
Or use asyncRunCatching
.
Example:
Future<Result<Post>> fetchPost() {
return http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts/1'))
.asResult()
.map(Post.fromJson);
}
Future<String> getCurrentTitle() {
return fetchPost()
.onFailure((exception, stacktrace) => print('$exception; $stacktrace'))
.map((post) => post.title)
.recover((_, __) => 'Something went wrong')
.getOrThrow;
}
As you can see, we have repeated each extension method for Future<Result<T>>
to make it easier for you to work with the results of asynchronous operations.
βοΈ Why does Result only catch Exceptions? #
The problem is that dart error system not perfect, Error
and Exception
does not have single parent.
And since Error
is a class that should not be caught, we decided to keep only Exception
.
Feel free to implement runErrorCatching
or runObjectCatching
in your project and use it. π₯
β²οΈ Benchmarks #
We conducted several benchmarks comparing three implementations of our Result
type:
- Inline Result: Our extension type implementation.
- Sealed with Extensions: The sealed implementation using extension methods.
- Sealed with Pattern Matching: The sealed implementation that leverages in-place pattern matching.
Method | Inline Result (us) | Sealed with Extensions (us) | Sealed with Pattern Matching (us) |
---|---|---|---|
getOrNull | 12.5148 | 81.8594 | 12.3652 |
map | 12.3678 | 131.572 | 12.3636 |
fold | 12.3604 | 91.9316 | 12.3607 |
Key Observations #
- Inline Result consistently shows excellent performance, matching the speed of the sealed implementation with in-place pattern matching.
- All benchmarks were executed using const constructors. Notably, removing const constructors only further degrades the performance of the sealed with extensions implementation.
- Benchmarks was run with AOT exe.
- You can see benchmarks code at
bench
folder
In summary, using inline classes provides the speed benefits of pattern matching while still offering convenient extension methods such as map, fold, and getOrNull.
β€οΈ Contributing #
If you have ideas, improvements, or just want to say Result.success("hello!")
, feel free to open an issue or PR!