1

I have the following structs:

struct A<T> {
    var val: T
}

struct B<T> {
    var setOfA: Set<A<T>>
}

Is this incorrect usage of generics, if the set of As can have values of both A<Int> and A<Float> type (or any other type)?

If yes, is using Any the correct way to do this?

Would generics have been the right way to go, if the set of As was constrained to A<>s of a single type?

Kunal Shah
  • 1,071
  • 9
  • 24
  • 2
    You might just be able to make them conform to the [Numeric Protocol](https://developer.apple.com/documentation/swift/numeric?changes=_4) instead. – ZGski Jun 20 '18 at 21:52
  • Int and Float were just examples. It could literally be of any type. – Kunal Shah Jun 20 '18 at 21:54
  • Possible duplicate of [Difference between Generics and AnyObject in Swift](https://stackoverflow.com/questions/31041265/difference-between-generics-and-anyobject-in-swift) – ZGski Jun 20 '18 at 21:59
  • You need to make your struct and generic type conform to Hashable. `struct A: Hashable { var val: T }` and `struct B { var setOfA: Set> }` – Leo Dabus Jun 21 '18 at 01:36
  • @LeoDabus There is no need for `struct B` to conform to `Hashable`, and there's neither a need for `T` to do that. With the generic type `T` in `B`, the `A`s in the `Set` can only be of the same type. – Andreas is moving to Codidact Jun 21 '18 at 08:20

2 Answers2

2

Swift is a moving target, all code tested using Xcode 9.2 & Swift 4.0.

First if you wish to insert A<T> items into a set then the type must implement the Hashable protocol. You can implement this protocol as needed but for illustrative purposes a simple approach is to require the type parameter T to be Hashable and to implement the protocol by indirecting to val:

struct A<T> : Hashable where T : Hashable
{
    var val : T

    var hashValue: Int { return val.hashValue }

    static func ==(lhs: A<T>, rhs: A<T>) -> Bool
    {
        return lhs.val == rhs.val
    }
}

The rest of the answer does not require the above implementation, just that A<T> is Hashable.

Constraining your Set to only containing instances of A<T> for any and all T is more of a challenge. One approach to this problem is to use an existential type, which in this context essentially allows the wrapping of differently typed value inside another non-generic wrapper. Swift has some pre-defined existential types, in particular AnyHashable which you could use to define B's var:

struct B
{
    var setOfA : Set<AnyHashable> = Set()

    ...
}

Unfortunately this does not constrain the set to only hold values of type A<T> for any T.

The type of setOfA cannot be Set<A<AnyHashable>> as that would require a value of type A<T> for some T to be cast-able to A<AnyHashable> and Swift does not support that (researching variance might help understand why).

You might wonder why the type cannot be Set<Hashable>. Unfortunately (in this context!) Hashable is a protocol with a Self requirement and Swift does not support using it as a generic type parameter in this context. AnyHashable is a non-generic struct which type erases (the existential type bit) the value it wraps.

So back to solving the problem of constraining the set to only contains A's. One solution is to hide (private) the set inside B and provide a generic insertion function which only accepts A<T> values, for any T. The set value itself can then be made public using a read-only computed property. For example:

struct B
{
    // private property so only B's functions can modify the set
    private var _setOfA : Set<AnyHashable> = Set()

    // public read-only property so _setOfA cannot be changed directly
    var setOfA : Set<AnyHashable> { return _setOfA } 

    // public insert function which only accepts A<T> values, for any T
    mutating func insert<T>(_ element : A<T>) -> (Bool, A<T>)
    {
        return _setOfA.insert(element)
    }
}

Here is dome sample code using the above:

let x = A(val: 4)        // x : A<Int>
let y  = A(val: "hello") // y : A<String>
var z = B()
print(z.insert(x)) // insert being generic accepts any A<T>
print(z.insert(y))
print(z.insert(x)) // should return (false, x) as element already added
print("\(z)")
for member in z.setOfA
{
    print("member = \(member) : \(type(of:member))")
    if let intMember = member as? A<Int>
    {
        print("   intMember = \(intMember) : \(type(of:intMember))")
    }
    else if let stringMember = member as? A<String>
    {
        print("   stringMember = \(stringMember) : \(type(of:stringMember))")
    }
}

This outputs:

(true, __lldb_expr_501.A<Swift.Int>(val: 4))
(true, __lldb_expr_501.A<Swift.String>(val: "hello"))
(false, __lldb_expr_501.A<Swift.Int>(val: 4))
B(_setOfA: Set([AnyHashable(__lldb_expr_501.A<Swift.String>(val: "hello")), AnyHashable(__lldb_expr_501.A<Swift.Int>(val: 4))]))
member = A<String>(val: "hello") : AnyHashable
   stringMember = A<String>(val: "hello") : A<String>
member = A<Int>(val: 4) : AnyHashable
   intMember = A<Int>(val: 4) : A<Int>

HTH

CRD
  • 52,522
  • 5
  • 70
  • 86
1

If you plan on letting your struct contain val of a specific type, use <T: DesiredProtocol>. If the type doesn't matter, and your val can be of any type, just implement the structure as struct A<T> {}. A<T> is equal to A<T: Any>.

Note that your example won't compile if you intend to have setOfA contain A with different types.

The elements in a Set must be hashable.

In B<T>, T represents only one type. Therefore, you cannot store different types in Set<A<T>>.

Because of this requirement, struct A<T> must conform to the Hashable protocol, and the implementation of B must be changed.

struct A<T>: Hashable {
    var val: T

    // Implementation of Hashable
}

/// This struct should not be generic. It would constrain its setOfA to only contain the same type. 
/// By removing the generics, and use Any instead, you let each element have its own type.
struct B {
    var setOfA: Set<A<Any>>
}

var set: Set<A<Any>> = Set()
set.insert(A(val: 0 as Int))
set.insert(A(val: 1 as Float))

let b = B(setOfA: set)

Edit:

Due to the fact that you're looking for a way to insert a generic A<T> into A<Any> without specifying its type as A<Any> on creation, I've come up with two different ways:

let v1 = A(val: 5)
var v2 = A(val: 3)

aSet.insert(A(val: v2.val))
aSet.insert(withUnsafePointer(to: &v3, { return UnsafeRawPointer($0) }).assumingMemoryBound(to: A<Any>.self).pointee)

Edit

If it is as you write, that B's setOfA would only have A<> of a single type, it's the choice of the context whether you should use generics or not. The question is rather if struct B needs to be generic. struct A can be a value type for many different tasks, while struct B doesn't need to. If B is more specific, you could rather just write:

struct B {
    var setOfA: Set<A<Int>>
}

As above-mentioned, if you intend to have the same set contain A<Int> and A<Float>, then var setOfA: Set<A<Any>> is the correct way.

If struct A has a very specific use-case, and you don't actually need it to be generic, you should change the type of val. Your code should be understandable and comprehensive. I would recommend one of these solutions:

struct A: Hashable {
    
    var val: Any
    // or
    var val: Numeric

    // Implementation of Hashable
}
Community
  • 1
  • 1