SE-0235 - Add Result to the Standard Library

The opinionated nature of Result is exactly what makes it worthwhile. It provides additional context and meaning to the values. Either is just a structural sum type. The use for Result-like purposes in Haskell is by convention rather than by semantics baked into the type. IMO it is much better to have a type that carries semantic weight.

Further, if we wanted to introduce structural sums in Swift they should be a true structural type similar to tuples rather than a generic Either type that can only support two cases. Perhaps something similar to the polymorphic variants in OCaml (scroll down to the relevant section). This is a much different topic than the one at hand, so if people are interested in discussing it they should start a new thread to do so.

7 Likes

What is your evaluation of the proposal?

Huge +1. Especially for the way it deals with the Error type.

Is the problem being addressed significant enough to warrant a change to Swift?

Yes. From my experience, many libraries define their own Result-like type or use another common implementation like antitypical/Result. Unifying it in the standard library would be a huge .success.

Does this proposal fit well with the feel and direction of Swift?

Yes. I believe some people in this thread (respectfully) misunderstood the goal of adding Result into the standard library.

From my understanding, the goal of this proposal is not:

  • to enforce a specific way of handling errors,
  • to dictate one and only way of designing asynchronous APIs,
  • or to undermine the potential evolution towards Future/Promise or async/await.

The goal of this proposal is to standardize a broadly used concept into the standard library. That's it. No language changes, no enforcement whatsoever. It doesn't block any further development towards language support for coroutines (on the contary, I think having Result in the standard library lays the foundation for them), and it doesn't mean that syntax sugar for dealing with Results can't be introduced in a future proposal.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

Yes, I have primarily used antitypical/Result but have also wrote my own implementations countless times.

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Read the proposal and the whole thread.

2 Likes

I do appreciate the value of semantic weight, but in general, I feel that's the task of a specific framework or app to define. In order to select a specific non-abstract semantic naming for inclusion in the standard library, I think it has to clear certain hurdles:

  • Is there really a significant enough difference from the most abstract semantics to justify creating what may be a redundant type just for a different semantic name? For example, HTTP request headers are always dictionaries of string keys with string values. Should the standard library include a type called "Headers" which makes it clear that the use of this [String: String] dictionary is specifically for HTTP headers, and perhaps include some convenience properties for well known headers like Content-Type? Certainly the semantic weight is helpful, and arguably would be used more broadly than a Result type (almost every app makes HTTP requests). However, it's hard to argue that the semantic weight is really a significant enough improvement over just using the generic Dictionary<String: String> to justify being a new type just for the semantics.

  • Is it used broadly enough, or is the implementation performance-sensitive or complex enough to warrant the added surface area being added to the standard library for ALL app and frameworks. In other words, something that is truly ubiquitous makes sense or something that is difficult enough to implement that even if only 30% of apps use it, it's still worth adding to the standard library because the majority of those 30% of apps will probably get it wrong if they roll their own implementation (see: hashing).

  • Is it likely to feel as broadly relevant and useful in 10 years as is does today?

I don't feel like Result clears those three bars, at least based on my own subjective perspective(and I've used various versions of Result and Result<T, E> for years), particularly if there were an Either type included in the standard library (which I think there should be)

Additionally (and this is purely my opinion), I would like to see the standard library be more of a distillation of the least common denominator pieces used by all or most apps, rather than an aggregation of every type and API that happen to be used by at least 10% apps or something like that. I think fundamental data structures and types are obvious candidates, but the further something moves away from that and towards opinionated and more limited, use-case specific semantics the less of a candidate I personally consider it for the standard library.

Regarding polymorphic variants like OCaml, that seems more like a language feature than a simple type, but I agree that they could be useful in Swift! I'm specifically bringing up the Haskell-like Either type option because it is specifically referenced in the proposal as an alternative considered, and I don't think the reason for discounting it is a defensible one.

If there were an Either type in the standard library, frameworks could add semantic weight through simple extensions and typealiases, e.g.:

typealias Result<SuccessType, ErrorType> = Either<T, U> where U: Error
extension Result {
    var isSuccess: Bool {
        switch self {
        case .right: return true
        case .left: return false
        }
    }
}

Which would then allows frameworks to specify API like:

func fetchString() -> Result<String, TypedError>

This adds semantic weight through a mechanism that is more tangible than convention, while still keeping Either abstract for other cases and not baking an overly specific type / use-case into the standard library. It would also allow interoperability between Result types typealiased by different modules, because under the hood they are just the same standard library Either type.

