Jordan
Jordan

Reputation: 4512

Go interface to enforce same argument type between methods (but can be any type)

The title is confusing, I'm sure, but it's hard to describe what I mean.

I want to create a Go interface that has two methods; the first one returns a value, and the second one accepts a value. I want to ensure that method 1 returns the same type as method 2 accepts, without specifying what the type is (other than it being a struct). For example:

type MyInterface interface {
    Method1() MyType
    Method2(MyType) error
}

Where MyType can be any type (of struct), as long as it's the same in both Method 1 and Method 2.

Is there any way to do this in Go?

EDIT:

Based on @iLoveReflection's answer, I've tried the following:

package main

type MyInterface interface {
    GetType() interface{}

    UseType(input interface{})
}

type MyImplementation struct{}

type MyType struct {
}

func (i MyImplementation) GetType() MyType {
    return MyType{}
}
func (i MyImplementation) UseType(input MyType) {
    return
}

func test(input MyInterface) {
    return
}

func assertArgAndResult() {
    var v MyImplementation
    v.UseType(v.GetType())
}

func main() {
    test(MyImplementation{})
}

So basically, I'm specifying an interface (MyInterface), and I want to ensure that a given implementation of that interface (MyImplementation, which would be created by the user of my package) meets the requirement stated in the original post.

assertArgAndResult() is working as expected, and ensures that MyImplementation meets the requirements. However, I get a compile error in the main() function:

cannot use MyImplementation literal (type MyImplementation) as type MyInterface in argument to test:
    MyImplementation does not implement MyInterface (wrong type for GetType method)
        have GetType() MyType
        want GetType() interface {}

Upvotes: 0

Views: 875

Answers (1)

user12258482
user12258482

Reputation:

Add the following function to a package to ensure that the input and output types match at compile time:

func assertArgAndResult() {
    var v MyInterface
    v.Method2(v.Method1())
}

This function will not be included in the executable as long as the function is not called.

There is no compile time check that will ensure that MyType is a struct type as specified in the question.

The reflect package can be used to check type types completely.

// checkItf returns true of the interface value pointed to by
// pi has Method1 with some return type T and Method2 with
// argument type T.
func checkItf(pi interface{}) bool {
    t := reflect.TypeOf(pi)
    if t.Kind() != reflect.Ptr {
        return false // or handle as error
    }
    t = t.Elem()
    if t.Kind() != reflect.Interface {
        return false // or handle as error
    }
    m1, ok := t.MethodByName("Method1")

    // Method1 should have no outputs and one input.
    if !ok || m1.Type.NumIn() != 0 || m1.Type.NumOut() != 1 {
        return false
    }

    // Method2 should have one input and one output.
    m2, ok := t.MethodByName("Method2")
    if !ok || m2.Type.NumIn() != 1 || m2.Type.NumOut() != 1 {
        return false
    }

    e := reflect.TypeOf((*error)(nil)).Elem()
    s := m1.Type.Out(0)

    // The type must be a struct and
    // the input type of Method2 must be the same as the output of Method1 and
    // Method2 must return error.
    return s.Kind() == reflect.Struct &&
        m2.Type.In(0) == s &&
        m2.Type.Out(0) == e
}

Call it like this:

func init() {
   if !checkItf((*MyInterface)(nil)) {
      panic("mismatched argument and return time son MyInterface")
   }
}

Upvotes: 1

Related Questions