Make self mutating in init()

I am trying to write a class base on class clusters like NSArray:

class C {
	init() { }
	mutating init(use1: Bool) { // 'mutating' may only be used on 'func' declarations
		self = use1 ? C1() : C2() // Cannot assign to value: 'self' is immutable
or
		return use1 ? C1() : C2() // 'nil' is the only return value permitted in an initializer
	}
}
class C1: C { }
class C2: C { }

is there any problems to achieve init with subclass in swift? I mean it could use static func to return subclass, but that is too weird that you can't use the init() right?

4 Likes

One scenario this could be tricky is when you're trying to subclass it.

class C3: C {
  init() {
    super.init(use1: true) // !??
  }
}

yes, it's a problem

You wouldn't even need to subclass to get into a weird situation, because if what you asked for would be allowed, then the initializer would also be available on C1, C2 and any other subclass of C, letting you write eg:

let myC2: C2 = C2(use1: true)
print(myC2 is C1) // true ... I guess.

which seems a bit nonsensical (since C1 is not a subclass of C2).

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.