Skip to content

Commit 47740c0

Browse files
jshiermoiseev
authored andcommitted
Add update Result type to standard library.
(cherry picked from commit 33dcfc4)
1 parent e5f1450 commit 47740c0

File tree

5 files changed

+361
-0
lines changed

5 files changed

+361
-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
@@ -223,5 +223,8 @@
223223
"Comparable.swift",
224224
"Codable.swift",
225225
"MigrationSupport.swift"
226+
],
227+
"Result": [
228+
"Result.swift"
226229
]
227230
}

stdlib/public/core/Result.swift

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2018 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 a failure, including an
14+
/// associated value in each case.
15+
@_frozen
16+
public enum Result<Success, Failure: Error> {
17+
/// A success, storing a `Success` value.
18+
case success(Success)
19+
20+
/// A failure, storing a `Failure` value.
21+
case failure(Failure)
22+
23+
/// Returns a new result, mapping any success value using the given
24+
/// transformation.
25+
///
26+
/// Use this method when you need to transform the value of a `Result`
27+
/// instance when it represents a success. The following example transforms
28+
/// the integer success value of a result into a string:
29+
///
30+
/// func getNextInteger() -> Result<Int, Error> { ... }
31+
///
32+
/// let integerResult = getNextInteger()
33+
/// // integerResult == .success(5)
34+
/// let stringResult = integerResult.map({ String($0) })
35+
/// // stringResult == .success("5")
36+
///
37+
/// - Parameter transform: A closure that takes the success value of this
38+
/// instance.
39+
/// - Returns: A `Result` instance with the result of evaluating `transform`
40+
/// as the new success value if this instance represents a success.
41+
public func map<NewSuccess>(
42+
_ transform: (Success) -> NewSuccess
43+
) -> Result<NewSuccess, Failure> {
44+
switch self {
45+
case let .success(success):
46+
return .success(transform(success))
47+
case let .failure(failure):
48+
return .failure(failure)
49+
}
50+
}
51+
52+
/// Returns a new result, mapping any failure value using the given
53+
/// transformation.
54+
///
55+
/// Use this method when you need to transform the value of a `Result`
56+
/// instance when it represents a failure. The following example transforms
57+
/// the error value of a result by wrapping it in a custom `Error` type:
58+
///
59+
/// struct DatedError: Error {
60+
/// var error: Error
61+
/// var date: Date
62+
///
63+
/// init(_ error: Error) {
64+
/// self.error = error
65+
/// self.date = Date()
66+
/// }
67+
/// }
68+
///
69+
/// let result: Result<Int, Error> = ...
70+
/// // result == .failure(<error value>)
71+
/// let resultWithDatedError = result.mapError({ e in DatedError(e) })
72+
/// // result == .failure(DatedError(error: <error value>, date: <date>))
73+
///
74+
/// - Parameter transform: A closure that takes the failure value of the
75+
/// instance.
76+
/// - Returns: A `Result` instance with the result of evaluating `transform`
77+
/// as the new failure value if this instance represents a failure.
78+
public func mapError<NewFailure>(
79+
_ transform: (Failure) -> NewFailure
80+
) -> Result<Success, NewFailure> {
81+
switch self {
82+
case let .success(success):
83+
return .success(success)
84+
case let .failure(failure):
85+
return .failure(transform(failure))
86+
}
87+
}
88+
89+
/// Returns a new result, mapping any success value using the given
90+
/// transformation and unwrapping the produced result.
91+
///
92+
/// - Parameter transform: A closure that takes the success value of the
93+
/// instance.
94+
/// - Returns: A `Result` instance with the result of evaluating `transform`
95+
/// as the new failure value if this instance represents a failure.
96+
public func flatMap<NewSuccess>(
97+
_ transform: (Success) -> Result<NewSuccess, Failure>
98+
) -> Result<NewSuccess, Failure> {
99+
switch self {
100+
case let .success(success):
101+
return transform(success)
102+
case let .failure(failure):
103+
return .failure(failure)
104+
}
105+
}
106+
107+
/// Returns a new result, mapping any failure value using the given
108+
/// transformation and unwrapping the produced result.
109+
///
110+
/// - Parameter transform: A closure that takes the failure value of the
111+
/// instance.
112+
/// - Returns: A `Result` instance, either from the closure or the previous
113+
/// `.success`.
114+
public func flatMapError<NewFailure>(
115+
_ transform: (Failure) -> Result<Success, NewFailure>
116+
) -> Result<Success, NewFailure> {
117+
switch self {
118+
case let .success(success):
119+
return .success(success)
120+
case let .failure(failure):
121+
return transform(failure)
122+
}
123+
}
124+
125+
/// Returns the success value as a throwing expression.
126+
///
127+
/// Use this method to retrieve the value of this result if it represents a
128+
/// success, or to catch the value if it represents a failure.
129+
///
130+
/// let integerResult: Result<Int, Error> = .success(5)
131+
/// do {
132+
/// let value = try integerResult.get()
133+
/// print("The value is \(value).")
134+
/// } catch error {
135+
/// print("Error retrieving the value: \(error)")
136+
/// }
137+
/// // Prints "The value is 5."
138+
///
139+
/// - Returns: The success value, if the instance represent a success.
140+
/// - Throws: The failure value, if the instance represents a failure.
141+
public func get() throws -> Success {
142+
switch self {
143+
case let .success(success):
144+
return success
145+
case let .failure(failure):
146+
throw failure
147+
}
148+
}
149+
}
150+
151+
extension Result where Failure == Swift.Error {
152+
/// Creates a new result by evaluating a throwing closure, capturing the
153+
/// returned value as a success, or any thrown error as a failure.
154+
///
155+
/// - Parameter body: A throwing closure to evaluate.
156+
@_transparent
157+
public init(catching body: () throws -> Success) {
158+
do {
159+
self = .success(try body())
160+
} catch {
161+
self = .failure(error)
162+
}
163+
}
164+
}
165+
166+
extension Result: Equatable where Success: Equatable, Failure: Equatable { }
167+
168+
extension Result: Hashable where Success: Hashable, Failure: Hashable { }

