swiftprotocols

Satisfying ExpressibleByArrayLiteral Protocol


Why when I extend the ExpressibleByArrayLiteral protocol in swift I need to make the init required. In the definition of the protocol, the init method is just public.

I pretty much have the same thing as in the doc, https://developer.apple.com/reference/swift/expressiblebyarrayliteral, and still, the compiler complains about making this init(arrayLiteral: Element...) required. The only difference I have is that I am implementing it in a class no a struct. Any suggestions?

UPDATE:

Here is an implementation of my code:

public class Stack<T> {
private var buffer: [T]

init() {
    self.buffer = []
}

public func push(_ value: T) {
    self.buffer.append(value)
} 

public func pop() -> T? {
    return self.buffer.popLast()
}

var size: Int {
    return self.buffer.count
}

var isEmpty: Bool {
    return self.size == 0
}
} 

extension Stack: ExpressibleByArrayLiteral {
    init(arrayLiteral: T...) {
        for item in arrayLiteral {
            self.push(item)
        }
    }
}

The errors I am getting are:

  1. designated initializer cannot be declared in an extension of 'Stack'; did you mean this to be a convenience initializer?

  2. initializer 'init(arrayLiteral:)' must be declared public because it matches a requirement in public protocol 'ExpressibleByArrayLiteral'

  3. initializer requirement 'init(arrayLiteral:)' can only be satisfied by a required initializer in the definition of non-final class 'Stack'

Questions:

  1. Error #1 -> Why can't I declare a designated init in an extension?

  2. Error #2 -> I understand this part, the protocol defines this as public. But why does the docs implement it with out the public keyword?

  3. Error #3 -> This is pretty much my biggest question, why this needs to be required.


Solution

  • The reason is inheritance: any init inside a protocol must be marked as required if adopted by a class. A struct cannot be inherited.

    For a class, An init must initialize an instance of that same class or return nil. When that class adopts a protocol, itself and all its subclasses must provide their own implementations so the compiler can verify that fact:

    class ClassA : ExpressibleByArrayLiteral {
        required init(arrayLiteral elements: Self.Element...) {
            // this implicitly returns an instance of ClassA
        }
    }
    
    class ClassB : ClassA {
       // without its own init(arrayLitteral:), it will fallback on ClassA's init and return
       // an instance of ClassA, which Swift does not allow.
    }
    

    This is due to Swift's static typing system. Unlike in Objective-C where you think you create an NSString but actually get an _NSContiguousString instead (the so-called class cluster design pattern)

    NSString * str = [[NSString alloc] initWithString:@"Hello world"];
    // surprise: you may get an _NSContiguousString instead
    

    Just get rid of the extension and implement it in the class's definition:

    public class Stack<T> : ExpressibleByArrayLiteral {
        private var buffer: [T]
    
        init() {
            self.buffer = []
        }
    
        public func push(_ value: T) {
            self.buffer.append(value)
        } 
    
        public func pop() -> T? {
            return self.buffer.popLast()
        }
    
        var size: Int {
            return self.buffer.count
        }
    
        var isEmpty: Bool {
            return self.size == 0
        }
    
        // MARK: -
        required public init(arrayLiteral: T...) {
            self.buffer = []
            for item in arrayLiteral {
                self.push(item)
            }
        }
    }