Skip to content

Commit 33dcfc4

Browse files
committed
Add update Result type to standard library.
1 parent 49ba9c5 commit 33dcfc4

File tree

4 files changed

+324
-0
lines changed

4 files changed

+324
-0
lines changed

stdlib/public/core/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ set(SWIFTLIB_ESSENTIAL
116116
ReflectionMirror.swift
117117
Repeat.swift
118118
REPL.swift
119+
Result.swift
119120
Reverse.swift
120121
Runtime.swift.gyb
121122
RuntimeFunctionCounters.swift

stdlib/public/core/GroupInfo.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,5 +220,8 @@
220220
"Comparable.swift",
221221
"Codable.swift",
222222
"MigrationSupport.swift"
223+
],
224+
"Result": [
225+
"Result.swift"
223226
]
224227
}

stdlib/public/core/Result.swift

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
/// A value that represents either a success or failure, capturing associated
14+
/// values in both cases.
15+
@_frozen
16+
public enum Result<Value, Error: Swift.Error> {
17+
/// A success, storing a `Value`.
18+
case value(Value)
19+
20+
/// A failure, storing an `Error`.
21+
case error(Error)
22+
23+
/// Evaluates the given transform closure when this `Result` instance is
24+
/// `.value`, passing the value as a parameter.
25+
///
26+
/// Use the `map` method with a closure that returns a non-`Result` value.
27+
///
28+
/// - Parameter transform: A closure that takes the successful value of the
29+
/// instance.
30+
/// - Returns: A new `Result` instance with the result of the transform, if
31+
/// it was applied.
32+
public func map<NewValue>(
33+
_ transform: (Value) -> NewValue
34+
) -> Result<NewValue, Error> {
35+
switch self {
36+
case let .value(value):
37+
return .value(transform(value))
38+
case let .error(error):
39+
return .error(error)
40+
}
41+
}
42+
43+
/// Evaluates the given transform closure when this `Result` instance is
44+
/// `.error`, passing the error as a parameter.
45+
///
46+
/// Use the `mapError` method with a closure that returns a non-`Result`
47+
/// value.
48+
///
49+
/// - Parameter transform: A closure that takes the failure value of the
50+
/// instance.
51+
/// - Returns: A new `Result` instance with the result of the transform, if
52+
/// it was applied.
53+
public func mapError<NewError>(
54+
_ transform: (Error) -> NewError
55+
) -> Result<Value, NewError> {
56+
switch self {
57+
case let .value(value):
58+
return .value(value)
59+
case let .error(error):
60+
return .error(transform(error))
61+
}
62+
}
63+
64+
/// Evaluates the given transform closure when this `Result` instance is
65+
/// `.value`, passing the value as a parameter and flattening the result.
66+
///
67+
/// - Parameter transform: A closure that takes the successful value of the
68+
/// instance.
69+
/// - Returns: A new `Result` instance, either from the transform or from
70+
/// the previous error value.
71+
public func flatMap<NewValue>(
72+
_ transform: (Value) -> Result<NewValue, Error>
73+
) -> Result<NewValue, Error> {
74+
switch self {
75+
case let .value(value):
76+
return transform(value)
77+
case let .error(error):
78+
return .error(error)
79+
}
80+
}
81+
82+
/// Evaluates the given transform closure when this `Result` instance is
83+
/// `.error`, passing the error as a parameter and flattening the result.
84+
///
85+
/// - Parameter transform: A closure that takes the error value of the
86+
/// instance.
87+
/// - Returns: A new `Result` instance, either from the transform or from
88+
/// the previous success value.
89+
public func flatMapError<NewError>(
90+
_ transform: (Error) -> Result<Value, NewError>
91+
) -> Result<Value, NewError> {
92+
switch self {
93+
case let .value(value):
94+
return .value(value)
95+
case let .error(error):
96+
return transform(error)
97+
}
98+
}
99+
100+
/// Unwraps the `Result` into a throwing expression.
101+
///
102+
/// - Returns: The success value, if the instance is a success.
103+
/// - Throws: The error value, if the instance is a failure.
104+
public func unwrapped() throws -> Value {
105+
switch self {
106+
case let .value(value):
107+
return value
108+
case let .error(error):
109+
throw error
110+
}
111+
}
112+
}
113+
114+
extension Result where Error == Swift.Error {
115+
/// Create an instance by capturing the output of a throwing closure.
116+
///
117+
/// - Parameter catching: A throwing closure to evaluate.
118+
@_transparent
119+
public init(catching body: () throws -> Value) {
120+
do {
121+
let value = try body()
122+
self = .value(value)
123+
} catch {
124+
self = .error(error)
125+
}
126+
}
127+
}
128+
129+
extension Result : Equatable where Value : Equatable, Error : Equatable { }
130+
131+
extension Result : Hashable where Value : Hashable, Error : Hashable {
132+
public func hash(into hasher: inout Hasher) {
133+
switch self {
134+
case let .value(value):
135+
hasher.combine(value)
136+
hasher.combine(Optional<Error>.none)
137+
case let .error(error):
138+
hasher.combine(Optional<Value>.none)
139+
hasher.combine(error)
140+
}
141+
}
142+
}

