Skip to content

Commit 10b8223

Browse files
Akka.Util: improve Result<T> (#7520)
* `Result<T>`: test equality members * make `Result<T>` nullable * reverted back to using fields * reverted back to using fields
1 parent 6a5c7c0 commit 10b8223

File tree

4 files changed

+79
-120
lines changed

4 files changed

+79
-120
lines changed

src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCore.DotNet.verified.txt

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5494,26 +5494,37 @@ namespace Akka.Util
54945494
public static Akka.Util.Option<T> op_Implicit(T value) { }
54955495
public static bool !=(Akka.Util.Option<T> left, Akka.Util.Option<T> right) { }
54965496
}
5497+
[System.Runtime.CompilerServices.NullableAttribute(0)]
54975498
public class static Result
54985499
{
5499-
public static Akka.Util.Result<T> Failure<T>(System.Exception exception) { }
5500-
public static Akka.Util.Result<T> From<T>(System.Func<T> func) { }
5501-
public static Akka.Util.Result<T> FromTask<T>(System.Threading.Tasks.Task<T> task) { }
5502-
public static Akka.Util.Result<T> Success<T>(T value) { }
5500+
[return: System.Runtime.CompilerServices.NullableAttribute(new byte[] {
5501+
0,
5502+
1})]
5503+
public static Akka.Util.Result<T> Failure<[System.Runtime.CompilerServices.NullableAttribute(2)] T>(System.Exception exception) { }
5504+
[return: System.Runtime.CompilerServices.NullableAttribute(new byte[] {
5505+
0,
5506+
1})]
5507+
public static Akka.Util.Result<T> From<[System.Runtime.CompilerServices.NullableAttribute(2)] T>(System.Func<T> func) { }
5508+
[return: System.Runtime.CompilerServices.NullableAttribute(new byte[] {
5509+
0,
5510+
1})]
5511+
public static Akka.Util.Result<T> FromTask<[System.Runtime.CompilerServices.NullableAttribute(2)] T>(System.Threading.Tasks.Task<T> task) { }
5512+
[return: System.Runtime.CompilerServices.NullableAttribute(new byte[] {
5513+
0,
5514+
1})]
5515+
public static Akka.Util.Result<T> Success<[System.Runtime.CompilerServices.NullableAttribute(2)] T>(T value) { }
55035516
}
5504-
public struct Result<T> : System.IEquatable<Akka.Util.Result<T>>
5517+
[System.Runtime.CompilerServices.IsReadOnlyAttribute()]
5518+
public struct Result<[System.Runtime.CompilerServices.NullableAttribute(2)] T> : System.IEquatable<Akka.Util.Result<T>>
55055519
{
5520+
[System.Runtime.CompilerServices.NullableAttribute(2)]
55065521
public readonly System.Exception Exception;
55075522
public readonly bool IsSuccess;
5523+
[System.Runtime.CompilerServices.NullableAttribute(2)]
55085524
public readonly T Value;
55095525
public Result(T value) { }
55105526
public Result(System.Exception exception) { }
5511-
public bool Equals(Akka.Util.Result<T> other) { }
5512-
public override bool Equals(object obj) { }
5513-
public override int GetHashCode() { }
55145527
public override string ToString() { }
5515-
public static bool ==(Akka.Util.Result<T> left, Akka.Util.Result<T> right) { }
5516-
public static bool !=(Akka.Util.Result<T> left, Akka.Util.Result<T> right) { }
55175528
}
55185529
public class Right<T>
55195530
{

src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCore.Net.verified.txt

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5483,26 +5483,36 @@ namespace Akka.Util
54835483
public static Akka.Util.Option<T> op_Implicit(T value) { }
54845484
public static bool !=(Akka.Util.Option<T> left, Akka.Util.Option<T> right) { }
54855485
}
5486+
[System.Runtime.CompilerServices.NullableAttribute(0)]
54865487
public class static Result
54875488
{
5488-
public static Akka.Util.Result<T> Failure<T>(System.Exception exception) { }
5489-
public static Akka.Util.Result<T> From<T>(System.Func<T> func) { }
5490-
public static Akka.Util.Result<T> FromTask<T>(System.Threading.Tasks.Task<T> task) { }
5491-
public static Akka.Util.Result<T> Success<T>(T value) { }
5489+
[return: System.Runtime.CompilerServices.NullableAttribute(new byte[] {
5490+
0,
5491+
1})]
5492+
public static Akka.Util.Result<T> Failure<[System.Runtime.CompilerServices.NullableAttribute(2)] T>(System.Exception exception) { }
5493+
[return: System.Runtime.CompilerServices.NullableAttribute(new byte[] {
5494+
0,
5495+
1})]
5496+
public static Akka.Util.Result<T> From<[System.Runtime.CompilerServices.NullableAttribute(2)] T>(System.Func<T> func) { }
5497+
[return: System.Runtime.CompilerServices.NullableAttribute(new byte[] {
5498+
0,
5499+
1})]
5500+
public static Akka.Util.Result<T> FromTask<[System.Runtime.CompilerServices.NullableAttribute(2)] T>(System.Threading.Tasks.Task<T> task) { }
5501+
[return: System.Runtime.CompilerServices.NullableAttribute(new byte[] {
5502+
0,
5503+
1})]
5504+
public static Akka.Util.Result<T> Success<[System.Runtime.CompilerServices.NullableAttribute(2)] T>(T value) { }
54925505
}
5493-
public struct Result<T> : System.IEquatable<Akka.Util.Result<T>>
5506+
public struct Result<[System.Runtime.CompilerServices.NullableAttribute(2)] T> : System.IEquatable<Akka.Util.Result<T>>
54945507
{
5508+
[System.Runtime.CompilerServices.NullableAttribute(2)]
54955509
public readonly System.Exception Exception;
54965510
public readonly bool IsSuccess;
5511+
[System.Runtime.CompilerServices.NullableAttribute(2)]
54975512
public readonly T Value;
54985513
public Result(T value) { }
54995514
public Result(System.Exception exception) { }
5500-
public bool Equals(Akka.Util.Result<T> other) { }
5501-
public override bool Equals(object obj) { }
5502-
public override int GetHashCode() { }
55035515
public override string ToString() { }
5504-
public static bool ==(Akka.Util.Result<T> left, Akka.Util.Result<T> right) { }
5505-
public static bool !=(Akka.Util.Result<T> left, Akka.Util.Result<T> right) { }
55065516
}
55075517
public class Right<T>
55085518
{

src/core/Akka.Tests/Util/ResultSpec.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,27 @@ public void IncompleteTaskResult()
118118
.Should().Throw<ArgumentException>().WithMessage("Task is not completed.*");
119119
}
120120

