Thanks for your help and very interesting ideas. In the end I used this:
type Set[T comparable] map[T]struct{}
func New[T comparable](elements ...T) Set[T] {
set := make(Set[T], len(elements))
for _, element := range elements {
set[element] = struct{}{}
}
return set
}
func (me Set[T]) String() string {
elements := make([]T, 0, len(me))
for element := range me {
elements = append(elements, element)
}
sort.Slice(elements, func(i, j int) bool {
return less(elements[i], elements[j])
})
s := "{"
sep := ""
for _, element := range elements {
s += sep + asStr(element)
sep = " "
}
return s + "}"
}
func asStr(x any) string {
if s, ok := x.(string); ok {
return fmt.Sprintf("%q", s)
}
return fmt.Sprintf("%v", x)
}
func less(a, b any) bool {
switch x := a.(type) {
case int:
return x < b.(int)
case float64:
return x < b.(float64)
case string:
return x < b.(string)
default:
return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b)
}
}
Interestingly, I couldn't put the asStr() code in the String() function since doing so produced this error:
invalid operation: cannot use type assertion on type parameter value element (variable of type T constrained by comparable)
Anyway, I'm happy that it all works now. (I know I ought to include every int & float32, but this is enough for now).