首页 文章

通过反射归零a * struct(不知道底层类型)并重新分配指向接口的指针

提问于
浏览
1

Background

我正在构建一个包装器包,它结合了已定义的命令,并允许它们在cli或交互式shell上下文中执行 . 命令在结构中定义,如下所示(显示相关字段):

type Handler func(c *Command) error

type Command struct {
    RequestHandler     Handler
    ResponseHandler    Handler
    Request            interface{} //pointer to struct
    Response           interface{} //pointer to struct
}

底层的Request / Response对象总是指向结构的指针,所以我非常大量地使用反射来递归迭代底层的结构字段,根据调用上下文(shell / cli)将它们的字段显示为标志或提示 . 然后,我根据用户输入使用反射填充请求对象 .

到现在为止还挺好...

Issue

当我在交互式shell上下文中时,可以调用一个命令,并填充请求结构及其值 - 一切都很好 . 但是,如果再次调用该命令,则仍会填充请求(并且响应可能也是如此),因此我希望在命令上实现'reset'方法,当从shell调用命令时可以在内部调用该方法 .

现在我说我可以保证请求和响应都是结构的指针 . 但是当我所拥有的只是一个接口并且没有基础类型的高级知识((和内部字段可能是结构值)时,如何将它们设置回各自初始化的nil值 .

Possible Approaches

  • 在命令注册时(在填充之前)添加底层请求/响应对象的第二个副本,然后在重置时复制它

  • 只是创建一个通用的重置结构函数,它作用于底层结构指针的任何接口

  • 某种方法我不知道这对我来说是什么

我已经尝试了两种方法,我也在努力实施

approach 1:

type Command struct {
    RequestHandler     Handler
    ResponseHandler    Handler
    zeroRequest        interface{}
    zeroResponse       interface{}
    Request            interface{} //pointer to struct
    Response           interface{} //pointer to struct
}

func (c *Command) register() error {
    // validate is pointer
    reqPtrVal := reflect.ValueOf(c.Request)
    if reqPtrVal.Kind() != reflect.Ptr {
        return ErrStructPtrExpected
    }

    // reflect the underlying value and validate is struct
    reqVal := reflect.Indirect(reqPtrVal)
    if reqVal.Kind() != reflect.Struct {
        return ErrStructPtrExpected
    }

    c.zeroRequest = reqVal.Interface()

    // other logic... 
}

func (c *Command) handleShell(sc *ishell.Context) {
    // reset request / response objects

    // reflect the underlying zero value and validate is struct
    reqVal := reflect.ValueOf(c.zeroRequest)
    if reqVal.Kind() != reflect.Struct {
        c.Commander.HandleError(ErrStructPtrExpected)
    }

    newRequest := reflect.New(reqVal.Type())

    // unfortunately below doesn't work as newRequest.CanAddr() == false
    c.zeroRequest = newRequest.Addr().Interface()

    // other logic
}

approach 2: 这感觉有点干净(没有零值拷贝/更少的代码),但我甚至无法编译这种方法:

func (c *Command) resetStruct(structPtr interface{}) error {
    // validate is pointer
    ptrVal := reflect.ValueOf(structPtr)
    if ptrVal.Kind() != reflect.Ptr {
        return ErrStructPtrExpected
    }

    // reflect the underlying value and validate is struct
    val := reflect.Indirect(ptrVal)
    if val.Kind() != reflect.Struct {
        return ErrStructPtrExpected
    }

    // create a new val from the underlying type and reassign the pointer
    newVal := reflect.New(val.Type())
    ptrVal.SetPointer(newVal.Addr())
    return nil
}

Summary

最终目标是通过反射将这个未知类型的结构归零,并将指针重新分配回请求接口 . 接近的优先顺序是3,2,1 - 但任何有效的方法都只是花花公子 .

似乎我可以解决这个问题,如果我可以从reflect.Type创建一个具体的类型,但也许这里有另一种方法

1 回答

  • 1

    您可以使用 reflect.Zeroreflect.New ,请查看下面的代码段以获得清晰度

    这是你如何使用它

    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    type A struct {
        B int
    }
    
    func reset(p interface{}) interface{} {
        var isPtr bool
        v := reflect.ValueOf(p)
        for v.Kind() == reflect.Ptr {
            isPtr = true
            v = v.Elem()
        }
        if isPtr {
            return reflect.New(v.Type()).Interface()
    
        }
    
        return reflect.Zero(v.Type()).Interface()
    
    }
    
    func main() {
        p := A{1}
        fmt.Printf("Original : %+v,Pointer : %+v,Value : %+v", p, reset(&p), reset(p))
    }
    

    玩:https://play.golang.com/p/8CJHHBL6c2p

相关问题