121+
[Fact]
122+
public void ResultEqualitySpec()
123+
{
124+
var result1 = Result.Success(1);
125+
var result2 = Result.Success(1);
126+
var exception = new TestException("BOOM"); // equality by value does not work for exceptions
127+
var result3 = Result.Failure<int>(exception);
128+
var result4 = Result.Failure<int>(exception);
129+
var result5 = Result.Success("foo");
130+
var result6 = Result.Success("bar");
131+
var result51 = Result.Success("foo");
132+
133+
result1.Equals(result2).Should().BeTrue();
134+
result1.Equals(result3).Should().BeFalse();
135+
result3.Equals(result4).Should().BeTrue();
136+
result5.Equals(result51).Should().BeTrue();
137+
(result5 == result51).Should().BeTrue(); // test operator overloads
138+
(result5 == result6).Should().BeFalse(); // test operator overloads
139+
(result5 != result6).Should().BeTrue(); // test operator overloads
140+
}
141+
121142
private static Task<int> CompletedTask(int n)
122143
{
123144
var tcs = new TaskCompletionSource<int>();

src/core/Akka/Util/Result.cs

Lines changed: 17 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -7,134 +7,60 @@
77

88
using System;
99
using System.Threading.Tasks;
10+
#nullable enable
1011

1112
namespace Akka.Util
1213
{
13-
//A generic type can't have a explicit layout
14-
//[StructLayout(LayoutKind.Explicit)]
1514
/// <summary>
16-
/// TBD
15+
/// A result type frequently used inside Akka.Streams and elsewhere.
1716
/// </summary>
18-
/// <typeparam name="T">TBD</typeparam>
19-
public struct Result<T> : IEquatable<Result<T>>
17+
public readonly record struct Result<T>
2018
{
21-
//[FieldOffset(0)]
2219
/// <summary>
23-
/// TBD
20+
/// <c>true</c> if the result is successful, <c>false</c> otherwise.
2421
/// </summary>
2522
public readonly bool IsSuccess;
26-
//[FieldOffset(1)]
27-
/// <summary>
28-
/// TBD
29-
/// </summary>
30-
public readonly T Value;
31-
//[FieldOffset(1)]
23+
3224
/// <summary>
33-
/// TBD
25+
/// <c>null</c> when <see cref="IsSuccess"/> is <c>false</c>.
3426
/// </summary>
35-
public readonly Exception Exception;
27+
public readonly T? Value;
3628

3729
/// <summary>
38-
/// TBD
30+
/// <c>null</c> when <see cref="IsSuccess"/> is <c>true</c>.
3931
/// </summary>
40-
/// <param name="value">TBD</param>
32+
public readonly Exception? Exception;
33+
4134
public Result(T value) : this()
4235
{
4336
IsSuccess = true;
4437
Value = value;
4538
}
46-
/// <summary>
47-
/// TBD
48-
/// </summary>
49-
/// <param name="exception">TBD</param>
39+
5040
public Result(Exception exception) : this()
5141
{
5242
IsSuccess = false;
5343
Exception = exception;
5444
}
55-
56-
57-
public bool Equals(Result<T> other)
58-
{
59-
if (IsSuccess ^ other.IsSuccess) return false;
60-
return IsSuccess
61-
? Equals(Value, other.Value)
62-
: Equals(Exception, other.Exception);
63-
}
64-
6545

66-
public override bool Equals(object obj)
67-
{
68-
if (obj is Result<T> result) return Equals(result);
69-
return false;
70-
}
71-
72-
73-
public override int GetHashCode()
74-
{
75-
return IsSuccess
76-
? (Value == null ? 0 : Value.GetHashCode())
77-
: (Exception == null ? 0 : Exception.GetHashCode());
78-
}
79-
80-
/// <summary>
81-
/// Compares two specified <see cref="Result{T}"/> for equality.
82-
/// </summary>
83-
/// <param name="left">The first <see cref="Result{T}"/> used for comparison</param>
84-
/// <param name="right">The second <see cref="Result{T}"/> used for comparison</param>
85-
/// <returns><c>true</c> if both <see cref="Result{T}"/> are equal; otherwise <c>false</c></returns>
86-
public static bool operator ==(Result<T> left, Result<T> right)
87-
{
88-
return left.Equals(right);
89-
}
90-
91-
/// <summary>
92-
/// Compares two specified <see cref="Result{T}"/> for inequality.
93-
/// </summary>
94-
/// <param name="left">The first <see cref="Result{T}"/> used for comparison</param>
95-
/// <param name="right">The second <see cref="Result{T}"/> used for comparison</param>
96-
/// <returns><c>true</c> if both <see cref="Result{T}"/> are not equal; otherwise <c>false</c></returns>
97-
public static bool operator !=(Result<T> left, Result<T> right)
98-
{
99-
return !(left == right);
100-
}
101-
10246
public override string ToString() => IsSuccess ? $"Success ({Value})" : $"Failure ({Exception})";
10347
}
10448

10549
/// <summary>
106-
/// TBD
50+
/// Helper methods for creating <see cref="Result{T}"/> instances.
10751
/// </summary>
10852
public static class Result
10953
{
110-
/// <summary>
111-
/// TBD
112-
/// </summary>
113-
/// <typeparam name="T">TBD</typeparam>
114-
/// <param name="value">TBD</param>
115-
/// <returns>TBD</returns>
11654
public static Result<T> Success<T>(T value)
11755
{
11856
return new Result<T>(value);
11957
}
120-
121-
/// <summary>
122-
/// TBD
123-
/// </summary>
124-
/// <typeparam name="T">TBD</typeparam>
125-
/// <param name="exception">TBD</param>
126-
/// <returns>TBD</returns>
58+
12759
public static Result<T> Failure<T>(Exception exception)
12860
{
12961
return new Result<T>(exception);
13062
}
131-
132-
/// <summary>
133-
/// TBD
134-
/// </summary>
135-
/// <typeparam name="T">TBD</typeparam>
136-
/// <param name="task">TBD</param>
137-
/// <returns>TBD</returns>
63+
13864
public static Result<T> FromTask<T>(Task<T> task)
13965
{
14066
if(!task.IsCompleted)
@@ -143,7 +69,7 @@ public static Result<T> FromTask<T>(Task<T> task)
14369
if(task.Exception is not null)
14470
return new Result<T>(task.Exception);
14571

146-
if (task.IsCanceled && task.Exception is null)
72+
if (task is { IsCanceled: true, Exception: null })
14773
{
14874
try
14975
{
@@ -157,18 +83,12 @@ public static Result<T> FromTask<T>(Task<T> task)
15783
throw new InvalidOperationException("Should never reach this line!");
15884
}
15985

160-
if(task.IsFaulted && task.Exception is null)
86+
if(task is { IsFaulted: true, Exception: null })
16187
throw new InvalidOperationException("Should never happen! something is wrong with .NET Task code!");
16288

16389
return new Result<T>(task.Result);
16490
}
165-
166-
/// <summary>
167-
/// TBD
168-
/// </summary>
169-
/// <typeparam name="T">TBD</typeparam>
170-
/// <param name="func">TBD</param>
171-
/// <returns>TBD</returns>
91+
17292
public static Result<T> From<T>(Func<T> func)
17393
{
17494
try
@@ -181,8 +101,5 @@ public static Result<T> From<T>(Func<T> func)
181101
return new Result<T>(e);
182102
}
183103
}
184-
185-
186-
187104
}
188105
}

0 commit comments

Comments
 (0)