1

In the following Swift code:

import Foundation

struct MySet<T: Hashable>: CustomStringConvertible {
    let set: Set<T>
}

extension MySet {
    var description: String {
        return "\(self.set)"
    }

}

extension MySet where T: Comparable {
    var description: String {
        return "\(self.set.sorted())"
    }
}

let intSet: Set<Int> = [6, 3, 4, 9, 12, 49, -1, 44, -1000, 1000]
let myIntSet = MySet(set: intSet)
print("myIntSet: \(myIntSet.description)")
print("myIntSet: \(myIntSet)")
print("myIntSet: ", String(describing: myIntSet))

I'm trying to create a custom description (for the CustomStringConvertible protocol) that is different depending on whether the elements of the set are Comparable. The output of the above is:

myIntSet: [-1000, -1, 3, 4, 6, 9, 12, 44, 49, 1000]
myIntSet: [9, -1, 3, 1000, 6, 4, 12, 44, 49, -1000]
muIntSet:  [9, -1, 3, 1000, 6, 4, 12, 44, 49, -1000]

As you can see, if I explicitly print myIntSet.description, I get the desired output, which is a sorted list. But if I use string interpolation on the struct itself, or String(describing: myIntSet), I get the description from the first (unconditional) extension.

Can anyone explain why this is happening? How do I get string interpolation and String(describing: ...) to use the desired description?

3
  • 1
    Not related to your question but initializing a new array before sorting it is pointless. You can use sort method directly on your set. var description: String { self.set.sorted().description } Commented Mar 24 at 20:33
  • I believe you need to look into the StringInterpolationProtocol and related protocols, at least for the string interpolation part. I added mutating func appendInterpolation(_ value: MySet<Int>) { appendInterpolation((value.description)") } in an extension to String.StringInterpolation and now the string interpolation example also work but as you can see it's a very simplified solution since the generic type is hardcoded. Commented Mar 24 at 21:05
  • Thanks. I'd prefer not to have to write extensions to any other types. Commented Mar 24 at 22:10

1 Answer 1

2

Every protocol requirement can only have one witness. In this case the witness for CustomStringConvertible.description is the one declared in the first MySet extension. The description declared in the second extension only hides the description declared in the first extension, and is not a witness to the protocol requirement.

When you access description on a comparable MySet, you cannot access the hidden implementation, but when description is accessed through the CustomStringConvertible protocol (indirectly by string interpolation or String.init(describing:)), only the protocol witness can be accessed.

This means that all the behaviour you want must be put in the description declared in the first extension. For example, you can add your own protocol that allows you to provide a custom description, then check if self conforms to that protocol in description.

protocol CustomSetDescribable {
    var customDescription: String { get }
}

struct MySet<T: Hashable>: CustomStringConvertible {
    let set: Set<T>
}

extension MySet {
    var description: String {
        (self as? any CustomSetDescribable)?.customDescription ?? "\(self.set)"
    }

}

extension MySet: CustomSetDescribable where T: Comparable {
    var customDescription: String { "\(set.sorted())" }
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.