Init in protocol extension

The following

protocol P {
        var id: Int { get set }
}

extension P {
        init() {
                self.id = 1
        }
}

struct S: P {
        var id: Int
}

results in

bla.swift:16:25: error: 'self' used before 'self.init' call or assignment to 'self'
                self.id = 1
                        ^
bla.swift:17:9: error: 'self.init' isn't called on all paths before returning from initializer
        }

Is there a way to omit this error?

I'm trying to get rid of some classes in a hot path and I have to compare to structs:

let a = S(... other members...)
let b = S(... other members...)
if a != b { ... }

Without more details it's not obvious what's wrong with this (just not having that protocol extension):

protocol P: Equatable {
    var id: Int { get set }
}

struct S: P {
    var id: Int
    var x: Double
}

func foo() {
    let a = S(id: 1, x: 2)
    let b = S(id: 1, x: 3)
    if a != b {
        ...
    }
}

Ok. Here is a more realistic example (trimmed down from original code):

protocol BxDF: AnyObject {
        func evaluate()
}

class Lambert: BxDF {
        func evaluate() {

        }
}

struct BSDF {

        func sample() {
                guard let r = bxdfs.randomElement() else {
                        return
                }
                for bxdf in bxdfs {
                        if bxdf !== r {
                                bxdf.evaluate()
                        }
                }
        }

        var bxdfs = [BxDF]()
}

@_semantics("optremark")
func main() {
        for _ in 0..<1_000_000 {
                var bsdf = BSDF()
                bsdf.bxdfs.append(Lambert())
                bsdf.sample()
        }
}

main()

When compiling with

swiftc -O -experimental-performance-annotations bla.swift

I get this:

bla.swift:31:35: remark: heap allocated ref of type 'Lambert'
                bsdf.bxdfs.append(Lambert())
                                  ^
bla.swift:32:29: remark: release of type '__ContiguousArrayStorageBase'
                bsdf.sample()
                            ^
bla.swift:32:22: remark: retain of type '__ContiguousArrayStorageBase'
                bsdf.sample()
                     ^
bla.swift:32:22: remark: retain of type '__ContiguousArrayStorageBase'
                bsdf.sample()
                     ^
bla.swift:32:22: remark: retain of type 'BxDF'
                bsdf.sample()
                     ^
bla.swift:32:29: remark: release of type '__ContiguousArrayStorageBase'
                bsdf.sample()
                            ^
bla.swift:32:22: remark: retain of type '__ContiguousArrayStorageBase'
                bsdf.sample()
                     ^
bla.swift:32:22: remark: retain of type 'BxDF'
                bsdf.sample()
                     ^
bla.swift:32:29: remark: release of type 'BxDF'
                bsdf.sample()
                            ^
bla.swift:32:29: remark: release of type '__ContiguousArrayStorageBase'
                bsdf.sample()
                            ^
bla.swift:32:29: remark: release of type 'BxDF'
                bsdf.sample()
                            ^
bla.swift:32:29: remark: release of type '__ContiguousArrayStorageBase'
                bsdf.sample()
                            ^
bla.swift:32:29: remark: release of type '__ContiguousArrayStorageBase'
                bsdf.sample()

I'm trying to get rid of all ARC traffic because this is the hot loop and kills performance.

The above example was one attempt to replace classes with structs and adding identity via an id counter.
I'm open for better ways.

Of course I can do

protocol BxDF {
        func evaluate()
}

struct Lambert: BxDF {
        func evaluate() {}
}

struct BSDF {

        func sample() {
                let i = Int.random(in: 0..<bxdfs.count)
                for j in 0..<bxdfs.count { if i != j { bxdfs[j].evaluate() } }
        }

        var bxdfs = [BxDF]()
}

@_semantics("optremark")
func main() {
        for _ in 0..<1_000_000 {
                var bsdf = BSDF()
                bsdf.bxdfs.append(Lambert())
                bsdf.sample()
        }
}

main()

which leaves me with

bla.swift:24:22: remark: retain of type '__ContiguousArrayStorageBase'
                bsdf.sample()
                     ^
bla.swift:24:29: remark: release of type '__ContiguousArrayStorageBase'
                bsdf.sample()
                            ^
bla.swift:24:29: remark: release of type '__ContiguousArrayStorageBase'
                bsdf.sample()
                            ^