2 Likes

-1. Result is a reaction to not Swift's error system not being as type-safe as its enumeration system. Longer-term, the solution is to fix that.

Swift's error system handles propagation of errors across multiple async calls. This proposal doesn't address that. (Example below of what would need to be addressed for me to change my mind; that's the way I've been doing things for 2-3 years.)

Shorter-term, I think a better proposal would be to:

  1. Add the ability to document asynchronous throws. Using throws isn't accurate.
  2. Add a common type alias for ( () throws -> Gettable ) -> Void that we all can use, that can be deprecated after the error system is improved or the future of asynchronicity makes that unnecessary.
public typealias Process<Processable> = (Processable) -> Void
public typealias Get<Property> = () throws -> Property
public typealias ProcessGet<Gettable> = Process< Get<Gettable> > // Close enough to Result.

typealias IntermediateResult = Any
typealias SuccessfulResult = Any

/// - throws: Async2Error, Async3Error
func async1(process: @escaping ProcessGet<SuccessfulResult>) {
  async2 { getIntermediateResult in
    do {
      let intermediateResult = try getIntermediateResult()
      async3(intermediateResult: intermediateResult, process: process)
    }
    catch {
      process { throw error }
    }
  }
}

struct Async2Error: Error { }

/// - throws: Async2Error
func async2(process: @escaping ProcessGet<IntermediateResult>) { }

struct Async3Error: Error { }

/// - throws: Async3Error
func async3(
  intermediateResult: IntermediateResult,
  process: @escaping ProcessGet<SuccessfulResult>
) { }

async1 { getSuccessfulResult in
  do {
    let getSuccessfulResult = try getSuccessfulResult()
  }
  catch let error as Async2Error { }
  catch let error as Async3Error { }
  catch { fatalError() }
}

Generally speaking (not saying this is what you suggested), I was wondering if the Result type should be a protocol Either so users could create domain specific result types and the standard libary provides an out-of-the-box concrete type Result.

It was a shocker of a thought experiment and no amount of showering helps me come clean. For a start, naming was hard because I found I was basically modelling a logical OR. First barf was something like the following:

protocol Either {
    associatedtype TrueValueType
    associatedtype FalseValueType
    
    func getTrueValue() throws -> TrueValueType
}

enum Result<ValueType>: Either {
    typealias TrueValueType = ValueType
    typealias FalseValueType = Error
    
    case success(TrueValueType)
    case failure(FalseValueType)
    
    func getTrueValue() throws -> ValueType {
        switch self {
        case .success(let value): return value
        case .failure(let error): throw error
        }
    }
}

Either suggests exclusitivity but nothing prevents a concrete type from using the same type non-exclusively. This applies to the Result type, too, whose stronger semantics make Result<Error, Error> incoherent (this is different from Result<Never, Error>).

The madness continued by putting naming aside. Could a user match these exclusivity semantics in a domain we're familiar with and implement it with a specific concrete type? I considered URLSessionTask. A task fails or succeeds but never both. Would the API benefit from a domain-specific concrete result type which always included a urlResponse? The only thing I could think of were these shockers:

struct URLSessionTaskResult: Either {
    typealias TrueValueType = Data
    typealias FalseValueType = Error
    
    public var urlResponse: URLResponse?
    
    func getTrueValue() throws -> Data {
        return underlyingResult.getTrueValue()
    }
    
    private var underlyingResult: Result<TrueValueType>
}

extension URLSession {
    
    func dataTask(with url: URL, completionHandler: @escaping (URLSessionTaskResult) -> ()) -> URLSessionTask {
        dataTask(with: url) { (data, response, error) in
            let result: Result
            if let data = data {
                result = .success(data)
            } else if let error = error {
                result = .failure(error)
            } else {
                fatalError() // ???
            }
            let taskResult = URLSessionDataTaskResult(urlResponse: response, underlyingResult: result)
            completionHandler(taskResult)
        }
    }
}

But this quickly turned to a dumpster of fire (which would be offensive to dumpsters already on fire). In the above, it composed the concrete implementation. Sure, it doesn't have to, but the implementation of concrete type without it turns awkward or ugly. For example:

enum AnotherURLSessionTaskResult: Either {
    
    struct Storage<ValueType> {
        var value: ValueType
        var urlResponse: URLResponse
    }
    
