0

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

Mark Anderson
  • 2,399
  • 3
  • 15
  • 21
  • If it's only using the `name` field, why does it take the entire struct as a parameter instead of just taking the name? i.e. just have one function `workOnName(name string)`. – Adrian Sep 09 '19 at 13:49
  • @Adrian In this case, it only uses the name, and I used this for illustrative purposes. The actual problem contains around 10 fields that are used. In addition, the check whether the pointer is nil has to happen somewhere, so if it doesn't happen in the function (where it belongs), that would mean checking whether the pointer is nil in dozens of places. – Mark Anderson Sep 09 '19 at 13:59
  • Your question said "This function, which only uses name, is the same across all structs" - your comment, "around 10 fields that are used" implies a very different problem. I'd probably be looking to change the model so that the fields aren't duplicated but instead use an embedded struct with the shared fields, then write the `workOnNameAndStuff` function to take the embedded type. But, without seeing actually relevant example code it's hard to offer much advice. – Adrian Sep 09 '19 at 14:06
  • @MarkAnderson If the function needs only the name to do its job, why don't you pass the name field to the function instead of the whole struct? – mkopriva Sep 09 '19 at 14:17
  • @MarkAnderson *"An interface has a pair of references, a reference to a type and reference to a value. If the interface itself is nil, then these are both nil. If the interface points to a nil value, then the type is non-nil (it points to the type of the nil value), but the value is nil. If the interface is of the first kind, type assertion fails because type is nil. If the interface is of the second kind, type assertion works because there is a non-nil type."* from: https://stackoverflow.com/questions/57781058/how-to-cast-nil-interface-to-nil-other-interface/57781089#comment101998636_57781089 – mkopriva Sep 09 '19 at 14:20
  • @mkopriva Thanks for that second reply, but is there a way to get around this and only get the first reference? – Mark Anderson Sep 09 '19 at 14:21
  • @MarkAnderson https://play.golang.org/p/apgPbhX0uYm – mkopriva Sep 09 '19 at 14:22
  • 1
    @MarkAnderson you could use reflect to figure out if a non-nil interface's value is nil https://play.golang.org/p/TJiIVf8JMED – mkopriva Sep 09 '19 at 14:25
  • @mkopriva Thanks! This worked great. If you want to add it as a reply, I can mark it as the right answer. – Mark Anderson Sep 09 '19 at 14:54

2 Answers2

2

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.

cd1
  • 15,908
  • 12
  • 46
  • 47
  • Thanks for the answer, but this does not work. See this playground here: https://play.golang.org/p/_d1qiZwnMe_f The problem is (and that I've been wrestling with) is that Go will not see the pointer as nil, even though it is, and will run the "else" branch instead. – Mark Anderson Sep 09 '19 at 13:58
  • you're right. I just edited the answer with the new changes. in Go, an interface variable is considered nil if, and only if, the variable points to nil - and not to a variable of some type, which has the value of nil. in other words, "var fakePointer WithName = nil" is not the same as "var fakePointer *foo = nil". one is a nil interface reference, the other one is a reference of foo pointing to nil. you need to use reflection to different both cases generically. please take a look at this (https://dev.to/pauljlucas/go-tcha-when-nil--nil-hic) for more details. – cd1 Sep 10 '19 at 16:45
0

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.

Alex Yu
  • 3,412
  • 1
  • 25
  • 38