Skip to content

Commit

Permalink
Merge pull request #21225 from moiseev/result-5
Browse files Browse the repository at this point in the history
[5.0] Add Result type to standard library.
  • Loading branch information
DougGregor committed Dec 17, 2018
2 parents c88654e + 47740c0 commit b3ce0c9
Show file tree
Hide file tree
Showing 5 changed files with 361 additions and 0 deletions.
1 change: 1 addition & 0 deletions stdlib/public/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ set(SWIFTLIB_ESSENTIAL
ReflectionMirror.swift
Repeat.swift
REPL.swift
Result.swift
Reverse.swift
Runtime.swift.gyb
RuntimeFunctionCounters.swift
Expand Down
3 changes: 3 additions & 0 deletions stdlib/public/core/GroupInfo.json
Original file line number Diff line number Diff line change
Expand Up @@ -223,5 +223,8 @@
"Comparable.swift",
"Codable.swift",
"MigrationSupport.swift"
],
"Result": [
"Result.swift"
]
}
168 changes: 168 additions & 0 deletions stdlib/public/core/Result.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2018 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

/// A value that represents either a success or a failure, including an
/// associated value in each case.
@_frozen
public enum Result<Success, Failure: Error> {
/// A success, storing a `Success` value.
case success(Success)

/// A failure, storing a `Failure` value.
case failure(Failure)

/// Returns a new result, mapping any success value using the given
/// transformation.
///
/// Use this method when you need to transform the value of a `Result`
/// instance when it represents a success. The following example transforms
/// the integer success value of a result into a string:
///
/// func getNextInteger() -> Result<Int, Error> { ... }
///
/// let integerResult = getNextInteger()
/// // integerResult == .success(5)
/// let stringResult = integerResult.map({ String($0) })
/// // stringResult == .success("5")
///
/// - Parameter transform: A closure that takes the success value of this
/// instance.
/// - Returns: A `Result` instance with the result of evaluating `transform`
/// as the new success value if this instance represents a success.
public func map<NewSuccess>(
_ transform: (Success) -> NewSuccess
) -> Result<NewSuccess, Failure> {
switch self {
case let .success(success):
return .success(transform(success))
case let .failure(failure):
return .failure(failure)
}
}

/// Returns a new result, mapping any failure value using the given
/// transformation.
///
/// Use this method when you need to transform the value of a `Result`
/// instance when it represents a failure. The following example transforms
/// the error value of a result by wrapping it in a custom `Error` type:
///
/// struct DatedError: Error {
/// var error: Error
/// var date: Date
///
/// init(_ error: Error) {
/// self.error = error
/// self.date = Date()
/// }
/// }
///
/// let result: Result<Int, Error> = ...
/// // result == .failure(<error value>)
/// let resultWithDatedError = result.mapError({ e in DatedError(e) })
/// // result == .failure(DatedError(error: <error value>, date: <date>))
///
/// - Parameter transform: A closure that takes the failure value of the
/// instance.
/// - Returns: A `Result` instance with the result of evaluating `transform`
/// as the new failure value if this instance represents a failure.
public func mapError<NewFailure>(
_ transform: (Failure) -> NewFailure
) -> Result<Success, NewFailure> {
switch self {
case let .success(success):
return .success(success)
case let .failure(failure):
return .failure(transform(failure))
}
}

/// Returns a new result, mapping any success value using the given
/// transformation and unwrapping the produced result.
///
/// - Parameter transform: A closure that takes the success value of the
/// instance.
/// - Returns: A `Result` instance with the result of evaluating `transform`
/// as the new failure value if this instance represents a failure.
public func flatMap<NewSuccess>(
_ transform: (Success) -> Result<NewSuccess, Failure>
) -> Result<NewSuccess, Failure> {
switch self {
case let .success(success):
return transform(success)
case let .failure(failure):
return .failure(failure)
}
}

/// Returns a new result, mapping any failure value using the given
/// transformation and unwrapping the produced result.
///
/// - Parameter transform: A closure that takes the failure value of the
/// instance.
/// - Returns: A `Result` instance, either from the closure or the previous
/// `.success`.
public func flatMapError<NewFailure>(
_ transform: (Failure) -> Result<Success, NewFailure>
) -> Result<Success, NewFailure> {
switch self {
case let .success(success):
return .success(success)
case let .failure(failure):
return transform(failure)
}
}

/// Returns the success value as a throwing expression.
///
/// Use this method to retrieve the value of this result if it represents a
/// success, or to catch the value if it represents a failure.
///
/// let integerResult: Result<Int, Error> = .success(5)
/// do {
/// let value = try integerResult.get()
/// print("The value is \(value).")
/// } catch error {
/// print("Error retrieving the value: \(error)")
/// }
/// // Prints "The value is 5."
///
/// - Returns: The success value, if the instance represent a success.
/// - Throws: The failure value, if the instance represents a failure.
public func get() throws -> Success {
switch self {
case let .success(success):
return success
case let .failure(failure):
throw failure
}
}
}

