how to make golang function param support different but similar struct

2023-07-13by@tim&@ChatGPT.Model: gpt-4
tim

how to make golang function param support different but similar struct

Avatar
Avatar

In Golang, if you want to have a function that accepts different but similar structs, you'll need to use interface. Interface is a type in go that defines and groups method signatures. Any type that implements those methods is said to satisfy that interface.

Here's an example of how to do it:

  1. Define an interface that includes the common methods that your structs will implement:
type Animal interface {
    Speak() string
}
  1. Define your different but similar structs:
type Dog struct {
    Name string
}

type Cat struct {
    Name string
}
  1. Implement the interface methods for each of your structs:
func (d Dog) Speak() string {
    return d.Name + " says woof"
}

func (c Cat) Speak() string {
    return c.Name + " says meow"
}
  1. Now you can define a function that accepts a parameter of the interface type. This function will accept any struct that implements the Animal interface:
func Talk(a Animal) {
    fmt.Println(a.Speak())
}
  1. And use it like this:
func main() {
    Talk(Dog{Name: "Rex"}) // Outputs: Rex says woof
    Talk(Cat{Name: "Fluffy"}) // Outputs: Fluffy says meow
}

This way, you're making Golang support different but similar structs as function parameters.

tim

I don't want to add extra function/property to the original struct

Avatar
Avatar

If you don't want to alter your original structs, another option in Golang is to use the interface{} type, which can accept any type, including any struct.

Here's an example:

type Dog struct {
    Name string
}

type Cat struct {
    Name string
}

func PrintName(v interface{}) {
    switch value := v.(type) {
    case Dog:
        fmt.Println("Dog's name is", value.Name)
    case Cat:
        fmt.Println("Cat's name is", value.Name)
    default:
        fmt.Println("Not a Dog or Cat")
    }
}

func main() {
    PrintName(Dog{Name: "Rex"}) 
    PrintName(Cat{Name: "Fluffy"}) 
}

In this example, the function PrintName accepts a parameter of type interface{}, then uses a type switch to determine the actual type of the value and print the Name field accordingly.

This method will work for any struct type you pass, provided you handle the type in the type switch. However, it does not enforce type safety and requires you to manually handle each possible type.

tim

I also want to use reflect on the struct, interface seems not to support reflect

Avatar
Avatar

You can use reflection in Go to operate on structs passed in as interface{} type. You can get the value of a struct's field by its name using the reflect package.

Here is an example:

package main

import (
	"fmt"
	"reflect"
)

type Dog struct {
	Name string
}

type Cat struct {
	Name string
}

func PrintName(v interface{}) {
	val := reflect.ValueOf(v)
	nameField := val.FieldByName("Name")
	if nameField.IsValid() {
		fmt.Println("Name is", nameField.String())
	} else {
		fmt.Println("No name field")
	}
}

func main() {
	PrintName(Dog{Name: "Rex"}) 
	PrintName(Cat{Name: "Fluffy"}) 
}

In this example, the PrintName function uses reflection to get the struct's Name field by its name. If the struct does not have a Name field, FieldByName returns a zero Value, and IsValid returns false, so it will print "No name field".

tim

when invoking PrintName, if I pass a pointer of struct, will it work

Avatar
Avatar

In the code provided, if you pass a pointer to a struct to the PrintName function, it wouldn't work. This is because reflect.ValueOf(v) does not dereference pointers.

To handle this case, you need to add an extra check to see if the passed value is a pointer, and if so, dereference it.

Here's how you can modify PrintName function:

func PrintName(v interface{}) {
    val := reflect.ValueOf(v)
    if val.Kind() == reflect.Ptr {
        val = val.Elem()
    }
    
    nameField := val.FieldByName("Name")
    if nameField.IsValid() {
        fmt.Println("Name is", nameField.String())
    } else {
        fmt.Println("No name field")
    }
}

Now the function will correctly handle both structs and pointers to structs:

func main() {
    dog := Dog{Name: "Rex"}
    PrintName(&dog) // Outputs: Name is Rex

    cat := Cat{Name: "Fluffy"}
    PrintName(&cat) // Outputs: Name is Fluffy
}