Skip to content

Commit

Permalink
Convert Result to an inline value class
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelbull committed Mar 16, 2024
1 parent b9eddc4 commit 19af0bd
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 101 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,6 @@ import kotlin.contracts.contract
* - Rust: [Result.ok](https://doc.rust-lang.org/std/result/enum.Result.html#method.ok)
*/
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 {
isOk -> value
else -> null
Expand All @@ -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)
*/
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 {
isErr -> error
else -> null
Expand Down Expand Up @@ -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].
*/
public fun <V, E : Throwable> Result<V, E>.getOrThrow(): V {
contract {
returns() implies (this@getOrThrow is Ok<V>)
}

return when {
isOk -> value
else -> throw error
Expand All @@ -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 {
contract {
returns() implies (this@getOrThrow is Ok<V>)
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
package com.github.michaelbull.result

import kotlin.jvm.JvmInline

/**
* 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> {
return Ok(value, null)
return Result(value)
}

/**
* 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> {
return Err(error, null)
return Result(Failure(error))
}

/**
Expand All @@ -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]).
*
* 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)
* - 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)
*/
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
public abstract val error: E
@Suppress("UNCHECKED_CAST")
public val value: V
get() = inlineValue as V

public abstract val isOk: Boolean
public abstract val isErr: Boolean

public abstract operator fun component1(): V?
public abstract operator fun component2(): E?
}
@Suppress("UNCHECKED_CAST")
public val error: E
get() = (inlineValue as Failure<E>).error

/**
* Represents a successful [Result], containing a [value].
*/
@Deprecated(
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()
}
public val isOk: Boolean
get() = inlineValue !is Failure<*>

override val isOk: Boolean = true
override val isErr: Boolean = false
public val isErr: Boolean
get() = inlineValue is Failure<*>

override fun component1(): V = value
override fun component2(): Nothing? = null

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false

other as Ok<*>

if (value != other.value) return false

return true
public operator fun component1(): V? {
return when {
isOk -> value
else -> null
}
}

override fun hashCode(): Int = value.hashCode()
override fun toString(): String = "Ok($value)"
}

/**
* Represents a failed [Result], containing an [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()
public operator fun component2(): E? {
return when {
isErr -> error
else -> null
}
}

override val isOk: Boolean = false
override val isErr: Boolean = true

override fun component1(): Nothing? = null
override fun component2(): E = error
override fun toString(): String {
return when {
isOk -> "Ok($value)"
else -> "Err($error)"
}
}
}

private class Failure<out E>(
val error: E,
) {
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 other is Failure<*> && error == other.error
}

return true
override fun hashCode(): Int {
return error.hashCode()
}

override fun hashCode(): Int = error.hashCode()
override fun toString(): String = "Err($error)"
override fun toString(): String {
return "Failure($error)"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@ public class UnwrapException(message: String) : Exception(message)
* @throws UnwrapException if this result [is an error][Result.isErr].
*/
public fun <V, E> Result<V, E>.unwrap(): V {
contract {
returns() implies (this@unwrap is Ok<V>)
}

return when {
isOk -> value
else -> throw UnwrapException("called Result.unwrap on an Err value $error")
Expand All @@ -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 {
contract {
callsInPlace(message, InvocationKind.AT_MOST_ONCE)
returns() implies (this@expect is Ok<V>)
}

return when {
Expand All @@ -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].
*/
public fun <V, E> Result<V, E>.unwrapError(): E {
contract {
returns() implies (this@unwrapError is Err<E>)
}

return when {
isErr -> error
else -> throw UnwrapException("called Result.unwrapError on an Ok value $value")
Expand All @@ -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 {
contract {
callsInPlace(message, InvocationKind.AT_MOST_ONCE)
returns() implies (this@expectError is Err<E>)
}

return when {
Expand Down

0 comments on commit 19af0bd

Please sign in to comment.