| Name | Modified | Size | Downloads / Week |
|---|---|---|---|
| Parent folder | |||
| 2.3.0 source code.tar.gz | 2026-03-12 | 156.2 kB | |
| 2.3.0 source code.zip | 2026-03-12 | 216.6 kB | |
| README.md | 2026-03-12 | 4.5 kB | |
| Totals: 3 Items | 377.2 kB | 0 | |
- Enable Kotlin unused return value checker by @rileymichael (753785c7cacad2a158962822ce5bbf9b8fa6645e)
- Closes #134 and #135
- Add
@BindingDslmarker to prevent implicit outer-scopebind()(520d36460ebfb8f5438d321ef88515de47ac9d12) - Shadow
asynconCoroutineBindingScopeto auto-bindResult(103ac8819eee3bd86e548c2ae8c9128e032152e4)
Warnings for unused Result return values
The library now compiles with -Xreturn-value-checker=full, implicitly treating all public functions as @MustUseReturnValue. Consumers who enable -Xreturn-value-checker=check in their own builds will receive warnings when they silently discard a Result returned by this library.
Side-effect functions whose return value exists only for optional chaining (onOk, onErr, onEachOk, onEachErr, and their indexed/Flow variants) are marked @IgnorableReturnValue.
Preventing implicit outer-scope bind() in nested bindings
When nesting binding/coroutineBinding blocks with different error types, bind() can silently resolve to an outer scope. This compiles without warning but produces wrong control flow — the BindingException bypasses the inner scope's try/catch and short-circuits the outer scope instead:
:::kotlin
val outer: Result<Int, String> = binding {
val inner: Result<Int, Int> = binding {
"hello".toErr().bind() // silently resolves to outer scope's bind()
}
inner.getOrElse { 0 } // never reached
}
This release annotates both BindingScope and CoroutineBindingScope with a shared @DslMarker annotation, making implicit cross-scope bind() calls a compiler error. All four nesting combinations (binding/coroutineBinding in either direction) are caught. If you intentionally need to bind across scopes, explicit qualification (e.g. this@binding) is required.
Auto-binding async in coroutineBinding
Previously, the correct way to run concurrent operations in coroutineBinding required placing bind() inside the async lambda:
:::kotlin
suspend fun provideX(): Result<Int, ExampleErr> { ... }
suspend fun provideY(): Result<Int, ExampleErr> { ... }
val result: Result<Int, ExampleErr> = coroutineBinding {
val x: Deferred<Int> = async { provideX().bind() }
val y: Deferred<Int> = async { provideY().bind() }
x.await() + y.await()
}
This was non-obvious. A natural reading of the API led users to place bind() outside, chaining .await().bind() at the call site:
:::kotlin
suspend fun provideX(): Result<Int, ExampleErr> { ... }
suspend fun provideY(): Result<Int, ExampleErr> { ... }
val result: Result<Int, ExampleErr> = coroutineBinding {
val x: Int = async { provideX() }.await().bind() // suspends here until provideX() completes
val y: Int = async { provideY() }.await().bind() // provideY() doesn't start until provideX() is done
x + y
}
This compiles without warning, but chaining .await() immediately after async defeats concurrency — each call suspends until completion before the next starts.
This release adds an async member function on CoroutineBindingScope that automatically binds Result-returning lambdas. The member returns Deferred<V> instead of Deferred<Result<V, E>>, so bind() placement is handled for you and the sequential .await().bind() pattern above becomes a compiler error (there is no Result left to bind()). The correct usage is now the natural one:
:::kotlin
suspend fun provideX(): Result<Int, ExampleErr> { ... }
suspend fun provideY(): Result<Int, ExampleErr> { ... }
val result: Result<Int, ExampleErr> = coroutineBinding {
val x: Deferred<Int> = async { provideX() }
val y: Deferred<Int> = async { provideY() }
x.await() + y.await()
}
Both coroutines launch immediately and run concurrently. bind() is called inside each async coroutine, cancelling the scope on the first error without waiting for the caller to await. When the block does not return a Result, the standard CoroutineScope.async extension is used instead.