Introducing an “Unwrap or Throw” operator

Without commenting on the larger proposal, I’ll note that while throw is a statement today, there’s nothing that requires it to be one. Since you can already put try immediatelyThrow(error) into expression position I don’t think there’s any ideological reason to keep throw error from being a valid expression.

7 Likes

+1 for this, I'm using it for several years. Here is the sample:

extension Optional {
  public func unwrapOrThrow(_ errorExpression: @autoclosure () -> Error) throws -> Wrapped {
    guard let value = self else { throw errorExpression() }
    return value
  }
  
  public func unwrapOrThrow(_ errorExpression: () -> Error) throws -> Wrapped {
    guard let value = self else { throw errorExpression() }
    return value
  }
}

// Usage:
let url = try urlComponents.url.unwrapOrThrow(AppLinkError(code: .unexpectedNilURL))
let deepLinkString = try (deepLinkURLValue as? String).unwrapOrThrow(AppLinkError(code: .typeCastFailed))

Using #file and #line is not good default option, because it increases binary size. As a tradeoff optional values win nil default value can be used.

public func unwrapOrThrow(_ errorExpression: @autoclosure () -> Error, file: StaticString? = nil, line: UInt? = nil) throws -> Wrapped

let value = try optional.unwrapOrThrow(MyError())
let value = try optional.unwrapOrThrow(MyError(), file: #fileID, line: #line)
1 Like

-1 here. I'm not seeing the need for a new operator. I'm doing this for years and it works/chains great:
try URL(string: "https://google.com").unwrap(). You could overload .unwrap() to satisfy all scenarios above, and in a clearer way (i.e. using named params).
Personally, I would be in favour of adding an .unwrap() method (or similar) on Optional, since this is something I use in almost all my projects. Not sure how it would fit others tho.

Same here. I don't like adding a new operator for this. Seems like most of us already use this "unwrap or throw" method on Optional and are happy with it. And yes, I would like to have the standard library featuring this method.

An unwrapOrThrow method certainly plays nicer with the pre-existing ability to put a Never returning function on the left hand side of ??. It's also slightly more explicit about the fact that the supplied error will be thrown.

Recently, someone pointed out to me that, if you squint a lot, Optional is just Result where Failure is nil kind of thing. Given this, the "correct" function to use would be try get().

Using the following generic conversion functions foo! is equivalent to try! foo.get() with try? foo.get() being a full round trip.

public extension Optional {
    enum UnwrappingError : Error {
        case foundNilWhileUnwrappingAnOptionalValue
    }
    
    func get(orThrow error: @autoclosure () -> Error = UnwrappingError.foundNilWhileUnwrappingAnOptionalValue) throws -> Wrapped {
        if let value = self {
            return value
        } else {
            throw error()
        }
    }
}

public extension Result {
    init(_ optional: Success?, failure: @autoclosure () -> Failure) {
        if let success = optional {
            self = .success(success)
        } else {
            self = .failure(failure())
        }
    }
}

public extension Result where Failure == Error {
    init(_ optional: Success?) {
        self = Result {
            try optional.get()
        }
    }
}

It is indeed. If Result was there first we would probably not invent Optional.

Why would I want to use a longer try! foo.get() than a shorter foo! ?

Very much agree, I was just pointing out that they are equivalent rather than suggesting one use the longer form.

Another example is if let foo = foo being equivalent to if case let .some(foo) = foo, although again that shows the similarity's between Optional and Result.

unwrap() is enough

extension Optional {

  @discardableResult
  @_transparent
  public func unwrap(_ message: @autoclosure () -> String = String(),
                     fileID: StaticString = #fileID, line: UInt = #line, column: UInt = #column) throws -> Wrapped {
    try unwrap(ErrorInCode(header: "Unwrap failed", message: message(), location: CodeLocation(fileID: fileID, line: line, column: column)))
  }

  @discardableResult
  @_transparent
  public func unwrap<E: Error>(_ error: @autoclosure () -> E) throws -> Wrapped {
    guard let value = self else {
      throw error()
    }
    return value
  }

}

1 Like