test/multifile/typealias/two-modules/main.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
// RUN: %target-build-swift -g %S/main.swift %t/linker/library.o -I %t/linker/ -L %t/linker/ -o %t/linker/main
1111

1212
import library
13+
import enum library.Result
1314

1415
func testFunction<T>(withCompletion completion: (Result<T, Error>) -> Void) { }
1516
testFunction { (result: GenericResult<Int>) in }

test/stdlib/Result.swift

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
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 success: Success? {
18+
switch self {
19+
case let .success(success):
20+
return success
21+
case .failure:
22+
return nil
23+
}
24+
}
25+
26+
var failure: Failure? {
27+
switch self {
28+
case .success:
29+
return nil
30+
case let .failure(failure):
31+
return failure
32+
}
33+
}
34+
}
35+
36+
ResultTests.test("Construction") {
37+
let result1: Result<String, Err> = .success(string)
38+
let result2: Result<String, Err> = .failure(.err)
39+
let string1: String? = {
40+
switch result1 {
41+
case let .success(string):
42+
return string
43+
case .failure:
44+
expectUnreachable()
45+
return nil
46+
}
47+
}()
48+
let error: Err? = {
49+
switch result2 {
50+
case let .failure(failure):
51+
return failure
52+
case .success:
53+
expectUnreachable()
54+
return nil
55+
}
56+
}()
57+
58+
expectEqual(string, string1)
59+
expectEqual(.err, error)
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+
expectEqual(result1.failure as? Err, Err.err)
75+
expectEqual(result2.success, string)
76+
77+
do {
78+
_ = try result1.get()
79+
} catch let error as Err {
80+
expectEqual(error, Err.err)
81+
} catch {
82+
expectUnreachable()
83+
}
84+
85+
do {
86+
let unwrapped = try result2.get()
87+
expectEqual(unwrapped, string)
88+
} catch {
89+
expectUnreachable()
90+
}
91+
92+
// Test unwrapping strongly typed error.
93+
let result3 = Result<String, Err>.failure(Err.err)
94+
do {
95+
_ = try result3.get()
96+
} catch let error as Err {
97+
expectEqual(error, Err.err)
98+
} catch {
99+
expectUnreachable()
100+
}
101+
}
102+
103+
ResultTests.test("Functional Transforms") {
104+
func transformDouble(_ int: Int) -> Int {
105+
return 2 * int
106+
}
107+
108+
func transformTriple(_ int: Int) -> Int {
109+
return 3 * int
110+
}
111+
112+
func transformError(_ err: Err) -> Err {
113+
if err == .err {
114+
return .derr
115+
} else {
116+
return .err
117+
}
118+
}
119+
120+
func resultValueTransform(_ int: Int) -> Result<Int, Err> {
121+
return .success(transformDouble(int))
122+
}
123+
124+
func resultErrorTransform(_ err: Err) -> Result<Int, Err> {
125+
return .failure(transformError(err))
126+
}
127+
128+
let result1: Result<Int, Err> = .success(1)
129+
let newResult1 = result1.map(transformDouble)
130+
131+
expectEqual(newResult1, .success(2))
132+
133+
let result2: Result<Int, Err> = .failure(.err)
134+
let newResult2 = result2.mapError(transformError)
135+
136+
expectEqual(newResult2, .failure(.derr))
137+
138+
let result3: Result<Int, Err> = .success(1)
139+
let newResult3 = result3.flatMap(resultValueTransform)
140+
141+
expectEqual(newResult3, .success(2))
142+
143+
let result4: Result<Int, Err> = .failure(.derr)
144+
let newResult4 = result4.flatMapError(resultErrorTransform)
145+
146+
expectEqual(newResult4, .failure(.err))
147+
}
148+
149+
ResultTests.test("Equatable") {
150+
let result1: Result<Int, Err> = .success(1)
151+
let result2: Result<Int, Err> = .failure(.err)
152+
153+
expectEqual(result1, .success(1))
154+
expectNotEqual(result1, .success(2))
155+
expectNotEqual(result1, .failure(.err))
156+
expectNotEqual(result1, .failure(.derr))
157+
158+
expectNotEqual(result2, .success(1))
159+
expectNotEqual(result2, .success(2))
160+
expectEqual(result2, .failure(.err))
161+
expectNotEqual(result2, .failure(.derr))
162+
163+
let confusables: [Result<Err, Err>] = [
164+
.success(.err),
165+
.success(.derr),
166+
.failure(.err),
167+
.failure(.derr)
168+
]
169+
170+
checkEquatable(confusables, oracle: { $0 == $1 })
171+
}
172+
173+
ResultTests.test("Hashable") {
174+
let result1: Result<Int, Err> = .success(1)
175+
let result2: Result<Int, Err> = .success(2)
176+
let result3: Result<Int, Err> = .failure(.err)
177+
checkHashable([result1, result2, result3], equalityOracle: { $0 == $1 })
178+
179+
let confusables: [Result<Err, Err>] = [
180+
.success(.err),
181+
.success(.derr),
182+
.failure(.err),
183+
.failure(.derr)
184+
]
185+
checkHashable(confusables, equalityOracle: { $0 == $1 })
186+
}
187+
188+
runAllTests()

0 commit comments

Comments
 (0)