英文:
Procedurally bind struct fields to command line flag values using reflect
问题
我有几个配置结构,我想要自动解析成可接受的命令行标志(以便用户可以通过命令行界面进行覆盖)。考虑到这些结构可能随时间而演变,并且其中一个结构是interface{}
,反射似乎是正确的方法。我只需要解析字符串、整数和浮点数。我已经实现了以下功能:
func ReconGenerateFlags(in *ReconConfig, cmd *cobra.Command) {
for _, f := range reflect.VisibleFields(reflect.TypeOf(*in)) {
group_name := f.Name
v := reflect.ValueOf(in).Elem().FieldByName(f.Name).Elem() // Return the concrete substructures pointed to by "in"
sub_fields := reflect.VisibleFields(v.Type())
for _, sub_f := range sub_fields {
tag_name := sub_f.Name
sub_v := v.FieldByName(tag_name)
full_flag_name := strings.ToLower(group_name) + "." + strings.ToLower(tag_name)
switch s := sub_v.Type().String(); s {
case "string":
ptr := (*string)(unsafe.Pointer(sub_v.UnsafeAddr()))
cmd.Flags().StringVar(ptr, flag_name, "", "")
case "int":
ptr := (*int)(unsafe.Pointer(sub_v.UnsafeAddr()))
cmd.Flags().IntVar(ptr, flag_name, 0, "")
//case "float64":
// ptr := (*float64)(unsafe.Pointer(sub_v.UnsafeAddr()))
// cmd.Flags().Float64Var(ptr, flag_name, 0.0, "")
default:
fmt.Printf("unrecognized type in config setup: %s\n", s)
}
}
}
}
但是当我取消注释float64的部分时,会出现恐慌错误:
panic: reflect.Value.UnsafeAddr of unaddressable value
goroutine 1 [running]:
reflect.Value.UnsafeAddr(...)
/usr/local/go/src/reflect/value.go:2526
所以,我的具体问题是:
- "有没有办法使float64正常工作?"
而我稍微广泛的问题是:
- "有没有更好的反射方法,不需要使用不安全指针转换?"
我更希望完全遵守类型系统,但如何在反射中实现这一点并不明显。另一种选择似乎是使用代码生成,我希望避免这样做,但如果需要的话也可以应对。
提前感谢您的任何建议!
英文:
I have a several configuration structures that I want to automatically parse into accepted command line flags (to allow a user to override them via CLI). Given that the structures are likely to evolve over time, and one of the structures is an interface{}
, reflection seems to be the correct approach. I only need to parse strings, ints, and float64s. I've gotten the following working:
func ReconGenerateFlags(in *ReconConfig, cmd *cobra.Command) {
for _, f := range reflect.VisibleFields(reflect.TypeOf(*in)) {
group_name := f.Name
v := reflect.ValueOf(in).Elem().FieldByName(f.Name).Elem() // Return the concrete substructures pointed to by "in"
sub_fields := reflect.VisibleFields(v.Type())
for _, sub_f := range sub_fields {
tag_name := sub_f.Name
sub_v := v.FieldByName(tag_name)
full_flag_name := strings.ToLower(group_name) + "." + strings.ToLower(tag_name)
switch s := sub_v.Type().String(); s {
case "string":
ptr := (*string)(unsafe.Pointer(sub_v.UnsafeAddr()))
cmd.Flags().StringVar(ptr, flag_name, "", "")
case "int":
ptr := (*int)(unsafe.Pointer(sub_v.UnsafeAddr()))
cmd.Flags().IntVar(ptr, flag_name, 0, "")
//case "float64":
// ptr := (*float64)(unsafe.Pointer(sub_v.UnsafeAddr()))
// cmd.Flags().Float64Var(ptr, flag_name, 0.0, "")
default:
fmt.Printf("unrecognized type in config setup: %s\n", s)
}
}
}
}
But when I uncomment the float64 block I get a panic:
panic: reflect.Value.UnsafeAddr of unaddressable value
goroutine 1 [running]:
reflect.Value.UnsafeAddr(...)
/usr/local/go/src/reflect/value.go:2526
So, my concrete question is
- "Is there a way to make this work for float64s?",
and my slightly broader question is
- "Is there a better approach with reflection that wouldn't require the unsafe pointer casting?"
I'd much prefer to fully respect the type system, but it's not obvious how to do this with reflection. The other alternative seems like it would be with code generation, which I'd like to avoid, but can wrangle if needed.
Thanks in advance for any input!
答案1
得分: 1
如果我正确理解了你的要求,那么不需要使用unsafe
。要获取字段的指针,你可以使用Value.Addr
方法和type assertions来获取具体类型。
func GenerateFlags(in interface{}, fs *flag.FlagSet, names []string) {
rv := reflect.ValueOf(in)
if rv.Kind() != reflect.Ptr || rv.Elem().Kind() != reflect.Struct {
return // 如果不是指向结构体的指针,则退出
}
rv = rv.Elem()
rt := rv.Type()
for i := 0; i < rt.NumField(); i++ {
sf := rt.Field(i)
fv := rv.Field(i)
name := strings.Join(append(names, strings.ToLower(sf.Name)), ".")
switch fv.Type() {
case reflect.TypeOf(string("")):
p := fv.Addr().Interface().(*string)
fs.StringVar(p, name, "", "")
case reflect.TypeOf(int(0)):
p := fv.Addr().Interface().(*int)
fs.IntVar(p, name, 0, "")
case reflect.TypeOf(float64(0)):
p := fv.Addr().Interface().(*float64)
fs.Float64Var(p, name, 0, "")
default:
names := append([]string{}, names...)
GenerateFlags(fv.Interface(), fs, append(names, strings.ToLower(sf.Name)))
}
}
}
https://go.dev/play/p/1F2Kyo0cBuj
英文:
If I understood your requirements correctly then there's NO need to use unsafe
. To get a pointer to a field you can just use the Value.Addr
method and type assertions to get the concrete type.
func GenerateFlags(in interface{}, fs *flag.FlagSet, names []string) {
rv := reflect.ValueOf(in)
if rv.Kind() != reflect.Ptr || rv.Elem().Kind() != reflect.Struct {
return // exit if not pointer-to-a-struct
}
rv = rv.Elem()
rt := rv.Type()
for i := 0; i < rt.NumField(); i++ {
sf := rt.Field(i)
fv := rv.Field(i)
name := strings.Join(append(names, strings.ToLower(sf.Name)), ".")
switch fv.Type() {
case reflect.TypeOf(string("")):
p := fv.Addr().Interface().(*string)
fs.StringVar(p, name, "", "")
case reflect.TypeOf(int(0)):
p := fv.Addr().Interface().(*int)
fs.IntVar(p, name, 0, "")
case reflect.TypeOf(float64(0)):
p := fv.Addr().Interface().(*float64)
fs.Float64Var(p, name, 0, "")
default:
names := append([]string{}, names...)
GenerateFlags(fv.Interface(), fs, append(names, strings.ToLower(sf.Name)))
}
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论