    typealias TrueValueType = Storage<Data>
    typealias FalseValueType = Storage<Error>
    
    case success(TrueValueType)
    case failure(FalseValueType)
    
    func getTrueValue() throws -> ValueType {
        switch self {
        case .success(let storage): return storage.value
        case .failure(let storage): throw storage.value
        }
    }
    
    public var urlResponse: URLResponse {
        switch self {
        case .success(let storage): return storage.urlResponse
        case .failure(let storage): return storage.urlResponse
        }
    }
}

or

struct YetAnotherURLSessionTaskResult: Either {
    typealias TrueValueType = Data
    typealias FalseValueType = Error
    
    public var urlResponse: URLResponse?
    var value: TrueValueType?
    var failure: FalseValueType?
    
    func getTrueValue() throws -> Data {
        switch (value, failure) {
        case (.some(let value), nil):
            return value
        case (nil, .some(let error)):
            return error
        case (nil, nil):
            fatalError()
        case (.some, .some):
            fatalError()
        }
    }
}

It added too much complexity and left too much room for programmer error. If you've stil kept your food down, this is all a long way of saying two things.

First, what, if anything, should stop people from writing incoherent things like Result<T, T> for the same T?

Second, it's not clear what having such a general purpose naming actually adds to the developer's toolkit when you can augment the semantics of the Result type with domain specific types by composition.

Kiel

I think there's more to it than just that. It's been said again and again in this thread that Result would still have uses when async/await is here. One way to think of it is that Result is a primitive that can be used more flexibly than throws allows, and I don't think it's just as a band-aid around async callback code.

I think we can look at three common features that can be modeled as type-level primitives and language-level sugar:

  • Optional<T> and optional sugar like iflet
  • Result<T, E> and throws/try
  • Future<T> and async/await

Let's start with Optional. It's a first-class type in Swift so we can work with it as such. This is easy to take for granted, but languages like Kotlin and TypeScript ship with "null safety" sugar, but without a primitive type that models the feature. Both languages are much less flexible and can be much more frustrating to use wherever nullability comes into play as a result. Swift could have shipped with optional sugar but without the Optional type! That's pretty wild to think about.

Next, let's jump to Future. Most languages with async/await have a primitive you can call to directly. In JavaScript it's Promise; in C# it's Task. Because async/await blocks, independent calls are still run sequentially, not in parallel. That's why in JavaScript you still have to use Promise.all. In C#, you have Task.WhenAll. When Swift gets async/await we'll probably still need a primitive to handle parallelism. This isn't to say that language sugar couldn't finally solve all of these problems, but should we have to wait years (or forever) for sugar when a primitive would bridge the gap today?

Finally, let's return to Result. It's another one of those primitives that I think we'd benefit from having around, not just because we're waiting for async/await. A Result primitive gives us something similar to what a Future primitive gives us: where Future is needed to run independent tasks in parallel, something like Result is needed to run independent validations on multiple things that can fail. You could accumulate several errors in validated form data in a Result, while if you tried to validate using throws, you'd only ever get the first thing you try to validate. For example, you could accumulate multiple failures when trying to build a User with a Result<User, [Error]>, but not with throws.

13 Likes

if you tried to validated using throws , you'd only ever get the first thing you try to validate. For example, you could accumulate multiple failures when trying to build a User with a Result<User, [Error]>

My solution has been:

public struct Errors: Error {
  public let array: [Error]
}

public extension Errors {
  init<Errors: Sequence>(_ errors: Errors)
  where Errors.Element == Error {
    array = Array(errors)
  }

  init(_ errors: Error...) {
    self.init(errors)
  }
}

(This could work except for the bug at the end of this post.)

extension Array: Error where Element: Error { }

public typealias Validate<Parameters> = (Parameters) throws -> Void

public func validate<
  Validates: Sequence,
  Parameters
>(
  _ validates: Validates,
  parameters: Parameters
) throws
where Validates.Element == Validate<Parameters> {
  let errors = validates.flatMap { validate -> [Error] in
    do {
      try validate(parameters)
      return []
    }
    catch let array as [Error] {
      return array
    }
    catch { return [error] }
  }

  guard errors.isEmpty
  else { throw errors } // Does not conform to Error
}

My point is that you can't use the language's throws/try for this in the abstract, as you can't use async/await for parallelism in the abstract. They're both sequential constructs. With your Errors type, even if the standard library provided it, there'd be no sugar to use it alongside throws/try. You're stuck in user-land code to manually handle errors and finally throw it at the end.

