-
Notifications
You must be signed in to change notification settings - Fork 64
Overhead
The base Result
class is modelled as an inline value class.
Calls to Ok
and Err
do not create a new instance of the Ok
/Err
objects - instead these are top-level functions that return a type of Result
. This achieves code that produces zero object allocations when on the "happy path", i.e. anything that returns an Ok(value)
.
The Err(error)
function does allocate a new object each call by internally wrapping the provided error
with a new instance of a Failure
object. This Failure
class is an internal implementation detail and not exposed to consumers. As a call to Err
is usually a terminal state, occurring at the end of a chain, the allocation of a new object is unlikely to cause a lot of GC pressure unless a function that produces an Err
is called in a tight loop.
Below is an example of the bytecode decompiled to Java. Despite three calls to Ok
and one call to Err
, there are zero object allocations on the happy path, and just one on the unhappy path.
Kotlin:
sealed interface Error
object ErrorOne : Error
object ErrorTwo : Error
fun one(): Result<Int, ErrorOne> {
return Ok(50)
}
fun two(): Int {
return 100
}
fun three(int: Int): Result<Int, Error> {
return Ok(int + 25)
}
fun example() {
val result = one()
.map { two() }
.mapError { ErrorTwo }
.andThen(::three)
.toString()
println(result)
}
Decompiled Java:
public final class Example {
@NotNull
public static final Example INSTANCE = new Example();
private Example() {
}
@NotNull
public final Object one() {
return this.Ok(50);
}
public final int two() {
return 100;
}
@NotNull
public final Object three(int var1) {
return this.Ok(var1 + 25);
}
public final void example() {
Object $this$map_u2dj2AeeQ8$iv = this.one();
Object var10000;
if (Result.isOk_impl($this$map_u2dj2AeeQ8$iv)) {
var10000 = this.Ok(INSTANCE.two());
} else {
var10000 = $this$map_u2dj2AeeQ8$iv;
}
Object $this$mapError_u2dj2AeeQ8$iv = var10000;
if (Result.isErr_impl($this$mapError_u2dj2AeeQ8$iv)) {
var10000 = this.Err(ErrorTwo.INSTANCE); // object allocation (1)
} else {
var10000 = $this$mapError_u2dj2AeeQ8$iv;
}
Object $this$andThen_u2dj2AeeQ8$iv = var10000;
if (Result.isOk_impl($this$andThen_u2dj2AeeQ8$iv)) {
int p0 = ((Number) Result.getValue_impl($this$andThen_u2dj2AeeQ8$iv)).intValue();
var10000 = this.three(p0);
} else {
var10000 = $this$andThen_u2dj2AeeQ8$iv;
}
String result = Result.toString_impl(var10000);
System.out.println(result);
}
@NotNull
public final <V> Object Ok(V value) {
return Result.constructor_impl(value);
}
@NotNull
public final <E> Object Err(E error) {
return Result.constructor_impl(new Failure(error));
}
public static final class Result<V, E> {
@Nullable
private final Object inlineValue;
public static final V getValue_impl(Object arg0) {
return arg0;
}
public static final E getError_impl(Object arg0) {
Intrinsics.checkNotNull(arg0, "null cannot be cast to non-null type Failure<E of Result>");
return ((Failure) arg0).getError();
}
public static final boolean isOk_impl(Object arg0) {
return !(arg0 instanceof Failure);
}
public static final boolean isErr_impl(Object arg0) {
return arg0 instanceof Failure;
}
@NotNull
public static String toString_impl(Object arg0) {
return isOk_impl(arg0) ? "Ok(" + getValue_impl(arg0) + ')' : "Err(" + getError_impl(arg0) + ')';
}
@NotNull
public String toString() {
return toString_impl(this.inlineValue);
}
public static int hashCode_impl(Object arg0) {
return arg0 == null ? 0 : arg0.hashCode();
}
public int hashCode() {
return hashCode_impl(this.inlineValue);
}
public static boolean equals_impl(Object arg0, Object other) {
if (!(other instanceof Result)) {
return false;
} else {
return Intrinsics.areEqual(arg0, ((Result) other).unbox_impl());
}
}
public boolean equals(Object other) {
return equals_impl(this.inlineValue, other);
}
private Result(Object inlineValue) {
this.inlineValue = inlineValue;
}
@NotNull
public static <V, E> Object constructor_impl(@Nullable Object inlineValue) {
return inlineValue;
}
public static final Result box_impl(Object v) {
return new Result(v);
}
public final Object unbox_impl() {
return this.inlineValue;
}
public static final boolean equals_impl0(Object p1, Object p2) {
return Intrinsics.areEqual(p1, p2);
}
}
static final class Failure<E> {
private final E error;
public Failure(E error) {
this.error = error;
}
public final E getError() {
return this.error;
}
public boolean equals(@Nullable Object other) {
return other instanceof Failure && Intrinsics.areEqual(this.error, ((Failure)other).error);
}
public int hashCode() {
Object var10000 = this.error;
return var10000 != null ? var10000.hashCode() : 0;
}
@NotNull
public String toString() {
return "Failure(" + this.error + ')';
}
}
}