Some newer standard libraries like crypto/hkdf
started using the generics form of func[H hash.Hash](h func() H, ...)
. For example,
func Expand[H hash.Hash](h func() H, pseudorandomKey []byte, info string, keyLength int) ([]byte, error)
Some older standard libraries like crypto/hmac
are still using the interface form of func(h func() hash.Hash, ...)
. For example,
func New(h func() hash.Hash, key []byte) hash.Hash
When mixing the two forms of functions, it creates asymmetric incompatibility:
- Assuming that we have functions declared in two different forms:
func A(h func() hash.Hash, ...)
andfunc B[H hash.Hash](h func() H, ...)
. - Functions using the interface form
A()
can call functions of the generic formB()
without any issue. - Functions using the generic form
B()
cannot call functions of the interface formA()
. You get a compiler error.
The following are two Go Playground examples (using Go 1.25) illustrating this issue:
Example 1: the interface form function calling the generic form function
package main
import (
"crypto/hkdf"
"fmt"
"hash"
)
func test(h func() hash.Hash, pseudorandomKey []byte, info string, keyLength int) ([]byte, error) {
// func Expand[H hash.Hash](h func() H, pseudorandomKey []byte, info string, keyLength int) ([]byte, error)
return hkdf.Expand(h, pseudorandomKey, info, keyLength)
}
func main() {
fmt.Println("Hello, 世界")
}
// You get "Hello, 世界", when running it. No compiler error
Example 2: the generic form function calling the interface form function
Note that I am using the same function named Expand
in golang.org/x/crypto/hkdf
which happened to use the interface form.
package main
import (
"fmt"
"hash"
"io"
"golang.org/x/crypto/hkdf"
)
func test[H hash.Hash](h func() H, pseudorandomKey []byte, info []byte) io.Reader {
// func Expand(hash func() hash.Hash, pseudorandomKey, info []byte) io.Reader
return hkdf.Expand(h, pseudorandomKey, info)
}
// try converting h into the interface form
func test1[H hash.Hash](h func() H, pseudorandomKey []byte, info []byte) io.Reader {
// func Expand(hash func() hash.Hash, pseudorandomKey, info []byte) io.Reader
return hkdf.Expand((func() hash.Hash)(h), pseudorandomKey, info)
}
func main() {
fmt.Println("Hello, 世界")
}
/* When running it, you get:
go: finding module for package golang.org/x/crypto/hkdf
go: downloading golang.org/x/crypto v0.42.0
go: found golang.org/x/crypto/hkdf in golang.org/x/crypto v0.42.0
# play
./prog.go:13:21: cannot use h (variable of type func() H) as func() hash.Hash value in argument to hkdf.Expand
./prog.go:18:40: cannot convert h (variable of type func() H) to type func() hash.Hash
*/
Is this expected behavior? Or an oversight of the type design?
Comment From: TapirLiu
You need to write it as
return hkdf.Expand(func() hash.Hash {return h()}, pseudorandomKey, info)
Comment From: seankhliao
see above, and https://go.dev/doc/faq#covariant_types
Comment From: hclihn
Thanks! The concept of covariant types is a bit subtle. It would great if this can be illustrated with a solid example in Go's documentation.