3 Likes

I don't fundamentally disagree with your statement here... more comment in this regard below.

Sure, it might be added later. However, irregardless of how throws may have been initially designed, this is what is published as documentation as how to handle errors in Swift: The Swift Programming Language: Redirect.

Introducing an Result type without addressing fluid language integration and changing the fundamental way error handling is documented, is incomplete, in my opinion. It makes Result feel like something that was bolted on as a workaround to a community issue.

For instance, if Result had existed alongside Optional, it seems far more natural that try? should have converted the throwing function's result into Result rather than Optional.

4 Likes

I agree that the thought experiment you walked through with a hypothetical Either protocol does not end up in a nice place!

I’m looking at this from possibly a different perspective than the many people who are partial to the Result<T, E> abstraction. Again, I’ve used various forms of Result and Result<T, E> and have included them in my own frameworks before as well. So I’m familiar with the paradigm. However:

  1. I philosophically disagree with the correctness of distinguishing between “success” and “failure” in Result-based code. I have seen lots of developers flatMap or optionalize out the error case, and most of the convenience methods on Result types exist to do exactly that. If an API can return two possible types, it is most correct to handle each possible type, with the compiler participating through generics and strong typing. I have seen plenty of production bugs in Result-based code because the failure case was ignored because “it will never fail”. The way I often seen Result types used is essentially equivalent to bad uses of the force unwrap ! operator.

    Therefore, I don’t actually think it’s desirable to have such a bias in sum type responses. A lot of my current APIs use enum return values with more than two cases. A bias in those situations would be even worse and less sensible, but it’s actually the same problem. If method returns a two-case sum type, the currect way to code for it is to handle both possible case, full stop.

    Therefore, I’m perfectly happy to have something like Option<Either, Or>, Choice<First,Second>, or Either<Left, Right> in all places where I see Result<Success, Failure> today. But I get that many would not find that pragmatic and would disagree. I don’t think it’s correct though to argue that “first” and “second” aren’t informative enough cases, when the strong type information for each case says everything about how to handle it, and both should be viewed as equal alternatives.

    I have always disliked “failure” in Result anyway because it’s remarkably ambiguous. What failed: my request? The network connection itself? The server operation? Consider an API for login: does .success mean login itself succeeded or just the request? If the latter, then .success would contain the fact that you succeeded at failing? Similarly does .failure mean a failure of the request, or of the login attempt? My point isn’t to be deliberately obtuse, just to point out that there are semantic issues and ambiguities with Result as well, even when used as intended!

    So going back to your example, I’m perfectly happy with a URLSessionTask method that takes a completion handler with the type signature:

    (Either<LoginResponse, NetworkError>) -> ()
    

    And the closure would look like:

    { either: Either<LoginResponse, NetworkError> in
         switch either {
         case .first(let loginResponse): // handle
         case .second(let networkError): // handle
         }
    }
    

    I find this completely clear, correct and usable personally.

  2. There are other places to use Either where Result doesn’t make sense semantically:

    let formField: Either<Filled<Date>, Unfilled>
    let key: Either<SimpleKey?, CompoundKey>
    let node: Either<Branch, Leaf>
    

    All of these would be bizarre if “Either” were replaced with “Result” IMO. Yet I think using “Either” in place of anywhere “Result” is used is clear and correct, e.g.:

    func request(completion: (Either<Response, Error>) -> ())
    

    What could be more clear than declaring that a handler that must handle “Either a Response or an Error”?

  3. As I alluded to earlier, the main reason people want a Result type isn’t for a named sum type with two cases. It’s for all the convenience methods to only address success or failure cases, and other shorthands, such as virtually all the methods implemented in this proposal. My point in saying that one could typealias Result and also add extensions like isSuccess using a fundamental Either type was that if someone wanted to add most of that sugar they could (though I’m not a fan personally).

    It is true that no extension or typealias could change the case names themselves from .first and .second. But the point of all the things like fold and mapError is to avoid switching on the actual cases anyway. So for all the code that uses that sugar, they wouldn’t even know the difference between the proposed Result type, and and Either type typealiased to Result with the same convenience methods added via extension.

8 Likes

This closure-based approach makes debugging harder. Converting a closure to a string results in nothing useful (just "(Function)"), and printing a closure in the debugger prints (at best) the source location of the closure body. This call:

