Two convenience initializers can call each other forever

I saw this other discussion, where a convenience init was allowed to call itself:

I’m not sure if this is the same issue. Two convenience inits calling each other wasn’t flagged by the compiler, despite the init chain never ending in a designated initializer. Xcode 26.2, Swift 5

class Person {
    var name: String
    var age: Int
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
    convenience init(name: String) {
        self.init(age: 23)
        self.name = name
    }
    convenience init(age: Int) {
        self.init(name: "bob")
        self.age = age
    }
}
// spins forever
let bob = Person(name: "bob")
1 Like

FWIW, also true in Xcode 26.4, Swift 6 mode. Xcode made a few thousand stack frames trying to do it before somehow hitting a bad access. Maybe a stack smash?

Ooooo, in thinking about this, having flexibility as a goal makes it difficult to check this without simply trying it, since you might want to have a particular convenience init call another convenience init... so I guess you'd need to have to check that somewhere in a chain you'd reach a designated init.

If I were designing a language, I'd probably opt for a rule that said convenience inits must call designated inits, despite the possible limitation that would impose. But then, I'm not anyone's wise first choice for language design.

I don't really see this as a language defect; it's just a novel way to write an infinite recursion, like you can in any other language. Would it be nice if Swift caught this? Sure, but only in the same way that it'd be nice if it caught any other infinite recursion.

10 Likes

Reasonable. My preference is for a language to gently discourage tricksy constructions, as they often lead to "clever code", i.e. hard to debug. That said, I'd hate to also discourage a clear and understandable construction.

I would agree that infinite recursion like this is in the hands of the programmer.

I appreciate that this is a forced example however I would expect both convenience initialisers to be calling the designated initialiser with a fixed parameter value; so no recursion. Up to the programmer to construct the initialisers into a pyramid.

1 Like

I don’t think it’s a language defect either. But I do think the compiler should catch the fact that the init rule about all init paths leading to a designated init is being broken. Maybe the other init rules (like convenience inits not calling up to the superclass) are just easier to detect than the ones about init paths.