Allow passing more concrete types for any

Consider the following snippet (playground)

package main

import "fmt"

type Foo struct{}

func foo() (Foo, error) {
	return Foo{}, nil
}

type Bar struct{}

func bar() (Bar, error) {
	return Bar{}, nil
}

func Wrap(fn func() (any, error)) func() string {
	return func() string {
		d, err := fn()
		if err != nil {
			return err.Error()
		} else {
			return fmt.Sprint(d)
		}
	}
}

func main() {
	_ = Wrap(foo)
	_ = Wrap(bar)
}

Trying to compile this yields the following error:

./prog.go:29:11: cannot use foo (value of type func() (Foo, error)) as func() (any, error) value in argument to Wrap
./prog.go:30:11: cannot use bar (value of type func() (Bar, error)) as func() (any, error) value in argument to Wrap

Any way to change the signature of Wrap to allow for this? A workaround currently is to do

func main() {
	_ = Wrap(func() (any, error) { return foo() })
}

but that is quite cumbersome.

You could use generics.

Replace

func Wrap(fn func() (any, error)) func() string {

with

func Wrap[T any](fn func() (T, error)) func() string {
3 Likes

If you’re using generics you could create a generic type more restrictive than any. For example,

func Wrap[T Foo | Bar](fn func() (T, error)) func() string {
...
}
1 Like

Thanks for the suggestion, but there are actually quite a few types that would make this cumbersome again. Since the implementation of Wrap actually does work with any object, there is no need to restrict it artificially.

Yeah, this happens because Go doesn’t allow implicit conversion of concrete return types to any. You’ll need to explicitly wrap the function call.

One way to adjust Wrap is to use generics:

func Wrap[T any](fn func() (T, error)) func() string {
	return func() string {
		d, err := fn()
		if err != nil {
			return err.Error()
		}
		return fmt.Sprint(d)
	}
}

This should let you use Wrap(foo) and Wrap(bar) without needing the extra wrapper function.