-
Notifications
You must be signed in to change notification settings - Fork 17.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
proposal: reflect: Add Type.Fields() iter.Seq[StructField] and Type.Methods() iter.Seq[Method] #66631
Comments
could this be a duplicate of #66626? |
No, that issue relates to the go/types package, not reflect. |
ah, overlooked this crucial detail, thank you! |
This was in fact inspired by #66626, but yes, it is for reflect, not types. |
Alternatively, the method value could be the iterator as opposed to returning an iterator, like so: package reflect
type Type interface {
...
// Fields is a go1.23 iterator over all the fields of a struct type (etc).
//
// Example: for ftype := range t.Fields { ... }
Fields(yield func(ftype *Func) bool)
}
func (t *rtype) Fields(yield func(ftype *Func) bool) {
for i := range t.NumFields() {
if !yield(t.Field(i)) {
break
}
}
} // client code
var t reflect.Type = ...
for ftype := range t.Fields { ... } |
Maybe. That keeps from introducing an import dependency we might not want, but it goes against the general conventions used elsewhere. |
I used this in a project and the signature should be |
I'd like an iterator version of |
Isn't that just a filter on field.IsExported() or is there more to it than that? I did only want the exported fields for my thing, but I also needed a type and tag test for the field, so having VisibleFields() per se wouldn't have saved much coding. |
|
Got it. There's already a slice version of VisibleFields(), so I'm not sure how much the iterator version saves since you'll presumably cache the slice somehow if performance is a concern. I'm willing to be wrong though. There could be AllVisibleFields and VisibleFields could just call and collect that. |
It's possible to take 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 {
strukt = strukt.Elem()
}
for i := range strukt.NumField() {
fieldName := strukt.Type().Field(i).Name
fieldVal := strukt.Field(i)
if isPointer {
fieldVal = fieldVal.Addr()
}
if !yield(fieldName, fieldVal.Interface()) {
return
}
}
}
} Usage would be like so: type vars struct {
Editor string
Lang string
Shell string
}
func ExampleReadFields() {
env := vars{
Editor: "vim",
Shell: "/bin/dash",
}
for name, val := range structs.Fields(env) {
name := strings.ToUpper(name)
val := fmt.Sprint(val) // val is a string because env was passed by value
os.Setenv(name, val)
}
fmt.Println(os.Environ())
//Output:
// [... EDITOR=vim SHELL=/bin/dash ...]
}
func ExampleWriteFields() {
var env vars
for name, val := range structs.Fields(&env) {
name := strings.ToUpper(name)
*val.(*string) = os.Getenv(name) // val is a *string because &env was passed
}
fmt.Printf("%#v\n", env)
//Output:
// {Editor:"nano", Lang:"en_US.utf8", Shell:"/bin/bash"}
}
func ExampleStringKeys() {
var env vars
{
envMap := maps.Collect(structs.Fields(&env))
*envMap["Editor"].(*string) = "code"
*envMap["Shell"].(*string) = "/bin/zsh"
}
fmt.Printf("%#v\n", env)
//Output:
// {Editor:"code", Lang:"", Shell:"/bin/zsh"}
} This is also possible for |
Package structs is about compiler magic fields. I'm not sure how it would help to move something out of reflect and into a different package if that package just imports reflect itself. |
It seemed like the natural place to put it (akin to
Since there's no reference to |
This s := reflect.ValueOf(s)
for i := range s.NumField() {
use(s.Type().Field(i), s.Field(i))
} |
This was intended to improve usability, and to make it more generalized and intuitive. Restricting it to pointer-to-structs would also work, although that seems more arbitrary to me.
These were mistakes, not design choices. Sorry about that.
Can you elaborate on this? From a cursory glance, it seems like type information can be fully recovered from the field pointer. for name, val := range structs.Fields(&env) {
val := reflect.TypeOf(val)
// ...
}
On the other hand, that would also seem to be an argument against the currently proposed |
This returns the type of the field value, but not the reflect.StructField describing the field itself.
Not at all. This proposal is for a modest and uncontroversial convenience method to enumerate each element of the Type.Field(i) sequence using go1.23 iterator conventions, nothing more. |
In that case, should I file |
I understand the appear of the name // Fields returns an iterator over the values of the fields of the struct v.
// It panics if v's kind is not [Struct].
func (v Value) Fields() iter.Seq[Value] {
return func(func (Value) bool) {
typ := v.Type()
for i := range typ.NumFields() {
if !yield(v.Field(i)) {
return
}
}
}
} If we really want the field name also, it should perhaps be I don't see any problem having both |
Sorry for making things confusing -
To clarify, does that mean |
Yes. I don't think they really have anything to do with each other. |
As a data point, I'm updating a bunch of Go code to use range-over-int, and I'm discovering many cases of: for i := range t.NumField() {
f := t.Field(i)
...
} In fact, in the particular codebase I'm working on, that's the most common reason (~95%) to ever call |
Unfortunately all your pleasing uses of |
Such is the life of someone who cares about modern Go code 😫. I think I held the record for most Go code commits in a single day before I left Google. |
I don't find |
This proposal hasn't been evaluated yet. #66626 will be in Go 1.24, however. |
Proposal Details
reflect.Type.Fields() iter.Seq[StructField]
andreflect.Type.Methods() iter.Seq[Method]
would be more convenient than having to calli := range myType.NumFields()
andmyType.Field(i)
etc.You could also add iterators to
reflect.Value
as well but those are less obviously useful. Probably worth doing just for completeness though.Edit: The proposal is now to return an
iter.Seq2[int, StructField]
so you can use.Field(i)
.The text was updated successfully, but these errors were encountered: