1
votes

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.

2
You would have to explicitly declare the generic type when calling your initializer: MyStruct<Something>(aBool: true, aInt: 10). Depending on how you're actually using this object it might work for you to keep it not generic and use AnyHashable? as the type for your property - dan
Hi Dan, thank you. I don't really like having to specify the type like this MyStruct<Something>(aBool: true, aInt: 10). It's really weird to force the user of this "api" to specify a random type that conforms to Hashable just to silence the problem. AnyHashable might be a solution, but I guess it could be a solution that doesn't involve type erasure. If I'm not wrong I've already seen some API from Apple doing the same. - matteopuc
What exactly are you tryign to achieve by this? Why is false/0 a valid substitute for a hashable thing? There's a lot of important back story here. - Alexander
@Alexander-ReinstateMonica It's not easy to explain exactly what I need (that's why I created this minimum example). But I just found an API from Apple that does exactly what I'd like to do. Take a look at developer.apple.com/documentation/swiftui/navigationlink it has several init. For example: init(destinationName: String, isActive: Binding<Bool>, @ViewBuilder label: () -> Label) and init<V>(destination: Destination, tag: V, selection: Binding<V?>, @ViewBuilder label: () -> Label) where V : Hashable The second one is generic, but the main Struct (NavigationLink) is not. - matteopuc
The reason I'm pressing for the motivation here is because I very highly suspect this is an X/Y problem. I suspect your example has distilled down too much. The example of NavigationLink isn't exactly relevant, because it doesn't store a value of a generic type (as is the case here with aHashable: T). There's nothing special about a non-generic type having a generic func/init, and that's just what NavigationLInk is. - Alexander

2 Answers

2
votes

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)
0
votes

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)