swiftgenericsoption-typeswift-extensions

Optional extension for any types


I want to write Optional extension for any types.

My code for integer:

extension Optional where Wrapped == Int {

    func ifNil<T>(default: T) -> T {
        if self != nil {
            return self as! T
        }
        return default
    }
}

var tempInt: Int?

tempInt.ifNil(default: 2) // returns 2

tempInt = 5

tempInt.ifNil(default: 2) // returns 5

It works but it is Optional(Int) extension (Optional where Wrapped == Int), I want to use this extension for any types like Date, String, Double etc.

What are your suggestions?


Solution

  • The answer to your basic question is to just remove the where clause:

    extension Optional { 
        // ... the rest is the same 
        func isNil<T>(value: T) -> T {
            if self != nil {
                return self as! T
            }
            return value
        }
    }
    

    Now it applies to all Optionals.

    But this code is quite broken. It crashes if T is not the same as Wrapped. So you would really mean a non-generic function that works on Wrapped:

    extension Optional {
        func isNil(value: Wrapped) -> Wrapped {
            if self != nil {
                return self!  // `as!` is unnecessary
            }
            return value
        }
    }
    

    But this is just an elaborate way of saying ?? (as matt points out)

    extension Optional {
        func isNil(value: Wrapped) -> Wrapped { self ?? value }
    }
    

    Except that ?? is much more powerful. It includes an autoclosure that avoids evaluating the default value unless it's actually used, and which can throw. It's also much more idiomatic Swift in most cases. You can find the source on github.

    public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T)
        rethrows -> T {
      switch optional {
      case .some(let value):
        return value
      case .none:
        return try defaultValue()
      }
    }
    

    But I can imagine cases where you might a method-based solution (they're weird, but maybe there are such cases). You can get that by just rewriting it as a method:

    extension Optional {
        public func value(or defaultValue: @autoclosure () throws -> Wrapped) rethrows -> Wrapped {
            switch self {
            case .some(let value):
                return value
            case .none:
                return try defaultValue()
            }
        }
    }
    
    tempInt.value(or: 2)