Interface with method that has interface{} parameter not working - go

I'm trying to implement some interfaces in Go.
I have InterfaceA:
type InterfaceA interface{
read(interface{}) string
}
I then have InterfaceB:
type InterfaceB interface{
fetch()
}
I have a function:
func Read(a InterfaceA) {}
I have StructA, which satisfies InterfaceA via it's methods, but instead of having a variable "interface{}", it is passed InterfaceB as such:
type StructA struct {}
func (a *StructA) read(b InterfaceB) string {
resp := b.fetch()
return resp
}
This seems to work for my unit tests, but when I import the package I get an error stating that InterfaceA is not implemented correctly, because it is expecting "read(interface{})" not "read(InterfaceB)". Error:
StructA does not implement InterfaceA (wrong type for read method)
have read(InterfaceB) string
want read(interface {}) string
The reason I'm trying to pass an interface to the read() function is so that I can mock out the "i.fetch()" method, but still test the rest of the read() function.
I also can't pass the read method of StructA an interface parameter (e.g. read(b interface{}), because then b won't have the fetch() method.
Am I doing this wrong? I thought that this was working as my unit tests work. I've only encountered the issue when importing the package.

thanks for the comments. I've managed to get this to work via type type assertions. More details can be found here: golang.org/ref/spec#Type_assertions

Related

Type assertions with interface embedding in Go

I have an interface and it is embedded into multiple structs that
form a decorator pattern.
Now I have added code to typecast Validator and APIService to the API interface. So that if I add and a function to the interface then I should be able to add it at Validator and APIService.
But this code seems to compile.
I should have got a compiler error as I have not implemented Get on Validator
If I remove Get from APIService I get an error which says I cant typecast to *APIService to API as get is not implemented.
package main
import "fmt"
type API interface {
Get()
}
var _ API = (*Validator)(nil)
var _ API = (*APIService)(nil)
type APIService struct {
id string
}
type Validator struct {
API
}
type APIRouter struct {
API
}
func(a *APIService) Get(){
fmt.Println("Calling Get api on APIService")
}
func NewAPIRouter() *APIRouter{
return &APIRouter{
API: &Validator{
API: &APIService{
},
},
}
}
func main() {
var api API = NewAPIRouter()
api.Get()
}
If I compile the above code:
./main
Calling Get api on APIService
But if I comment Get function on APIService i get the below error which is valid:
go build
# main
./main.go:9:5: cannot use (*APIService)(nil) (type *APIService) as type API in assignment:
*APIService does not implement API (missing Get method)
I should have got a compiler error before as well as I have not implemented Get on Validator
If I remove Get from APIService I get an error which says I cant typecast to *APIService to API as get is not implemented.
The type APIService is a struct with a method Get, so it implements API interface.
The type Validator is a struct with an embedded interface API. This means, a Validator instance has an embedded member that implements the API interface, and since it is embedded, Validator also implemented the API interface. However, note that you have to initialize Validator.API to an implementation first. Something like this should work:
v:=Validator{API:&APIService{}}
With this, v.Get() will call the embedded APIService.Get.

How to use a type parameter in an interface method?

I am trying to write a sample program to try and implementing the a data structure using Go generics.
As part of this I want to define a iterator interface. I have the following code:
package collection
type Iterator interface {
ForEachRemaining(action func[T any](T) error) error
// other methods
}
It keeps giving me following error
function type cannot have type parameters
Moving the type parameter to the method also doesn't work:
type Iterator interface {
ForEachRemaining[T any](action func(T) error) error
// other methods
}
Gives error:
methods cannot have type parameters
Is there any way to define generic interface
As the error suggests, methods cannot have type parameters of their own as per the latest design. However, they can use the generics from the interface or struct they belong to.
What you need is to specify the type parameter on the interface type as follows:
type Iterator[T any] interface {
// ...
}
and then use the T as any other type parameter in methods within the interface body. For example:
package main
import "fmt"
type Iterator[T any] interface {
ForEachRemaining(action func(T) error) error
// other methods
}
func main() {
fmt.Println("This program compiles")
}
Try it on Go playground.

Override interface's function callback arguments

