Search code examples
swiftgenericsenumsswift-extensionsswift-protocols

Swift extension on RawRepresentable has no accessible initializer


I'm trying to create an extension for my FieldIdentifiable protocol only where the enum that implements it has a RawValue of Int. The only problem is that the return FieldIdItem(rawValue: newValue) line keeps showing this error:

'Self.FieldIdItem' cannot be constructed because it has no accessible initializers

Is this a Swift bug or am I missing something?

enum SignUpField: Int, FieldIdentifiable {
  case Email = 0, Password, Username

  typealias FieldIdItem = SignUpField
}

protocol FieldIdentifiable {
  typealias FieldIdItem

  func next() -> FieldIdItem?
  func previous() -> FieldIdItem?
}

extension FieldIdentifiable where Self: RawRepresentable, Self.RawValue == Int {

  func next() -> FieldIdItem? {
    let newValue: Int = self.rawValue+1
    return FieldIdItem(rawValue: newValue)
  }

  func previous() -> FieldIdItem? {
    return FieldIdItem(rawValue: self.rawValue-1)
  }
}

Solution

  • In

    extension FieldIdentifiable where Self: RawRepresentable, Self.RawValue == Int { ... }
    

    the associated type FieldIdItem of Self is not (necessarily) RawRepresentable, and that's why

    FieldIdItem(rawValue: newValue)
    

    does not compile. You could fix that by adding additional constraints:

    extension FieldIdentifiable where Self: RawRepresentable, Self.RawValue == Int,
    Self.FieldIdItem : RawRepresentable, Self.FieldIdItem.RawValue == Int { ... }
    

    However, if the next() and previous() methods actually should return instances of the same type then you don't need the associated type at all, and can use Self as return type in the protocol:

    enum SignUpField: Int, FieldIdentifiable {
        case Email = 0, Password, Username
    }
    
    protocol FieldIdentifiable {
    
        func next() -> Self?
        func previous() -> Self?
    }
    
    extension FieldIdentifiable where Self: RawRepresentable, Self.RawValue == Int {
    
        func next() -> Self? {
            return Self(rawValue: self.rawValue + 1)
        }
    
        func previous() -> Self? {
            return Self(rawValue: self.rawValue - 1)
        }
    }
    

    Note also that the constraint

    Self.RawValue == Int
    

    can be relaxed slightly to

    Self.RawValue : IntegerType