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:

  1. Assuming that we have functions declared in two different forms: func A(h func() hash.Hash, ...) and func B[H hash.Hash](h func() H, ...).
  2. Functions using the interface form A() can call functions of the generic form B() without any issue.
  3. Functions using the generic form B() cannot call functions of the interface form A(). 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.