I am using a package that has a Router interface, and I have created my own app-specific Router interface that wraps the third party package.
Everything is working well, however one of the methods is throwing a compilation error:
controllers/auth.go:52:17: cannot use func literal (type func(router.Router)) as type func(chi.Router) in argument to c.router.Group
This is the interface of the third party package (chi):
type Router interface {
// ...
// Group adds a new inline-Router along the current routing
// path, with a fresh middleware stack for the inline-Router.
Group(fn func(r Router)) Router
// ...
}
This is my wrapper interface:
type Router interface {
chi.Router
// Custom methods...
}
My usage of the Group function is like so:
type AuthController struct {
router router.Router
// ...
}
func (c *AuthController) SetRoutes() {
c.router.Group(func(r router.Router) {
r.Use(middleware.Anyone)
r.Post("/auth/register", c.Register)
r.Post("/auth/login", c.Authenticate)
r.Post("/auth/token/refresh", c.RefreshToken)
})
c.router.Group(func(r router.Router) {
r.Use(middleware.Authorized)
r.Get("/auth/ping", c.Ping)
r.Post("/auth/logout", c.Logout)
})
}
Why is it screaming at my function callbacks argument type? My wrapper router.Router implements the chi.Router interface, so it should work fine shouldn't it? Am I misunderstanding how Go works here?
I can see how this can be confusing so I will try to break it down. You have this method:
Group(fn func(r Router)) Router
This method takes a function as a parameter. That function must have a specific signature:
func(r Router)
That is, it takes a single argument of type chi.Router and has no return values. However, when you call it:
c.router.Group(func(r router.Router) { /***/ }
You're passing in a function of the wrong signature; your function signature is:
func(r router.Router)
That's not the signature required by the method you're calling, so it won't compile. It doesn't matter if router.Router implements chi.Router; the parameter (a func(router.Router)) passed is not of the expected type (a func(chi.Router)).
This may seem silly at first - after all, any router.Router must implement chi.Router. But, think about it: that method, Group, is expecting to receive a function, to which it can pass any chi.Router. That means it can pass a chi.Router which does not implement router.Router. If it were to accept your function, it would break type safety, and what in Go is meant to be a compile-time error (the error you're getting, in fact) would become a run-time error. Basically, by passing a function with a different (and more strict) argument type, you're expecting a guarantee which that method never offered.
The parameter types aren't the same, so the function type doesn't match what's expected, even though your interface includes the interface from the other package (the type has to match exactly). You need to have your functions take a chi.router and then use a type assertion, i.e., myRouter := r.(Router), to convert to your type.

Calling methods from derived type in Go

I have two files, generic_handler.go, which essentially looks like this:
type Handler struct{
fields map[string]interface{}
}
func (handler *Handler) addField(key string, value interface{}){ some code }
And stat_handler.go:
type StatHandler Handler
When I try to call (from within the stat_handler.go file) the method like this, I get an error:
//inside some function
stathandler.addField("some field", "some value")
It errors out with type StatHandler has no field or method addField. This is the same even if I export addField by making it AddField. Both of these files reside in the same package.
Is there something I am missing?
In Go you'd typically do this through composition:
type StatHandler struct {
Handler // Anonymous member of type Handler
}
// ...Later on
var stathandler StatHandler
stathandler.addField("some field", "some value")
Please be aware that in Go in order to access a method from another package you need to export it, meaning that you have to capitalize the first letter of the method.
type Handler struct{
fields map[string]interface{}
}
func (handler *Handler) AddField(key string, value interface{}){ some code }
Then if your package is named as stathandler you can access the AddField method as:
stathandler.AddField("some field", "some value")
A declared method belongs to a specific type. The type keyword creates a new type which does not "inherit" methods of the type you used in the type specification.
The method addField() you declared belongs to Handler, the type StatHandler does not have any methods because you haven't specified any methods with that as the receiver type.
If you have a variable of type StatHandler and if you want to call the addField() method, you need to have a value of type *Handler. You may obtain such a value by using a type conversion, e.g.:
s := StatHandler{}
(*Handler)(&s).addField("somekey", "someval")
Note that since addField() has pointer receiver, a pointer is required (hence the use of & address operator), but then the *StatHandler can only be converted to *Handler.
If you would have an *StatHandler pointer in the first place:
sp := &StatHandler{}
(*Handler)(sp).addField("somekey", "someval")
Since new types do not "inherit" methods of the underlying type, you should consider embedding to achieve what you want, because methods and fields of an embedded type are promoted to the embedder type:
type StatHandler struct {
Handler // Embed Handler, so addField() method will be promoted
}
s2 := StatHandler2{}
s2.addField("somekey", "someval")
Try the examples on the Go Playground.
The exact definition of what methods a type has is covered in the spec: Method sets.

How to pass interfaces indirectly in golang

I have a package with a method:
func Route(router *mux.Router){
subrouter := router.PathPrefix(_API).Subrouter()
subrouter.Path(_FOO).HandlerFunc(foo)
subrouter.Path(_BAR).HandlerFunc(bar)
}
and I would like to remove the external dependency of mux by having a matching interface in my package that simple encompasses all the functionality used above, like so:
type Router interface{
Path(string) Path
PathPrefix(string) Path
}
type Path interface{
HandlerFunc(http.HandlerFunc)
Subrouter() Router
}
func Route(router Router){
subrouter := router.PathPrefix(_API).Subrouter()
subrouter.Path(_FOO).HandlerFunc(foo)
subrouter.Path(_BAR).HandlerFunc(bar)
}
but when I build this I get error:
*mux.Router does not implement api.Router (wrong type for Path method)
have Path(string) *mux.Route
want Path(string) api.Path
but I thought interfaces were implicitly used in golang so I thought that *mux.Route did implement my Path interface.
I thought interfaces were implicitly used in golang
Values are wrapped in interfaces implicitly, but only in specific cases like when passing an implementation to a function with an interface argument:
func X(api.Path) {}
X(&mux.Route{}) // works, implicitly converted to api.Path
or when returning an implementation from a function with an interface return type:
func Y() api.Path {
return &mux.Route{} // works, implicitly converted to api.Path
}
In your question's case the compiler wants a value that has a method with the signature:
Path(string) api.Path
But you're giving it a value with a method with signature:
Path(string) *mux.Route
As you might now, Go types are invariant. Formally:
type A interface { Path(string) *mux.Route }
is not a subtype of
type B interface { Path(string) api.Path }
Therefore this won't work.

Resources