extension Result where Failure == Swift.Error {
/// Creates a new result by evaluating a throwing closure, capturing the
/// returned value as a success, or any thrown error as a failure.
///
/// - Parameter body: A throwing closure to evaluate.
@_transparent
public init(catching body: () throws -> Success) {
do {
self = .success(try body())
} catch {
self = .failure(error)
}
}
}

extension Result: Equatable where Success: Equatable, Failure: Equatable { }

extension Result: Hashable where Success: Hashable, Failure: Hashable { }
1 change: 1 addition & 0 deletions test/multifile/typealias/two-modules/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
// RUN: %target-build-swift -g %S/main.swift %t/linker/library.o -I %t/linker/ -L %t/linker/ -o %t/linker/main

import library
import enum library.Result

func testFunction<T>(withCompletion completion: (Result<T, Error>) -> Void) { }
testFunction { (result: GenericResult<Int>) in }
188 changes: 188 additions & 0 deletions test/stdlib/Result.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
// RUN: %target-run-simple-swift
// REQUIRES: executable_test

import StdlibUnittest
import Swift

let ResultTests = TestSuite("Result")

fileprivate enum Err: Error, Equatable {
case err
case derr
}

fileprivate let string = "string"

fileprivate extension Result {
var success: Success? {
switch self {
case let .success(success):
return success
case .failure:
return nil
}
}

var failure: Failure? {
switch self {
case .success:
return nil
case let .failure(failure):
return failure
}
}
}

ResultTests.test("Construction") {
let result1: Result<String, Err> = .success(string)
let result2: Result<String, Err> = .failure(.err)
let string1: String? = {
switch result1 {
case let .success(string):
return string
case .failure:
expectUnreachable()
return nil
}
}()
let error: Err? = {
switch result2 {
case let .failure(failure):
return failure
case .success:
expectUnreachable()
return nil
}
}()

expectEqual(string, string1)
expectEqual(.err, error)
}

ResultTests.test("Throwing Initialization and Unwrapping") {
func notThrowing() throws -> String {
return string
}

func throwing() throws -> String {
throw Err.err
}

let result1 = Result { try throwing() }
let result2 = Result { try notThrowing() }

expectEqual(result1.failure as? Err, Err.err)
expectEqual(result2.success, string)

do {
_ = try result1.get()
} catch let error as Err {
expectEqual(error, Err.err)
} catch {
expectUnreachable()
}

do {
let unwrapped = try result2.get()
expectEqual(unwrapped, string)
} catch {
expectUnreachable()
}

// Test unwrapping strongly typed error.
let result3 = Result<String, Err>.failure(Err.err)
do {
_ = try result3.get()
} catch let error as Err {
expectEqual(error, Err.err)
} catch {
expectUnreachable()
}
}

ResultTests.test("Functional Transforms") {
func transformDouble(_ int: Int) -> Int {
return 2 * int
}

func transformTriple(_ int: Int) -> Int {
return 3 * int
}

func transformError(_ err: Err) -> Err {
if err == .err {
return .derr
} else {
return .err
}
}

func resultValueTransform(_ int: Int) -> Result<Int, Err> {
return .success(transformDouble(int))
}

func resultErrorTransform(_ err: Err) -> Result<Int, Err> {
return .failure(transformError(err))
}

let result1: Result<Int, Err> = .success(1)
let newResult1 = result1.map(transformDouble)

expectEqual(newResult1, .success(2))

let result2: Result<Int, Err> = .failure(.err)
let newResult2 = result2.mapError(transformError)

expectEqual(newResult2, .failure(.derr))

let result3: Result<Int, Err> = .success(1)
let newResult3 = result3.flatMap(resultValueTransform)

expectEqual(newResult3, .success(2))

let result4: Result<Int, Err> = .failure(.derr)
let newResult4 = result4.flatMapError(resultErrorTransform)

expectEqual(newResult4, .failure(.err))
}

ResultTests.test("Equatable") {
let result1: Result<Int, Err> = .success(1)
let result2: Result<Int, Err> = .failure(.err)

expectEqual(result1, .success(1))
expectNotEqual(result1, .success(2))
expectNotEqual(result1, .failure(.err))
expectNotEqual(result1, .failure(.derr))

expectNotEqual(result2, .success(1))
expectNotEqual(result2, .success(2))
expectEqual(result2, .failure(.err))
expectNotEqual(result2, .failure(.derr))

let confusables: [Result<Err, Err>] = [
.success(.err),
.success(.derr),
.failure(.err),
.failure(.derr)
]

checkEquatable(confusables, oracle: { $0 == $1 })
}

ResultTests.test("Hashable") {
let result1: Result<Int, Err> = .success(1)
let result2: Result<Int, Err> = .success(2)
let result3: Result<Int, Err> = .failure(.err)
checkHashable([result1, result2, result3], equalityOracle: { $0 == $1 })

let confusables: [Result<Err, Err>] = [
.success(.err),
.success(.derr),
.failure(.err),
.failure(.derr)
]
checkHashable(confusables, equalityOracle: { $0 == $1 })
}

runAllTests()

0 comments on commit b3ce0c9

Please sign in to comment.