1

First of all, apologies if this question is confused since I'm just trying out Go and have no idea what I'm doing. I have a struct composed of a variety of attributes of different types, example:

type foo struct {
    bar string
    baz int
    bez []string
(...)

Initially I wanted to iterate over all these attributes and print the value if it existed, but I realized you cannot range over a struct the same way you could, say, a list or map. So I've tried out a few tricks with no luck (like trying to iterate over a separate list of attributes), and I think it's better I just ask for help because I'm probably in over my head here.

The idea is that if I create a new instance of this struct, I'd like to be able to then only print values that are set:

obj := foo{"bar_string", 1}

Given that the string slice bez is not set in obj, I'd like to be able to do something like (pseudo):

for i in obj:
    print i

Giving:

"bar_string"
1

Ideally, not printing [] which I guess is the zero value for bez.

Am I approaching this whole thing wrong? The reason I'm not using a map is because I'd like the attributes to be different types, and I'd like future differing objects I'm working in to be organized into structs for clarity.

9
  • Probably you should use reflect, but iterating over struct attributes it's wierd idea. Commented Apr 5, 2021 at 9:54
  • Do you have a suggestion for a less weird idea if you want to have an object of different value types and only print the values that are set? I was under the impression that maps for example are required to have a single type. Commented Apr 5, 2021 at 9:57
  • 2
    what about map[string]interface{} ? - play.golang.org/p/swysnXZxvGu Commented Apr 5, 2021 at 10:08
  • @pzkpfw This seems a lot like you're trying to make go behave the way you are used to from other (non-compiled) languages. Don't do that. Learn how to use the language the way it's supposed to be used. Go being a statically typed language, you can't have null values or a map of different data types without using type reflection and conversions. Commented Apr 5, 2021 at 11:07
  • 1
    "The idea is that if I create a new instance of this struct, I'd like to be able to then only print values that are set" That is not how Go works. There simply are no "unset" struct fields. If you do not "set" them they just will be set to their zero value by the compiler/runtime and you cannot (really!) distinguish between you setting the value or the compiler. Commented Apr 5, 2021 at 12:05

1 Answer 1

2

Go doesn't have builtin struct iteration. The for ... range statement is applicable only to:

all entries of an array, slice, string or map, or values received on a channel

or defined types with one of those underlying types (e.g. type Foo []int)

If you must iterate over a struct not known at compile time, you can use the reflect package. To know whether a field is set or not, you can compare it to its zero value. Otherwise there is no notion of set vs. unset in a Go struct. As explained by Volker in this comment:

[...] There simply are no "unset" struct fields. If you do not "set" them they just will be set to their zero value by the compiler/runtime and you cannot (really!) distinguish between you setting the value or the compiler.

type Foo struct {
  Bar string
  Baz int
  Quux []int
}

// x := Foo{"bar", 1, nil}
func printAny(x interface{}) {
    v := reflect.ValueOf(x)

    for i := 0; i < v.NumField(); i++ {
        field := v.Field(i)
        if !reflect.DeepEqual(field.Interface(), reflect.Zero(field.Type()).Interface()) {
            fmt.Println(field) 
            // bar
            // 1
        }
    }
}

...but it's slower and there are some gotchas, for example:

  • field.Interface() panics if the field is unexported
  • in the if clause you can't just use the comparison operator == because operands might be not comparable:
  • you have to make sure that the zero value for field types is what you expect

If your goal is to just print the struct, you can simply implement the Stringer interface, where you can do type-safe checks the way you want without reflect:

type Foo struct {
  Bar string
  Baz int
  Quux []int
}

func (f Foo) String() string {
    s := []string{f.Bar, strconv.Itoa(f.Baz)}
    if f.Quux != nil {
        s = append(s, fmt.Sprintf("%v", f.Quux))
    }
    return strings.Join(s, "\n")
}

func main() {
   fmt.Println(Foo{"bar", 1, nil}) 
   // bar
   // 1
}

A Go playground

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.