dougu1896 2018-03-07 12:23
浏览 35
已采纳

通过反射将* struct零(不知道底层类型),然后将指针重新分配给接口

Background

I'm building a wrapper package which combines defined commands and allows them to be executed in either a cli or interactive shell context. Commands are defined in a struct something like this (relevant fields shown):

type Handler func(c *Command) error

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

The underlying Request/Response objects are always pointers to structs so I make use of reflection quite heavily to recursively iterate through the underlying structs fields to present their fields as either flags or prompts based on the calling context (shell/cli). I then populate the request object using reflection based on the users input.

So far, so good...

Issue

When I am in an interactive shell context a command may be called and I populate the request struct and its values - all good. However, if the command is called again the request is still populated (and the response may be too) so I am looking to implement a 'reset' method on the command which can be called internally when a command is called from the shell.

Now as I say i can guarantee the request and response are both pointers to structs. but how can I set them back to their respective initialised nil values when all I have is an interface and no advanced knowledge of the underlying type ((and internal fields may be struct values).

Possible Approaches

  1. add a second copy of the underlying request/response object at command registration (prior to population) and then copy it across on reset

  2. just create a generic reset struct function that acts on any interface to an underlying struct pointer

  3. some method Im unaware of which does this for me

I've tried both and I'm struggling to implement either

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: This feels a little cleaner (no zero value copies/ less code), but I can't even get this approach to compile:

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

The ultimate goal is to zero this struct of unknown type via reflection and reassign the pointer back to the request interface. preferred order of approach is 3,2,1 - but anything that works would be just dandy.

It seems that I could solve this is if I could create a concrete type from a reflect.Type, but perhaps there is another approach here

  • 写回答

1条回答 默认 最新

  • duangua5308 2018-03-07 12:44
    关注

    You may use reflect.Zero or reflect.New please check the code snippet below for clarity

    Here is how you may use it

    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))
    }
    

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

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 关于#hadoop#的问题
  • ¥15 (标签-Python|关键词-socket)
  • ¥15 keil里为什么main.c定义的函数在it.c调用不了
  • ¥50 切换TabTip键盘的输入法
  • ¥15 可否在不同线程中调用封装数据库操作的类
  • ¥15 微带串馈天线阵列每个阵元宽度计算
  • ¥15 keil的map文件中Image component sizes各项意思
  • ¥20 求个正点原子stm32f407开发版的贪吃蛇游戏
  • ¥15 划分vlan后,链路不通了?
  • ¥20 求各位懂行的人,注册表能不能看到usb使用得具体信息,干了什么,传输了什么数据