Sasha
Sasha

Reputation: 764

Recursive generic structs with different types in Swift 4

struct Lock<Element: Hashable> {

    var element: Element

    init(_ element: Element, _ args:[Lock<Element>]? = nil) {
        self.element = element
    }
}

Is it possible to modify this kind of struct to be able call init() with different Element types in arguments?

Lock("element", [Lock("100")]) it's ok

Lock("element", [Lock(100)]) this causes an error: Cannot convert value of type 'Int' to expected argument type 'Lock<_>'

Upvotes: 4

Views: 417

Answers (3)

Guy Kogus
Guy Kogus

Reputation: 7341

You have 2 options for doing this.

The simple solution, improving on @Cristik's answer, is to have another initialiser that doesn't expect another generic parameter:

struct Lock<Element> {
    var element: Element

    init(_ element: Element) {
        self.element = element
    }
    init<T>(_ element: Element, _ args: [Lock<T>]?) {
        self.init(element)
    }
}

You can then have the code you want above, but you lose the information that Element conforms to Hashable.


Your second option is to create a protocol using an associatedtype. Using a similar trick with having 2 inits, you can do the same except explicitly defining the types:

protocol Lock {
    associatedtype Element

    init(_ element: Element)
    init<T>(_ element: Element, _ args: [T]?) where T: Lock
}

struct HashableLock<H: Hashable>: Lock {
    typealias Element = H

    var element: Element

    init(_ element: Element) {
        self.element = element
    }
    init<T>(_ element: Element, _ args: [T]?) where T: Lock {
        self.init(element)
    }
}

struct IntLock: Lock {
    typealias Element = Int

    var element: Int

    init(_ element: Int) {
        self.element = element
    }
    init<T>(_ element: Int, _ args: [T]?) where T: Lock {
        self.init(element)
    }
}

Then you can create locks like this:

let stringStringLock = HashableLock("element", [HashableLock("100")])
let stringIntLock = HashableLock("element", [IntLock(100)])

The first version is a lot cleaner but it's more limiting. It's up to you which one to use, depends on your needs.

Upvotes: 1

Cristik
Cristik

Reputation: 32825

You might get better results by making the initializer generic:

struct Lock<Element: Hashable> {

    var element: Element

    init<T>(_ element: Element, _ args:[Lock<T>]? = nil) {
        self.element = element
    }
}

Upvotes: 0

Paolo
Paolo

Reputation: 3945

This cannot be done.

The reason is that Element gets assigned to the type used for the element parameter in init. Now that same type has to be used whenever Element appears in the struct but if you want the generic type of Lock to be anything else, you'll need to declare another generic type.

The problem here is that by adding another generic type to Lock (i.e. Lock<Element, AnotherElement>) is that now the Lock in your init will need to specify two generic types, and so on.

Upvotes: 0

Related Questions