test/stdlib/Result.swift

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
// RUN: %target-run-simple-swift
2+
// REQUIRES: executable_test
3+
4+
import StdlibUnittest
5+
import Swift
6+
7+
let ResultTests = TestSuite("Result")
8+
9+
fileprivate enum Err: Error, Equatable {
10+
case err
11+
case derr
12+
}
13+
14+
fileprivate let string = "string"
15+
16+
fileprivate extension Result {
17+
var value: Value? {
18+
switch self {
19+
case let .value(value):
20+
return value
21+
case .error:
22+
return nil
23+
}
24+
25+
var error: Error? {
26+
switch self {
27+
case .value:
28+
return nil
29+
case let .error(error):
30+
return error
31+
}
32+
}
33+
}
34+
}
35+
36+
ResultTests.test("Construction") {
37+
let result1: Result<String, Err> = .value(string)
38+
let result2: Result<String, Err> = .error(.err)
39+
let string1: String? = {
40+
switch result1 {
41+
case let .value(string):
42+
return string
43+
case .error:
44+
expectUnreachable()
45+
return nil
46+
}
47+
}()
48+
let error: Err? = {
49+
switch result2 {
50+
case let .error(error):
51+
return error
52+
case .value:
53+
expectUnreachable()
54+
return nil
55+
}
56+
}()
57+
58+
expectEqual(string1, string)
59+
expectEqual(error, .err)
60+
}
61+
62+
ResultTests.test("Throwing Initialization and Unwrapping") {
63+
func notThrowing() throws -> String {
64+
return string
65+
}
66+
67+
func throwing() throws -> String {
68+
throw Err.err
69+
}
70+
71+
let result1 = Result { try throwing() }
72+
let result2 = Result { try notThrowing() }
73+
74+
// Kept getting enum case 'error' cannot be used as an instance member, so get value manually.
75+
switch result1 {
76+
case let .error(error):
77+
expectEqual(error as? Err, Err.err)
78+
case .value:
79+
expectUnreachable()
80+
}
81+
82+
expectEqual(result2.value, string)
83+
84+
do {
85+
_ = try result1.unwrapped()
86+
} catch let error as Err {
87+
expectEqual(error, Err.err)
88+
} catch {
89+
expectUnreachable()
90+
}
91+
92+
do {
93+
let unwrapped = try result2.unwrapped()
94+
expectEqual(unwrapped, string)
95+
} catch {
96+
expectUnreachable()
97+
}
98+
99+
// Test unwrapping strongly typed error.
100+
let result3 = Result<String, Err>.error(Err.err)
101+
do {
102+
_ = try result3.unwrapped()
103+
} catch let error as Err {
104+
expectEqual(error, Err.err)
105+
} catch {
106+
expectUnreachable()
107+
}
108+
}
109+
110+
ResultTests.test("Functional Transforms") {
111+
func transformDouble(_ int: Int) -> Int {
112+
return 2 * int
113+
}
114+
115+
func transformTriple(_ int: Int) -> Int {
116+
return 3 * int
117+
}
118+
119+
func transformError(_ err: Err) -> Err {
120+
if err == .err {
121+
return .derr
122+
} else {
123+
return .err
124+
}
125+
}
126+
127+
func resultValueTransform(_ int: Int) -> Result<Int, Err> {
128+
return .value(transformDouble(int))
129+
}
130+
131+
func resultErrorTransform(_ err: Err) -> Result<Int, Err> {
132+
return .error(transformError(err))
133+
}
134+
135+
let result1: Result<Int, Err> = .value(1)
136+
let newResult1 = result1.map(transformDouble)
137+
138+
expectEqual(newResult1, .value(2))
139+
140+
let result2: Result<Int, Err> = .error(.err)
141+
let newResult2 = result2.mapError(transformError)
142+
143+
expectEqual(newResult2, .error(.derr))
144+
145+
let result3: Result<Int, Err> = .value(1)
146+
let newResult3 = result3.flatMap(resultValueTransform)
147+
148+
expectEqual(newResult3, .value(2))
149+
150+
let result4: Result<Int, Err> = .error(.derr)
151+
let newResult4 = result4.flatMapError(resultErrorTransform)
152+
153+
expectEqual(newResult4, .error(.err))
154+
}
155+
156+
ResultTests.test("Equatable") {
157+
let result1: Result<Int, Err> = .value(1)
158+
let result2: Result<Int, Err> = .error(.err)
159+
160+
expectEqual(result1, .value(1))
161+
expectNotEqual(result1, .value(2))
162+
expectNotEqual(result1, .error(.err))
163+
expectNotEqual(result1, .error(.derr))
164+
165+
expectNotEqual(result2, .value(1))
166+
expectNotEqual(result2, .value(2))
167+
expectEqual(result2, .error(.err))
168+
expectNotEqual(result2, .error(.derr))
169+
}
170+
171+
ResultTests.test("Hashable") {
172+
let result1: Result<Int, Err> = .value(1)
173+
let result2: Result<Int, Err> = .value(2)
174+
let result3: Result<Int, Err> = .error(.err)
175+
checkHashable([result1, result2, result3], equalityOracle: { $0 == $1 })
176+
}
177+
178+
runAllTests()

0 commit comments

Comments
 (0)