Soroush Khanlou writes: “Lots of times i just wish there were no optional type, and it was all just `Result`”
He gives this example:
struct NilError: Error { } func throwable<T>( _ block: @autoclosure() -> T?) throws -> T { guard let result: T = block() else { throw NilError() } return result } let image = try throwable(UIImage(data: imageData))
I decided to fancy it up a little:
// Inspired by Soroush Khanlou public enum Throws { public struct NilError: Error, CustomStringConvertible { public var description: String { return _description } public init(file: String, line: Int) { _description = "Nil returned at " + (file as NSString).lastPathComponent + ":\(line)" } private let _description: String } public static func this<T>( file: String = #file, line: Int = #line, block: () -> T?) throws -> T { guard let result = block() else { throw NilError(file: file, line: line) } return result } } do { let imageData = Data() let image = try Throws.this { NSImage(data: imageData) } } catch { print(error) }
The obvious difference is that my errors look like “Nil return at playground13.swift:24” but I also added a few things according to some style choices I’m testing out:
- No
autoclosure
. Reserve autoclosure for lazy evaluation. Apple writes, “The context and function name should make it clear that evaluation is being deferred.” - No global freestanding function. Embed globals into types as static members.
- Nested error declaration. It’s a subordinate and specific to the type.
- Custom description. The error tells you more about itself than just its name.
- Allman after a multi-line complex initializer declaration.
- Late private property declaration. I’m kicking the wheels on this, which I picked up from the Standard Library folk.
Thoughts?
Update: Loic has a really nice alternative that inspired me to tweak:
public struct NilError: Error, CustomStringConvertible { public var description: String { return _description } public init(file: String, line: Int) { _description = "Nil returned at " + (file as NSString).lastPathComponent + ":\(line)" } private let _description: String } extension Optional { public func unwrap(file: String = #file, line: Int = #line) throws -> Wrapped { guard let unwrapped = self else { throw NilError(file: file, line: line) } return unwrapped } } do { let imageData = Data() let image = try NSImage(data: imageData).unwrap() } catch { print(error) }
7 Comments
Interesting, i also come up with something like this :
I really don’t like throwable() or Throws.this() for the name of this operation, because I already know that it can throw at the call site (I see the “try”) but it doesn’t tell me _why_ it might throw. I’d prefer a name like Throws.ifNil() or notOptional() or something like that.
Check out the alternative suggested by Loic
is there a link to Khanlou’s comment?
Here you go: https://iosdevelopers.slack.com/messages/swift/
My team members alway say they use hard unwrap that it will be caught during a dev lifecycle.
It is problematic, especially if the code is in a framework. Hard unwraps just explode and kill more tinker bells.
Your solution works, but it is not alway possible to use, due to it being throwable, try/catch, multiline.
Here is what we came up with:
extension Optional {
/// Check that a value was set or throw an assertionFailure with a human-readable description,
/// as oppose to just using hard unwrapping ! which will throw a hard exception with a generic message.
func unwrap(_ message: String = “value is not set!!!”) -> Wrapped {
if let unwrapped = self { return unwrapped }
fatalError(message)
}
}
instead of using ! we use .unwrap(“You had to set this value, please read the README”)
Yes, if it ever makes to prod it will explode, but if it is was meant to explode in dev, here you have it with a nicer message to users of your code, so they do not have to spend ton of time figuring out why.
I don’t actually use this code. I was really just messing with style and possibilities.