Skip to content

proposal: structs: add Fields() iterator #69398

@myaaaaaaaaa

Description

@myaaaaaaaaa

Proposal Details

Currently, the only way to dynamically access the fields in a struct is through reflection. Having a simple way to range over structs as if they were map[string]anys would enable various struct idioms to be used more often.

See below for some examples of such idioms:

type vars struct {
	Editor string
	Shell  string
	Pid    int
}

func ExampleGetFields() {
	env := vars{
		Editor: "vim",
		Shell:  "/bin/dash",
		Pid:    100,
	}

	for name, val := range structs.Fields(env) {
		name := strings.ToUpper(name)
		val := fmt.Sprint(val) // val is a string/int because env was passed by value
		os.Setenv(name, val)
	}

	fmt.Println(os.Environ())

	//Output:
	// [...    EDITOR=vim    SHELL=/bin/dash    PID=100    ...]
}
func ExampleSetFields() {
	var env vars

	for name, val := range structs.Fields(&env) {
		name := strings.ToUpper(name)
		switch val := val.(type) { // val is a *string/*int because &env was passed
		case *string:
			*val = os.Getenv(name)
		case *int:
			*val, _ = strconv.Atoi(os.Getenv(name))
		}
	}

	fmt.Printf("%#v\n", env)

	//Output:
	// {Editor:"nano", Shell:"/bin/bash", Pid:2499}
}
func ExampleStringKeys() {
	var env vars

	{
		envMap := maps.Collect(structs.Fields(&env))
		*envMap["Editor"].(*string) = "code"
		*envMap["Shell"].(*string) = "/bin/zsh"

		pidKey := "P" + strings.ToLower("ID")
		*envMap[pidKey].(*int) = 42
	}

	fmt.Printf("%#v\n", env)

	//Output:
	// {Editor:"code", Shell:"/bin/zsh", Pid:42}
}

Sample implementation (may contain errors):

package structs

func Fields(strukt any) iter.Seq2[string, any] {
	return func(yield func(string, any) bool) {
		strukt := reflect.ValueOf(strukt)
		isPointer := strukt.Kind() == reflect.Pointer
		if isPointer {
			if strukt.IsNil() {
				return
			}
			strukt = strukt.Elem()
		}

		for i := range strukt.NumField() {
			field := strukt.Type().Field(i)
			if !field.IsExported() {
				continue
			}

			fieldName := field.Name
			fieldVal := strukt.Field(i)
			if isPointer {
				fieldVal = fieldVal.Addr()
			}

			if !yield(fieldName, fieldVal.Interface()) {
				return
			}
		}
	}
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions