deckarep / golang-set

A simple, battle-tested and generic set type for the Go language. Trusted by Docker, 1Password, Ethereum and Hashicorp.
Other
4k stars 272 forks source link

Issue: Contains #124

Closed maikefischer closed 10 months ago

maikefischer commented 11 months ago

This code creates decimals from the shopspring library.

Why does .Contains not return true in this case?

func TestContains(t *testing.T) {

    d1, _ := decimal.NewFromString("1.00")
    d2, _:= decimal.NewFromString("2.00")

    set1 := mapset.NewSet[decimal.Decimal](d1, d2)

    d3,_ := decimal.NewFromString("1.00")
    result := set1.Contains(d3) // returns false

    assert.True(t, result)
}
deckarep commented 11 months ago

Likely because the shopspring Decimal type doesn’t satisfy the comparable type. When using this Set only Go types that are comparable will work and this is by design.

It’s the same reason that map keys must be comparable as well. This library uses maps extensively under the hood.

deckarep commented 10 months ago

@maikefischer - I had a little more time to investigate this and I now I have an answer. It seems to be how the Decimal library works because when you invoke: NewFromString you get a Decimal value type back which embeds a pointer to big integer: value *big.Int.

While pointers can be compared in Go (the address itself) what you are likely seeing is that each call to NewFromString is giving you a fresh big.Int object for every time NewFromString is invoked even if the string value is the same.

In other words:

d1, _ := decimal.NewFromString("1.00")
d2, _:= decimal.NewFromString("1.00")

Both give you a `Decimal` value type but the underlying `value` fields of `*big.Int` are each their own instantiated object and it's not enough to compare just pointer types which is why `Decimal` also provides its own `Equal` implementation.

If you place these two items in a vanilla Go map, you'll observe the same problem.

I hope that is helpful. I'll close this for now.