Convert Result to an inline value class

This commit is contained in:
Michael Bull 2024-03-05 20:36:03 +00:00
parent eecd1b7d99
commit 981fbe2812
3 changed files with 53 additions and 104 deletions

View File

@ -10,11 +10,6 @@ import kotlin.contracts.contract
* - Rust: [Result.ok](https://doc.rust-lang.org/std/result/enum.Result.html#method.ok) * - Rust: [Result.ok](https://doc.rust-lang.org/std/result/enum.Result.html#method.ok)
*/ */
public fun <V, E> Result<V, E>.get(): V? { public fun <V, E> Result<V, E>.get(): V? {
contract {
returnsNotNull() implies (this@get is Ok<V>)
returns(null) implies (this@get is Err<E>)
}
return when { return when {
isOk -> value isOk -> value
else -> null else -> null
@ -27,11 +22,6 @@ public fun <V, E> Result<V, E>.get(): V? {
* - Rust: [Result.err](https://doc.rust-lang.org/std/result/enum.Result.html#method.err) * - Rust: [Result.err](https://doc.rust-lang.org/std/result/enum.Result.html#method.err)
*/ */
public fun <V, E> Result<V, E>.getError(): E? { public fun <V, E> Result<V, E>.getError(): E? {
contract {
returns(null) implies (this@getError is Ok<V>)
returnsNotNull() implies (this@getError is Err<E>)
}
return when { return when {
isErr -> error isErr -> error
else -> null else -> null
@ -111,10 +101,6 @@ public inline infix fun <V, E> Result<V, E>.getErrorOrElse(transform: (V) -> E):
* This is functionally equivalent to [`getOrElse { throw it }`][getOrElse]. * This is functionally equivalent to [`getOrElse { throw it }`][getOrElse].
*/ */
public fun <V, E : Throwable> Result<V, E>.getOrThrow(): V { public fun <V, E : Throwable> Result<V, E>.getOrThrow(): V {
contract {
returns() implies (this@getOrThrow is Ok<V>)
}
return when { return when {
isOk -> value isOk -> value
else -> throw error else -> throw error
@ -127,7 +113,6 @@ public fun <V, E : Throwable> Result<V, E>.getOrThrow(): V {
*/ */
public inline infix fun <V, E> Result<V, E>.getOrThrow(transform: (E) -> Throwable): V { public inline infix fun <V, E> Result<V, E>.getOrThrow(transform: (E) -> Throwable): V {
contract { contract {
returns() implies (this@getOrThrow is Ok<V>)
callsInPlace(transform, InvocationKind.AT_MOST_ONCE) callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
} }

View File

@ -1,19 +1,21 @@
package com.github.michaelbull.result package com.github.michaelbull.result
import kotlin.jvm.JvmInline
/** /**
* Returns a [Result] that [is ok][Result.isOk] and contains a [value][Result.value]. * Returns a [Result] that [is ok][Result.isOk] and contains a [value][Result.value].
*/ */
@Suppress("FunctionName", "DEPRECATION") @Suppress("FunctionName")
public fun <V> Ok(value: V): Result<V, Nothing> { public fun <V> Ok(value: V): Result<V, Nothing> {
return Ok(value, null) return Result(value)
} }
/** /**
* Returns a [Result] that [is an error][Result.isErr] and contains an [error][Result.error]. * Returns a [Result] that [is an error][Result.isErr] and contains an [error][Result.error].
*/ */
@Suppress("FunctionName", "DEPRECATION") @Suppress("FunctionName")
public fun <E> Err(error: E): Result<Nothing, E> { public fun <E> Err(error: E): Result<Nothing, E> {
return Err(error, null) return Result(Failure(error))
} }
/** /**
@ -37,94 +39,66 @@ public inline fun <V, E, F> Result<V, E>.asErr(): Result<Nothing, F> {
/** /**
* [Result] is a type that represents either success ([Ok]) or failure ([Err]). * [Result] is a type that represents either success ([Ok]) or failure ([Err]).
* *
* A [Result] that [is ok][Result.isOk] will have a [value][Result.value] of type [V], whereas a
* [Result] that [is an error][Result.isErr] will have an [error][Result.error] of type [E].
*
* - Elm: [Result](http://package.elm-lang.org/packages/elm-lang/core/5.1.1/Result) * - Elm: [Result](http://package.elm-lang.org/packages/elm-lang/core/5.1.1/Result)
* - Haskell: [Data.Either](https://hackage.haskell.org/package/base-4.10.0.0/docs/Data-Either.html) * - Haskell: [Data.Either](https://hackage.haskell.org/package/base-4.10.0.0/docs/Data-Either.html)
* - Rust: [Result](https://doc.rust-lang.org/std/result/enum.Result.html) * - Rust: [Result](https://doc.rust-lang.org/std/result/enum.Result.html)
*/ */
public sealed class Result<out V, out E> { @JvmInline
public value class Result<out V, out E> internal constructor(
private val inlineValue: Any?,
) {
public abstract val value: V @Suppress("UNCHECKED_CAST")
public abstract val error: E public val value: V
get() = inlineValue as V
public abstract val isOk: Boolean @Suppress("UNCHECKED_CAST")
public abstract val isErr: Boolean public val error: E
get() = (inlineValue as Failure<E>).error
public abstract operator fun component1(): V? public val isOk: Boolean
public abstract operator fun component2(): E? get() = inlineValue !is Failure<*>
public val isErr: Boolean
get() = inlineValue is Failure<*>
public operator fun component1(): V? {
return when {
isOk -> value
else -> null
}
} }
/** public operator fun component2(): E? {
* Represents a successful [Result], containing a [value]. return when {
*/ isErr -> error
@Deprecated( else -> null
message = "Using Ok as a return type is deprecated.", }
replaceWith = ReplaceWith("Result<V, Nothing>"),
)
public class Ok<out V> internal constructor(
override val value: V,
@Suppress("UNUSED_PARAMETER") placeholder: Any?,
) : Result<V, Nothing>() {
override val error: Nothing
get() {
throw NoSuchElementException()
} }
override val isOk: Boolean = true override fun toString(): String {
override val isErr: Boolean = false return when {
isOk -> "Ok($value)"
override fun component1(): V = value else -> "Err($error)"
override fun component2(): Nothing? = null }
}
}
private class Failure<out E>(
val error: E,
) {
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true return other is Failure<*> && error == other.error
if (other == null || this::class != other::class) return false
other as Ok<*>
if (value != other.value) return false
return true
} }
override fun hashCode(): Int = value.hashCode() override fun hashCode(): Int {
override fun toString(): String = "Ok($value)" return error.hashCode()
} }
/** override fun toString(): String {
* Represents a failed [Result], containing an [error]. return "Failure($error)"
*/
@Deprecated(
message = "Using Err as a return type is deprecated.",
replaceWith = ReplaceWith("Result<Nothing, E>"),
)
public class Err<out E> internal constructor(
override val error: E,
@Suppress("UNUSED_PARAMETER") placeholder: Any?,
) : Result<Nothing, E>() {
override val value: Nothing
get() {
throw NoSuchElementException()
} }
override val isOk: Boolean = false
override val isErr: Boolean = true
override fun component1(): Nothing? = null
override fun component2(): E = error
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as Err<*>
if (error != other.error) return false
return true
}
override fun hashCode(): Int = error.hashCode()
override fun toString(): String = "Err($error)"
} }

View File

@ -14,10 +14,6 @@ public class UnwrapException(message: String) : Exception(message)
* @throws UnwrapException if this result [is an error][Result.isErr]. * @throws UnwrapException if this result [is an error][Result.isErr].
*/ */
public fun <V, E> Result<V, E>.unwrap(): V { public fun <V, E> Result<V, E>.unwrap(): V {
contract {
returns() implies (this@unwrap is Ok<V>)
}
return when { return when {
isOk -> value isOk -> value
else -> throw UnwrapException("called Result.unwrap on an Err value $error") else -> throw UnwrapException("called Result.unwrap on an Err value $error")
@ -38,7 +34,6 @@ public fun <V, E> Result<V, E>.unwrap(): V {
public inline infix fun <V, E> Result<V, E>.expect(message: () -> Any): V { public inline infix fun <V, E> Result<V, E>.expect(message: () -> Any): V {
contract { contract {
callsInPlace(message, InvocationKind.AT_MOST_ONCE) callsInPlace(message, InvocationKind.AT_MOST_ONCE)
returns() implies (this@expect is Ok<V>)
} }
return when { return when {
@ -56,10 +51,6 @@ public inline infix fun <V, E> Result<V, E>.expect(message: () -> Any): V {
* @throws UnwrapException if this result [is ok][Result.isOk]. * @throws UnwrapException if this result [is ok][Result.isOk].
*/ */
public fun <V, E> Result<V, E>.unwrapError(): E { public fun <V, E> Result<V, E>.unwrapError(): E {
contract {
returns() implies (this@unwrapError is Err<E>)
}
return when { return when {
isErr -> error isErr -> error
else -> throw UnwrapException("called Result.unwrapError on an Ok value $value") else -> throw UnwrapException("called Result.unwrapError on an Ok value $value")
@ -80,7 +71,6 @@ public fun <V, E> Result<V, E>.unwrapError(): E {
public inline infix fun <V, E> Result<V, E>.expectError(message: () -> Any): E { public inline infix fun <V, E> Result<V, E>.expectError(message: () -> Any): E {
contract { contract {
callsInPlace(message, InvocationKind.AT_MOST_ONCE) callsInPlace(message, InvocationKind.AT_MOST_ONCE)
returns() implies (this@expectError is Err<E>)
} }
return when { return when {