Golang : interface to swap two numbers

后端 未结 1 2034
无人及你
无人及你 2021-02-06 05:02

I want to swap two numbers using interface but the interface concept is so confusing to me.

http://play.golang.org/p/qhwyxMRj-c

This is the code and playground.

1条回答
  •  遇见更好的自我
    2021-02-06 05:53

    First of all, the interface{} type is simply a type which accepts all values as it is an interface with an empty method set and every type can satisfy that. int for example does not have any methods, neither does interface{}.

    For a method which swaps the values of two variables you first need to make sure these variables are actually modifiable. Values passed to a function are always copied (except reference types like slices and maps but that is not our concern at the moment). You can achieve modifiable parameter by using a pointer to the variable.

    So with that knowledge you can go on and define SwapNum like this:

    func SwapNum(a interface{}, b interface{})
    

    Now SwapNum is a function that accepts two parameters of any type. You can't write

    func SwapNum(a *interface{}, b *interface{})
    

    as this would only accept parameters of type *interface{} and not just any type. (Try it for yourself here).

    So we have a signature, the only thing left is swapping the values.

    func SwapNum(a interface{}, b interface{}) {
        *a, *b = *b, *a
    }
    

    No, this will not work that way. By using interface{} we must do runtime type assertions to check whether we're doing the right thing or not. So the code must be expanded using the reflect package. This article might get you started if you don't know about reflection.

    Basically we will need this function:

    func SwapNum(a interface{}, b interface{}) {
        ra := reflect.ValueOf(a).Elem()
        rb := reflect.ValueOf(b).Elem()
        tmp := ra.Interface()
    
        ra.Set(rb)
        rb.Set(reflect.ValueOf(tmp))
    }
    

    This code makes a reflection of a and b using reflect.ValueOf() so that we can inspect it. In the same line we're assuming that we've got pointer values and dereference them by calling .Elem() on them.

    This basically translates to ra := *a and rb := *b. After that, we're making a copy of *a by requesting the value using .Interface() and assigning it (effectively making a copy).

    Finally, we set the value of a to b with [ra.Set(rb)]5, which translates to *a = *b and then assigning b to a, which we stored in the temp. variable before. For this, we need to convert tmp back to a reflection of itself so that rb.Set() can be used (it takes a reflect.Value as parameter).

    Can we do better?

    Yes! We can make the code more type safe, or better, make the definition of Swap type safe by using reflect.MakeFunc. In the doc (follow the link) is an example which is very like what you're trying. Essentially you can fill a function prototype with content by using reflection. As you supplied the prototype (the signature) of the function the compiler can check the types, which it can't when the value is reduced to interface{}.

    Example usage:

    var intSwap func(*int, *int)
    a,b := 1, 0
    makeSwap(&intSwap)
    intSwap(&a, &b)
    // a is now 0, b is now 1
    

    The code behind this:

    swap := func(in []reflect.Value) []reflect.Value {
        ra := in[0].Elem()
        rb := in[1].Elem()
        tmp := ra.Interface()
    
        ra.Set(rb)
        rb.Set(reflect.ValueOf(tmp))
    
        return nil
    }
    
    makeSwap := func(fptr interface{}) {
        fn := reflect.ValueOf(fptr).Elem()
        v := reflect.MakeFunc(fn.Type(), swap)
        fn.Set(v)
    }
    

    The code of swap is basically the same as that of SwapNum. makeSwap is the same as the one used in the docs where it is explained pretty well.

    Disclaimer: The code above makes a lot of assumptions about what is given and what the values look like. Normally you need to check, for example, that the given values to SwapNum actually are pointer values and so forth. I left that out for reasons of clarity.

    0 讨论(0)
提交回复
热议问题