superpuccio
superpuccio

Reputation: 13012

Swift: generic init inside a non generic struct

Ok, I'll try to explain you what I'm trying to get with a minimum viable example: I'd like to have a struct like this:

struct MyStruct {
    let aBool: Bool
    let aInt: Int
    let aHashable: Hashable?
}

but of course this can't be done because:

Protocol 'Hashable' can only be used as a generic constraint because it has Self or associated type requirements

and this is fine. I can get what I want this way:

struct MyStruct<T> where T: Hashable {
    let aBool: Bool
    let aInt: Int
    let aHashable: T?
}

But I want my struct to have two init this way:

struct MyStruct<T> where T: Hashable {
    let aBool: Bool
    let aInt: Int
    let aHashable: T?

    init(aBool: Bool, aInt: Int) {
        self.init(aBool: aBool, aInt: aInt, aHashable: nil)
    }

    init(aHashable: T?) {
        self.init(aBool: false, aInt: 0, aHashable: aHashable)
    }

    private init(aBool: Bool, aInt: Int, aHashable: T?) {
        self.aBool = aBool
        self.aInt = aInt
        self.aHashable = aHashable
    }
}

And if I try to init the struct like this:

let myStruct = MyStruct(aBool: true, aInt: 10)

I get an error:

Generic parameter 'T' could not be inferred

The problem is that even if I turn the struct into a non generic struct (with a couple of generic init):

struct MyStruct {
    let aBool: Bool
    let aInt: Int
    let aHashable: T?

    init(aBool: Bool, aInt: Int) {
        self.init(aBool: aBool, aInt: aInt, aHashable: nil)
    }

    init<T>(aHashable: T?) where T: Hashable {
        self.init(aBool: false, aInt: 0, aHashable: aHashable)
    }

    private init<T>(aBool: Bool, aInt: Int, aHashable: T?) where T: Hashable {
        self.aBool = aBool
        self.aInt = aInt
        self.aHashable = aHashable
    }
}

I still get an error. This time on the let aHashable: T? stored property:

Use of undeclared type 'T'

What's the right way to get what I want? Thank you.

Upvotes: 1

Views: 273

Answers (2)

Rob Napier
Rob Napier

Reputation: 299703

The T you want in this case is Never, since it can never have a value. To define that kind of init, you need to constrain it in an extension like this:

extension MyStruct where T == Never {
    init(aBool: Bool, aInt: Int) {
        self.init(aBool: aBool, aInt: aInt, aHashable: nil)
    }
}

IMO, Swift should also allow this as:

init(aBool: Bool, aInt: Int) where T == Never {...}

But that's not currently legal Swift. You have to put it in an extension. It's just a syntax issue.

For completeness, here's the full code:

struct MyStruct<T> where T: Hashable {
    let aBool: Bool
    let aInt: Int
    let aHashable: T?

    init(aHashable: T?) {
        self.init(aBool: false, aInt: 0, aHashable: aHashable)
    }

    private init(aBool: Bool, aInt: Int, aHashable: T?) {
        self.aBool = aBool
        self.aInt = aInt
        self.aHashable = aHashable
    }
}

extension MyStruct where T == Never {
    init(aBool: Bool, aInt: Int) {
        self.init(aBool: aBool, aInt: aInt, aHashable: nil)
    }
}

let myStruct = MyStruct(aBool: true, aInt: 10)

Upvotes: 2

Yervand Saribekyan
Yervand Saribekyan

Reputation: 502

try this way

struct MyStruct<T: Hashable> {
    let aBool: Bool
    let aInt: Int
    let aHashable: T?

    init(aBool: Bool, aInt: Int) {
        self.init(aBool: aBool, aInt: aInt, aHashable: nil)
    }

    init(aHashable: T?) {
        self.init(aBool: false, aInt: 0, aHashable: aHashable)
    }

    private init(aBool: Bool, aInt: Int, aHashable: T?) {
        self.aBool = aBool
        self.aInt = aInt
        self.aHashable = aHashable
    }
}

than

let myStruct = MyStruct<'Your Hashable Type'>(aBool: true, aInt: 10)

Upvotes: 0

Related Questions