Mark Anderson
Mark Anderson

Reputation: 2459

How can I implement the same function for pointers to different structs?

Suppose I have a lot of different structs, but they all share a common field, such as "name". For example:

type foo struct {
    name string
    someOtherString string
    // Other fields
}

type bar struct {
    name string
    someNumber int
    // Other fields
}

Further on in the program, I repeatedly encounter the situation where I get pointers to these structs (so *foo, *bar, etc.) and need to perform operations depending on whether the pointer is nil or not, basically like so:

func workOnName(f *foo) interface{} {
    if (f == nil) {
        // Do lots of stuff
    } else {
        // Do lots of other stuff
    }
    // Do even more stuff
    return something
}

This function, which only uses name, is the same across all structs. If these were not pointers, I know I could write a common interface for each struct that returns the name and use that as the type. But with pointers, none of this has worked. Go either complains while compiling, or the nil check doesn't work and Go panics. I haven't found anything smarter than to copy/paste the exact same code for every struct that I have, so basically to implement all the functions:

func (f *foo) workOnName() interface{}
func (b *bar) workOnName() interface{}
func (h *ham) workOnName() interface{}
// And so on...

Is there a way to do this better, i.e. to only implement a simple function (or even better, no function at all) for all my structs and simply write the complicated stuff once, for all the structs?

Edit: Thank you to the answers so far, but simply using an interface of the type:

func (f foo) Name() string {
    return f.name
}

for some interface that provides Name() does not work, because the pointer is not recognized as nil. See this playground: https://play.golang.org/p/_d1qiZwnMe_f

Upvotes: 0

Views: 1041

Answers (2)

cd1
cd1

Reputation: 16534

You can declare an interface which declares a function returning a name:

type WithName interface {
    Name() string
}

In order to implement that interface, you types (foo, bar, etc) need to have that method - not just the field, the method.

func (f foo) Name() string {
    return f.name
}

Then, workOnName needs to receive a reference of that interface:

func workOnName(n WithName) interface{} {
    if (n == nil || reflect.ValueOf(n).isNil()) {
        // Do lots of stuff
    } else {
        // Do lots of other stuff
    }
    // Do even more stuff
    return something
}

Keep in mind that the parameter n WithName is always treated as a pointer, not an object value.

Upvotes: 2

Alex Yu
Alex Yu

Reputation: 3547

I think that's the case for reflect.

Something like:

package main

import (
    "fmt"
    "reflect"
)

func SetFieldX(obj, value interface{}) {
    v := reflect.ValueOf(obj).Elem()
    if !v.IsValid() {
        fmt.Println("obj is nil")
        return
    }
    f := v.FieldByName("X")
    if f.IsValid() && f.CanSet() {
        f.Set(reflect.ValueOf(value))
    }

}

func main() {
    st1 := &struct{ X int }{10}
    var stNil *struct{}

    SetFieldX(st1, 555)
    SetFieldX(stNil, "SSS")

    fmt.Printf("%v\n%v\n", st1, stNil)
}

https://play.golang.org/p/OddSWT4JkSG

Note that IsValid checks more than just obj==nil but if you really want to distinguish cases of nil pointers and non-struct objects - you are free to implement it.

Upvotes: 0

Related Questions