Daniel Santos
Daniel Santos

Reputation: 911

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.

Upvotes: 2

Views: 1303

Answers (2)

Code Different
Code Different

Reputation: 93151

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)
        }
    }
}

Upvotes: 5

Rob Napier
Rob Napier

Reputation: 299275

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

Because convenience initializers must call a designated initializer. Extensions may be defined in other files. With your current structure, it is possible for the class to have no designated initializer defined in this compile unit (file), and that creates too much confusion for the current compiler. (It's not that this is an unsolvable type-theory problem; it just creates problems when it needs to figure out everything within a single file.) Extensions are extensions. If they fundamentally change how the object works (like creating designated initializers), then they may not be able to be extensions.

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

Because the doc renderer always strips the public on protocols. If this is causing confusion, open a bug to bugs.swift.org.

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

Because this is a non-final class. Consider if there were a subclass to Stack that had its own required initializer. How would you ensure that init(arrayLiteral:) called it? It couldn't call it (because it wouldn't know that it existed). So either init(arrayLiteral:) has to be required (which means it needs to be part of the main declaration and not a extension), or Stack has to be final.

If you mark it final, this works like you expect. If you want it to be subclassed, just move it out of the extension and into the main body, and define it like this:

public convenience required init(arrayLiteral: T...) {
    self.init()
    for item in arrayLiteral {
        self.push(item)
    }
}

Upvotes: 2

Related Questions