←back to thread

480 points jedeusus | 1 comments | | HN request time: 0.001s | source
Show context
stouset ◴[] No.43543575[source]
Checking out the first example—object pools—I was initially blown away that this is not only possible but it produces no warnings of any kind:

    pool := sync.Pool{
        New: func() any { return 42 }
    }

    a := pool.Get()

    pool.Put("hello")
    pool.Put(struct{}{})

    b := pool.Get()
    c := pool.Get()
    d := pool.Get()

    fmt.Println(a, b, c, d)
Of course, the answer is that this API existed before generics so it just takes and returns `any` (née `interface{}`). It just feels as though golang might be strongly typed in principle, but in practice there are APIs left and rigth that escape out of the type system and lose all of the actual benefits of having it in the first place.

Is a type system all that helpful if you have to keep turning it off any time you want to do something even slightly interesting?

Also I can't help but notice that there's no API to reset values to some initialized default. Shouldn't there be some sort of (perhaps optional) `Clear` callback that resets values back to a sane default, rather than forcing every caller to remember to do so themselves?

replies(5): >>43543875 #>>43544042 #>>43544109 #>>43544700 #>>43546668 #
tgv ◴[] No.43543875[source]
You never programmed in Go, I assume? Then you have to understand that the type of `pool.Get()` is `any`, the wildcard type in Go. It is a type, and if you want the underlying value, you have to get it out by asserting the correct type. This cannot be solved with generics. There's no way in Java, Rust or C++ to express this either, unless it is a pool for a single type, in which case Go generics indeed could handle that as well. But since Go is backwards compatible, this particular construct has to stay.

> Also I can't help but notice that there's no API to reset values to some initialized default.

That's what the New function does, isn't it?

BTW, the code you posted isn't syntactically correct. It needs a comma on the second line.

replies(6): >>43543989 #>>43544008 #>>43544021 #>>43544567 #>>43545116 #>>43546665 #
gwd ◴[] No.43544008[source]
> That's what the New function does, isn't it?

But that's only run when the pool needs to allocate more space. What GP seems to expect is that sync.Pool() would always return a zeroed structure, just as Golang allocation does.

I think Golang's implementation does make sense, as sync.Pool() is clearly an optimization you use when performance is an issue; and in that case you almost certainly want to only initialize parts of the struct that are needed. But I can see why it would be surprising.

> [any] is a type

It's typed the way Python is typed, not the way Rust or C are typed; so loses the "if it compiles there's a good chance it's correct" property that people want from statically typed languages.

I don't use sync.Pool, but it does seem like now that we have generics, having a typed pool would be better.

replies(4): >>43544056 #>>43544305 #>>43544487 #>>43548531 #
1. stouset ◴[] No.43548531[source]
> But that's only run when the pool needs to allocate more space. What GP seems to expect is that sync.Pool() would always return a zeroed structure, just as Golang allocation does.

Not quite that. Imagine I have a pool of buffers with a length and capacity, say when writing code to handle receiving data from the network.

When I put one of those buffers back, I would like the next user of that buffer to get it back emptied. The capacity should stay the same, but the length should be zero.

I think it’s reasonable to have a callback to do this. One, it doesn’t force every consumer of the pool to have to remember themselves; it’s now a guarantee of the system itself. Two, it’s not much work but it does prevent me from re-emptying freshly-allocated items (in this case reinitialzing is fast, but in some cases it may not be).

This also should be an optional callback since there are many cases where you don’t want any form of object reset.