-1

I am writing a collector that collects metrics and stores in structs that looks something like this:

type Metric struct {
  Name string
  Data []float64
}

However for some metrics, it does not make sense to use float64, since their values are unsigned integers. Any idea how I could use different numeric types for the Data field?

I could use Data []interface{}, but then I won't be able to use indexing on the array elements.

(For clarity: I don't need different types in one slice, like a list in Python: my slice has to be strongly typed, but I want to be able to change the type of the slice.)

vgratian
  • 45
  • 9
  • 2
    What about defining another Metric struct, that stores data as type uint? Besides, why shouldn't you be able to use indexing on a slice of interfaces? [-> example](https://play.golang.org/p/oSiuSjCYNCx) – FObersteiner Mar 12 '21 at 08:58
  • 3
    You cannot "change the type of the slice" just like you can't change the type of any other value. Use multiple fields or different struct types, as suggested above. – Peter Mar 12 '21 at 09:38
  • thanks for the replies, I am trying now to define an interface "vector" and work with that. @MrFuppes, thanks, you're right, but doesn't this make the slice dynamically typed? performance is critical for my project, so I really want the same performance as with native slices. – vgratian Mar 12 '21 at 09:41
  • For clarity, I wouldn't use a slice of interface if I had only two types (and I know which). Regarding the interface slice, the slice would be of interface type (so basically static); but each element could hold reference to a different type. Compared to a list in Python, a list is an object, it doesn't have a type in that sense. – FObersteiner Mar 12 '21 at 09:52
  • thanks, that's exactly what I *don't* want - a python list. – vgratian Mar 12 '21 at 10:02

1 Answers1

1

For a full solution to this, you'll have to wait until generics lands in Go (potentially in 1.18): https://blog.golang.org/generics-proposal

With generics, you'd be able to have a generic Metric type that can either hold float64 or unsigned, and you could instantiate each of them separately.

E.g. (generics-enabled playgorund):

type Metric[T any] struct {
  Name string
  Data []T
}

func main() {
  mf := Metric[float64]{"foo", []float64{12.24, 1.1, 2.22}}
  mu := Metric[uint32]{"bar", []uint32{42, 2}}

  fmt.Println(mf)
  fmt.Println(mu)
}

Note that [T any] means that the type held in Data is unconstrained. You can constrain it to types with certain characteristics, or to a hardcoded list like float64, uint32 if you prefer.


In the meanwhile, there are some options:

  1. float64 can represent a lot of integers; at least all 32-bit ones (see Representing integers in doubles)
  2. You can use Data []interface{}, but it's rather wasteful. There should be no problem indexing into this slice, but you'll have to have type asserts whenever you work with it. It's costly both memory-wise and runtime performance-wise; something that can really matter for metrics.
  3. You can have two versions of Metric, with code duplication (and use code generation to help, if needed).
Eli Bendersky
  • 263,248
  • 89
  • 350
  • 412