Value semantics of init with defaulted private let fields does not make sense

one popular way to manually insert padding into a Swift struct is by doing the following

struct S {
    let x: Int
    private let padding: Int = 0

    init(x: Int) {
        self.x = x
    }
}

now it’s not quite right - the struct isn’t @frozen and so the layout isn’t correctly specified, so this code doesn’t formally do what the library author probably thinks it does. nevertheless, it’s a very popular pattern, it is used, for example, by Google flatc.

if you’re handed an API like this, you run into some really inconsistent compiler policies surrounding definite initialization. you can’t, for example, do this:

extension S {
    init() {
        self = .init(x: 0)
        // Immutable value 'self.padding' may only be initialized once
        // Initial value already provided in 'let' declaration
        // Change 'let' to 'var' to make it mutable
    }
}

this is a rather useless guardrail, because self = new is something that should always be semantically valid for value types, regardless of field mutability, and it’s trivial (but awkward) to circumvent the restriction:

protocol P {
    static func create(x: Int) -> Self
}
extension P {
    init() {
        self = .create(x: 0)
    }
}

extension S: P {
    static func create(x: Int) -> Self {
        .init(x: 0)
    }
}

let s: S = .init()
5 Likes

Yeah, this looks like a bug to me.

7 Likes

Why not just write

extension S {
    init() {
        self.init(x: 0)
    }
}

...because it would not then demonstrate the bug...

5 Likes

good question

the most common reason why is because you get the data for the init from some API that vends a closure

extension S {
    init(xml: XML) {
        self = xml.parse { .init(x: $0.decode("x"), y: $0.decode("y")) } 
    }
}

classes Suck! you cannot do this with classes. that is why value semantics are exquisite

2 Likes
3 Likes