async1 { print("result: \($0)") }

produces this output for success and failure cases:

result: (Function)
result: (Function)

If I want to log something useful, I have to work considerably harder:

async1 {
    do {
        let value = try $0()
        print("result: \(value)")
    } catch {
        print("result: \(error)")
    }
}

to get this:

result: async3 success
result: Async3Error()

In a real program, I might have the do/catch structure anyway, but I still have to log the result in two places.

By contrast, the Result enum is exactly as easy to debug as its type parameters are. If the callback receives a Result, then this call:

async1 { print("result: \($0)") }

produces this output for success and failure cases:

result: Result.success("async3 success")
result: Result.failure(__lldb_expr_3.Async3Error())
4 Likes

It is not. The proposal describes that Result has prior art in many other languages with established error handling techniques, thus proving that it is a useful primitive nevertheless.

This is a valid point but you can make that point for basically every language feature and construct. Swift is trying its best, but you can always just fatalError in various ways anywhere. Just because a developer can force ignore a failure case doesn't imply we shouldn't have Result (similarly to just because they can force unwrap an optional doesn't mean we shouldn't have them).

Just like above, I find this criticism universal to ambiguous API design no matter the language construct. It is not unique to Result, you can make the same point for throws/try, Either, or tuple-based callbacks. I don't think it shows a disadvantage of this proposal, specifically.

1 Like

+1 to the general proposal.

  • It’s great that the proposal doesn't hard code Swift.Error.
  • Shadowing Error could be confusing.
  • Some standard library functions use Result as a type variable, that could become confusing.
  • I prefer the Either naming alternative (or something else generic). While error handling is almost certainly the most common use case, it's not the only one, and naming it as if it is could hinder a deeper understanding of the concept.
  • Perhaps consider swap instead of mapError etc.

typealias Result<T> = Either<Error, T> seems an easy way to simplify local types if you desperately want to avoid having to specify Error, while keeping it more generic acknowledges other uses, including more strict error handing like Either<E: Error, T>

It will be great to see all the (error?, value?) style callbacks go away. ;)

The piecemeal addition and support of types like Optional and Either indicate a deeper type problem that I hope we work out a solution for. :)

I don't feel like I've formulated a full opinion about Result, but I'm definitely anti-Either-as-Result, and mildly anti-Either-in-general. Either gets used in all sorts of different ways, and having to use generic terminology to manipulate it makes it harder to understand what it's representing in a particular context. The downside is having to reimplement [flat]mapLeft/[flat]mapRight/bimap/fold in each case, and that is a downside, but the upside is being able to have "biases" (preferring the success path over the error path), or force something to be handled, or conform to protocols in different ways.

Why doesn't this apply to Optional? Well, it kind of does, but because the none case doesn't carry any information other than "absent", there's usually less you'd want to build on top of it that's different for each type.

