.init is called an implicit member expression, which you can read about here. There's no difference when they both work, but an implicit member expression requires a contextual type so it knows where to look up the method/property (provided here by the self = assignment).
in this case self.init(n: xxx) is obviously better as with self = .init(xxx) form the two initialisers will be called instead of just one (*). general rule - use self.init(xxx) unless impossible.
(*) these two: init(n:) and init(ccc:)
hmm. now that i wrote that i realised that these two inits will be called with self.init(...) version as well... don't know why, but it "feels" that the self.init(...) version is more correct...
probably this:
when you call self = .init(n: 1) the "init(n:)" will be called on a different variable (compared to the variable that is currently being initialled in init(ccc:)), which memory contents is later copied to the current variable. with the "self.init(n:)" version the second initialiser is called on the same variable thus there is no need of extra copy. that's why it feels slightly better (and is indeed slightly faster).
self = .init() is just a combination of the two distinct & general features. .init(...) alone is useful when the type of the initializer is known, and/or is a hassle to type, e.g., in a function argument like foo(.init()). On the other side, self = is useful when you already have an object that you want to replaceself with.
self.init(), OTOH, will initialize self with that particular initializer. It is much more lenient for structs & protocols, but for classes, it requires that you follow the initialization rules, including two-phase initialization.
Note though, because self = .init() is an assignment, this is permitted on any mutating functions but is not allowed when self is known to be a class instance*, while self.init() is only allowed in another initializer.
Personally, I tend to prefer self.init() when it's inside an initializer. Mostly because it's aligned with how you'd write a class initializer. I of course need to use self = outside of an initializer, though I only do that when I want to reset the instance to a default state.
Performance-wise, I'd be surprised if the compiler wouldn't generate similar code (worthy of bug report even). It may affect the type-checking performance, but at some point, it's just another i++ vs ++i. You'd better spend energy on something else than shaving a few ms off the compile time.
* There are some subtleties when self is a protocol with an underlying class.
yet another minor reason to prefer self.init(...) form by default:
struct Foo {
let n: Int
let one = 1
}
extension Foo {
init(ccc: Bool) {
self = ccc ? .init(n: 1) : .init(n: 0)
// Error: Immutable value 'self.one' may only be initialized once
}
}