steady provides Result<T, E> and Option<T> for explicit, type-safe control flow in Dart.
It is inspired by Rust and is intended for codebases that prefer modeling success, failure,
and optional values without relying on unchecked exceptions or nullable values.
- Explicit success and failure handling with
Result<T, E extends Exception> - Explicit optional values with
Option<T> - Sync and async transformation helpers
- Chaining and fallback APIs for readable workflows
- Immutable sealed data types with pattern matching support
Add the package to your pubspec.yaml:
dependencies:
steady: ^1.1.1Then install dependencies:
dart pub getImport the package:
import 'package:steady/steady.dart';Create success and failure values:
final ok = Result<int, Exception>.ok(42);
final err = Result<int, Exception>.error(Exception('failed'));Read values safely:
final value = ok.unwrap(); // 42
final fallback = err.unwrapOr(0); // 0
final computed = err.unwrapOrElse((error) {
print('error: $error');
return 0;
});Transform and chain:
final result = Result<int, Exception>.ok(21)
.map((value) => value * 2)
.andThen((value) => Result.ok(value + 1));
print(result.unwrap()); // 43Recover from errors:
final recovered = Result<int, Exception>.error(Exception('network'))
.recover((error) => Result.ok(0));
print(recovered.unwrap()); // 0Work with async callbacks:
final asyncResult = await Result<String, Exception>.ok('steady')
.mapAsync((value) async => value.toUpperCase());
print(asyncResult.unwrap()); // STEADYCreate present and absent values:
final some = Option.some(42);
final none = Option<int>.none();Read values safely:
final value = some.unwrap(); // 42
final fallback = none.unwrapOr(0); // 0Transform and chain:
final option = Option.some(21)
.map((value) => value * 2)
.andThen((value) => Option.some(value + 1));
print(option.unwrap()); // 43Convert an Option to a Result:
final userId = Option<String>.none();
final result = userId.toResult(() => StateError('missing user id'));
print(result.isErr); // trueResult<T, E>:
unwrap,unwrapOr,unwrapOrElse,expectmap,mapAsync,mapErr,mapErrAsyncandThen,andThenAsyncorElse,orElseAsync,recover,recoverAsyncfold
Option<T>:
fromNullableunwrap,unwrapOr,unwrapOrElse,expectmap,mapAsyncandThen,andThenAsyncorElse,orElseAsyncfilter,fold,toResult
Future<Result<User, Exception>> fetchUser(ApiClient client, int id) async {
try {
final response = await client.getUser(id);
return Result.ok(response);
} on Exception catch (error) {
return Result.error(error);
}
}
Future<String> loadUserName(ApiClient client, int id) async {
final result = await fetchUser(client, id);
return result.map((user) => user.name).unwrapOr('Unknown');
}- API reference: https://pub.dev/documentation/steady/latest/
- Example app: example/example.dart
- Issue tracker: https://github.com/cos-overclock/steady/issues
This repository includes a GitHub Actions workflow at .github/workflows/release_publish.yml.
When you push a tag like v1.1.1, GitHub Actions will:
- verify that the tag matches the
versioninpubspec.yaml - create a GitHub Release using the matching section from
CHANGELOG.md - publish the package to
pub.dev
The workflow runs on tag pushes regardless of which branch the tag was created from.
Before using it, configure automated publishing on pub.dev:
- Open
https://pub.dev/packages/steady/admin. - Enable publishing from GitHub Actions for
cos-overclock/steady. - Set the tag pattern to
v{{version}}.
This workflow uses GitHub OIDC via the official dart-lang/setup-dart reusable workflow,
so no pub.dev API token needs to be stored as a GitHub secret.
If you enable a required GitHub Actions environment on pub.dev, add the same environment
name to the publish job in .github/workflows/release_publish.yml.
This package is available under the MIT License. See LICENSE.