(Another interesting case is Bool. Bool is useful, but sometimes a two-case enum is more expressive. That doesn't mean you don't want Bool, but if the convention were two-case enums, Bool might be limited to just conditions. I'm not sure we need such an equivalent with Either.)

14 Likes
  • What is your evaluation of the proposal?
    -1

  • Is the problem being addressed significant enough to warrant a change to Swift?

I have not had any need for a result type, but it seems like a lot of people have independently reinvented this concept so there must be some need that is being fulfilled. The standard library does have a goal (as far as I know) of providing common currency types so it’s not insane that such a thing could be provided.

  • Does this proposal fit well with the feel and direction of Swift?

No.

As far as fitting in with the rest of Swift, I would prefer it to be Result<T> to be consistent with throw. As written, the proposal creates a weird dichotomy where if you prefer typed errors you must use Result and can not use throw. If typed errors are going to be encouraged they should work universally across error handling mechanisms. I should be able to throw anything in a result’s error. if this was fixed I would be +0 on the proposal.

As for the direction of swift, I feel that a significant motivation for this proposal is for usage in asynchronous callbacks. Since swift does not yet provide a 1st class concurrency model, lots of people have rolled their own concurrency mechanism, for which Result is undoubtedly convenient. When swift does provide promise, future, etc, what then becomes the role of Result? If we already had 1st class concurrency support I’m not sure there would still be sufficient motivation for this proposal. The only motivating examples for synchronous error handling presented so far involve caching errors for deferred handling. It seems like there are a lot of ways to solve this problem that don’t need to involve the standard library. I this proposal might still be a good candidate for non-standard-library if such things existed.

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

n/a

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Read the proposal, and followed some of the pitch discussions.

6 Likes

I still don’t really understand the statement I’ve read a few times (and that you make as well) that the generic nature of “Either” makes it hard or unclear to work with. If I were describing an API that might return a String or might produce a ParsingError, I would say “this method returns either a String or a ParsingError”. If I wrote that signature as: func parse(data: Data) -> Either<String, ParsingError> it would be completely clear in my opinion.

If might then describe something like: “let’s say we want to use the returned value of the parse function, which can be either a String or a ParsingError. If it’s the former (a String), then print the string. If it’s the latter (the ParsingError), then log the error to the error log.” If I wrote this in code as:

let result: Either<String, ParsingError> = parse(data: someData)
switch result {
  case .former(let string): print(string)
  case .latter(let parsingError): ErrorLog.log(parsingError)
}

It’s not only very clear to me exactly what cases are being handled, but it reads almost the same way I would speak it in a sentence.

Yes, we can arbitrarily constrain this to specific terminology like: “this method returns a Result whose success state is a String and whose failure state is a ParsingError”, but I don’t find that any clearer. And in cases where “success” and “failure” aren’t good semantic descriptions of the two possibilities, it goes downhill quickly in terms of clarity.

Can you provide some examples of when and how such naturally expressive statements as “returns either this or that” and “if it’s the former (this) then do x and if it’s the latter (that) then do y” are unclear, hard to use, etc.? It’s been asserted as obvious that this is undesirable, but it’s not obvious to me.

1 Like

Is such a method interchangeable with one that returns "either a ParsingError or a String"? If so, how would you express that using Swift's generics? If not, how would you describe the semantic difference between 'Either<A, B>' and 'Either<B, A>'?

By contrast, it is plenty clear that 'Result where Success == String, Failure == ParsingError' is not the same as 'Result where Success == ParsingError, Failure == String'.

14 Likes

This is a very useful example, thank you!

I would say that in terms of implementing flatMap or other methods on top of Either<A, B>, that yes, Either<A, B> and Either<B, A> are equivalent. And I think that for an unbiased, two-value sum type, it would be reasonable to expect that equivalence and it should be possible to write extension methods and operators that treat them as interchangeable. This could be expressed for example using the Swift generics system as:

func ==<T: Equatable, U: Equatable>(left: Either<T, U>, right: Either<U, T>) -> Bool {
    switch (left, right) {
        case (.former(let former), .latter(let latter)): return former == latter
        case (.latter(let latter), .former(let former)): return former == latter
        default: return false
    }
}

Or

extension Either<Former, Latter> {
    func flatMap<NewType>(transform: (Former) -> Either<NewType, Latter>) -> Either<NewType, Latter> { ... }
    func flatMap<NewType>(transform: (Former) -> Either<Latter, NewType>) -> Either<Latter, NewType> { ... }
    func flatMap<NewType>(transform: (Latter) -> Either<Former, NewType>) -> Either<Former, NewType> { ... }
    func flatMap<NewType>(transform: (Latter) -> Either<NewType, Former>) -> Either<NewType, Former> { ... }
} 

I do see that semantically, Result<Success, Failure> makes it clear that the left and right types are not interchangeable (at the cost of not being useful in situations where the two possibilties don’t have a success / failure polarity). But I’m still not sure I see why this makes usage of a semantically tagged Result<Success, Failure> more clear or correct than Either<T, U> provided that Either<T, U> is interchangeable with Either<U, T> as the name implies. At least from my perspective, both Either<Former, Latter> and Result<Success, Failure> are intended to have both cases handled, and neither one ignored. Is there a desirable benefit to biasing towards one possibility and not handling both equally?

I don't see that as a compelling argument against have an Either type instead of a Result type. Even in spoken language, the order in which you state the alternatives matter for referring back to them using position language, such as the aforementioned "former" and "latter". That you have to stick with whatever your initial choice was seems to be an argument for, not against.

There you go: Result<Foo, Any>.

I'd like to address this misconception for all people who ask this question in this thread: Future is a Result delayed in time. It is backed by it. You can see this in all major implementations: PromiseKit, BrightFutures, FutureKit and SwiftNIO. Having a primitive Result in standard library is actually a step towards, not against having Future/Promise in the future.

7 Likes