bla.swift:24:29: remark: release of type '__ContiguousArrayStorageBase'
                bsdf.sample()
                            ^
bla.swift:24:29: remark: release of type '__ContiguousArrayStorageBase'
                bsdf.sample()
                            ^
bla.swift:24:29: remark: release of type '__ContiguousArrayStorageBase'
                bsdf.sample()
                            ^
bla.swift:24:29: remark: release of type '__ContiguousArrayStorageBase'
                bsdf.sample()

And then one wishes for a stack-based small Array.

Do you need the contents of bxdfs to be type erased (and heterogeneous) or would it be an option to turn to use a generic parameter?

struct BSDF<T: BxDF> {
    var bxdsf: [T] = []
    // ...
}

I'm not sure if it makes a difference in this contrived example with optimisations enabled, but it is recommendable in general.

Do you have a limit on array length and how big is this limit? In the above example arrays are 1-element long but perhaps it is an oversimplified version of real code. If it is up to a reasonably small number and only a few operations are needed (like get count and element at index) I'd invest into some simple fixed-sized array implementation.

Something like this (untested).
struct FixedArray<Element> {
    typealias T = Element
    
    private (set) var count: Int = 0
    private var elements: (T?, T?, T?, T?, T?, T?, T?, T?, T?, T?) = (nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
    
    init() {
    }
    
    init(_ v0: T, _ v1: T? = nil, _ v2: T? = nil, _ v3: T? = nil, _ v4: T? = nil, _ v5: T? = nil, _ v6: T? = nil, _ v7: T? = nil, _ v8: T? = nil, _ v9: T? = nil) {
        append(v0)
        guard let v1 else { return }
        append(v1)
        guard let v2 else { return }
        append(v2)
        guard let v3 else { return }
        append(v3)
        guard let v4 else { return }
        append(v4)
        guard let v5 else { return }
        append(v5)
        guard let v6 else { return }
        append(v6)
        guard let v7 else { return }
        append(v7)
        guard let v8 else { return }
        append(v8)
        guard let v9 else { return }
        append(v9)
    }
    
    subscript (_ index: Int) -> T {
        get {
            switch index {
            case 0: return elements.0!
            case 1: return elements.1!
            //  ...
            default: fatalError("index out of bounds")
            }
        }
        set {
            switch index {
            case 0: elements.0 = newValue
            case 1: elements.1 = newValue
            //  ...
            default: fatalError("index out of bounds")
            }
        }
    }
    
    mutating func append(_ element: T) {
        precondition(count < 10)
        self[count] = element
        count += 1
    }
    
    func forEach(_ execute: (T) -> Void) {
        for i in 0 ..< count {
            execute(self[i])
        }
    }
    
    func filter(_ execute: (T) -> Bool) -> FixedArray {
        var filtered = FixedArray()
        for i in 0 ..< count {
            let element = self[i]
            if execute(element) {
                filtered.append(element)
            }
        }
        return filtered
    }
    
    func map<R>(execute: (T) -> R) -> FixedArray<R> {
        var mapped = FixedArray<R>()
        for i in 0 ..< count {
            mapped.append(execute(self[i]))
        }
        return mapped
    }
    
    func reduce<R>(_ initial: R, execute: (R, T) -> R) -> R {
        var r = initial
        for i in 0 ..< count {
            r = execute(r, self[i])
        }
        return r
    }
}

Yes, the BxDFs can be all kinds: Diffuse, dielectric, conductor, hair, ...

Yes, I'm thinking of a fixed size array. Thanks.

IIUC, when you work with type erased values you work with a (value type) box that points to a heap allocated object, whose payload is the actual value. That will cause ARC traffic... To avoid it consider using an enum:

enum BxDF {
    case diffuse(Diffuse)
    case dielectric(Dielectric)
    case conductor(Conductor)
    case hair(Hair)
    
    func evaluate() {
        switch self {
        case .diffuse(let diffuse):
            diffuse.evaluate()
        case .dielectric(let dielectric):
            dielectric.evaluate()
        }
    }
}

struct Diffuse {
    func evaluate() {}
}

struct Dielectric {
    func evaluate() {}
}

In this case you'll be working with real unboxed values (in C that would be called "union") of a size big enough to encapsulate the biggest member plus a discriminator value. That should be ARC-free.

This is a good idea. Another one worth considering is if you can distribute the single array of n polymorphic values of N datatypes into N separate arrays of monomorphic values, one per datatype.