Make self mutating in init()

replacing self is finicky to begin with. It breaks Liskov principle at almost every turn.

This isn‘t a question about mutability inside init, this is the factory pattern which isn‘t yet supported for classes through the init.

4 Likes

I have another idea

Actually the question is: I want to use something behave like init() to return subclass

We can do this now:

class C {
	init() { }
	static func `init`(use1: Bool) -> C { // Keyword 'init' cannot be used as an identifier here If this name is unavoidable, use backticks to escape it
		return use1 ? C1() : C2()
	}
}
class C1: C { }
class C2: C { }

then

let c = C.`init`(use1: <#T##Bool#>)

But we can't:

let c = C(use1: )

If I want to use C(use1: ), there is a actually way is callAsFunction, even It can't be use in static function and It will look very very confused because it's not named init():

class C {
	init() { }
	static func callAsFunction(use1: Bool) -> C {
		return use1 ? C1() : C2()
	}
}
class C1: C { }
class C2: C { }

let c = C(use1: true) // It is impossible now

So there is my simple solution, add a new attribute like @callAsFunction or other name (actually, I think callAsFunction should be done by using attribute from the beginning and make this feature be supported in Type, now just making all function named callAsFunction confusing everyone)

then:

class C {
	init() { }
	@callAsFunction static func `init`(use1: Bool) -> C {
		return use1 ? C1() : C2()
	}
}
class C1: C { }
class C2: C { }

let c = C(use1: true)

or

let c = C.`init`(use1: true) // is not good enough but at least it‘s clear

and the question from @Jens also can be avoid:

class C {
	init() { }
}
extension C {
	private class C1: C { }
	private class C2: C { }
	@callAsFunction static func `init`(use1: Bool) -> C where Self == C { // I think Self == C should be infered successed in Swift 5.3 
		return use1 ? C1() : C2()
	}
}

But having C1 and C2 be private C.C1 and C.C2 makes them inaccessible/unusable outside C, ie if you try to use them outside the scope of C, you'd get this error:
'C1' is inaccessible due to 'private' protection level
which I guess is not what you'd want.

if where Self == C work in 5.3, you can move C1, C2 outside if you want, and change the function to:

@callAsFunction static func `init`<R>(use1: Bool) -> R where Self == C, R: C { ... }

I just do that incidentally

I'm not seeing why

let c: C1 = C(use1: true)

would be better than simply

let c = C1()

though.

Can you give a more specific / real world example that shows the benefits of this?


Also, with your generic solution, how would R be resolved for eg:
let c = C(use1: Bool.random())
?
I guess R would have to be C?

there is a exmaple Generic type not equal in extension
because swift does't support generic type not equal in extension, so I think if I could use a subclass to distinguish when I don't need the userInfo

and I am sorry I make a mistake, init(use1: Bool) -> R where Self == C, R: C is never work, because swift can't infer R, even you use some C :sweat_smile:

This topic was discussed in the past
You can search for "factory init"

I know, the oldest proposal was created 5 years ago...

wow, I didn't know that before! If Opaque result type can infer diffent type in init(), there is a more easier way to do that:

init(use1: Bool) -> some {
	return use1 ? C1() : C2()
}

And I think it also can be used in Protocol extension, that looks cool

You can get close to what you want by adding a global function with the same name as your class and use that instead of an initializer. No need for language changes.

struct Foo {}
func Foo(bar: Int) {}

> Foo() // creates an instance of Foo
> Foo(bar: 42) // calls the function Foo

Or alternatively just add a static instantiation method to your class:

extension Foo {
    static func createInstance(foo: Int) -> Foo {}
}
1 Like

Please see this gist for a technique that allows you to write factory initializers.

5 Likes

It does't work
func Notificatable(name: String) -> Notificatable { // Invalid redeclaration of 'Notificatable(name:)'
...
}

Surprisingly, it works! I am confused as to why the self can be mutated in the protocol.

The protocol doesn’t know it’s going to be a class, so can’t prevent it.

This is fascinating - I don’t think this was possible in Swift, so thank you for posting it! I’m going to give it a try in a library I’m writing next week.

1 Like

It's been out there a while; I think I came up with it first while helping someone in the WWDC 2014 labs. Surprised more people don't know about it!

1 Like

Is there an easy way to verify that it only allocates space for a single class instance?

In other words, the part where it reassigns self, can we verify that the compiler did not actually allocate space for Self instance prior to that, only to then deallocate it after the reassignment?

swiftc -S FactoryInitializable.swift | xcrun swift-demangle and compare the code for testMe1 and testMe2

1 Like