通过引用设置接口{}参数

huangapple go评论89阅读模式
英文:

Setting An Interface{} Parameter By Reference

问题

我很难理解如何设置作为指针传递的接口值。我试图实现类似以下代码的功能:

import "fmt"

var Stuff map[string]interface{}

func main() {
    var num int
    Stuff["key"] = 9001
    get("key", &num)
    fmt.Println("num:", num)
}

func get(k string, v interface{}) {
    *v = Stuff[k]
}

要使程序输出为num: 9001,你需要做什么修改?

编辑:是否可以使用reflect来实现一个通用解决方案?

英文:

I am having difficulty understanding how to set an interface value that has been passed as a pointer. I am trying to accomplish something along the lines of this:

import "fmt"

var Stuff map[string]interface{}

func main() {
    var num int
    Stuff["key"] = 9001
    get("key", &num)
    fmt.Println("num:", num)
}

func get(k string, v interface{}) {
    *v = Stuff[k]
}

What would I have to do to make my program output be

num: 9001

Edit: is there a possible catch-all solution using reflect?

答案1

得分: 14

你可以使用reflect来模拟AppEngine数据存储接口;通常我会说尽量减少使用反射,但是在这种情况下,你(以及AppEngine和其他ORM)没有其他更好的选择来呈现所需的接口。对于模拟Get的操作,你可以按照以下步骤进行:

  • 使用ValueOf()获取一个reflect.Value
  • 获取你想要创建的对象的类型
  • 使用reflect.Zero创建对象
  • 可选地使用reflect.Field()等方法填充一些数据
  • 使用reflect.Indirect()Value.Set()通过指针设置原始值

下面是一个简单的示例,通过指针将结构体清零,你可以在http://play.golang.org/p/g7dNlrG_vr中查看并复制以下代码:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    i := 1
    clear(&i)
    fmt.Println(i)
}

func clear(dst interface{}) {
    // ValueOf to enter reflect-land
    dstPtrValue := reflect.ValueOf(dst)
    // need the type to create a value
    dstPtrType := dstPtrValue.Type()
    // *T -> T, crashes if not a ptr
    dstType := dstPtrType.Elem()
    // the *dst in *dst = zero
    dstValue := reflect.Indirect(dstPtrValue)
    // the zero in *dst = zero
    zeroValue := reflect.Zero(dstType)
    // the = in *dst = 0
    dstValue.Set(zeroValue)
}

要模拟GetMulti,你需要更多的步骤来处理切片。你可以在http://play.golang.org/p/G_6jit2t-2中查看以下示例代码:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    s := []int{}
    getMultiZeroes(&s, 10)
    fmt.Println(s)
}

func getMultiZeroes(slicePtrIface interface{}, howMany int) {
    // enter `reflect`-land
    slicePtrValue := reflect.ValueOf(slicePtrIface)
    // get the type
    slicePtrType := slicePtrValue.Type()
    // navigate from `*[]T` to `T`
    sliceElemType := slicePtrType.Elem().Elem() // crashes if input type not `*[]T`
    // we'll need this to Append() to
    sliceValue := reflect.Indirect(slicePtrValue)
    // and this to Append()
    sliceElemValue := reflect.Zero(sliceElemType)

    // append requested number of zeroes
    for i := 0; i < howMany; i++ {
        // s := append(s, v)
        sliceValue.Set(reflect.Append(sliceValue, sliceElemValue))
    }
}

在实际代码中(而不是像你现在这样进行测试),使用类型判断(正如Martin建议的)会更快,因为每种类型都会运行专门的本地代码;如果你有不同的类型行为,这也可能很方便。你可以在http://play.golang.org/p/q-9WyUqv6P中查看GetMulti的示例代码:

package main

import "fmt"

func main() {
    s := []int{}
    getZeroes(&s)
    fmt.Println(s)

    fails := []float32{}
    getZeroes(&fails)
}

func getZeroes(slicePtrIface interface{}) {
    switch sp := slicePtrIface.(type) {
    case *[]int:
        (*sp) = append((*sp), 0, 0)
    case *[]string:
        (*sp) = append((*sp), "", "")
    default:
        panic(fmt.Sprintf("getZeroes: passed type %T, which is not a pointer to a slice of a supported type", slicePtrIface))
    }
}

你甚至可以将这两种方法简单地结合起来;为常见类型编写自定义代码,并在默认情况下调用基于reflect的版本。你可以在http://play.golang.org/p/6qw52B7eC3中查看演示(不复制代码,因为它只是将上述两种方法简单地组合在一起)。

如果需要,你还可以参考最近的另一个问题,该问题讨论如何创建一个传递给GetMulti的值,而不是模拟GetMulti本身。

更多的是一般参考,而不是回答你的问题:

了解“Go缺乏按引用传递”是有用的,但也需要一些解释。Go有指针和其他类型(如包含指针的切片)来存储数据。所谓“缺乏按引用传递”的意思只是Go永远不会隐式地将值参数(如intstruct)更改为指针。C++的引用参数正是这样做的:C++中的void f(i int&) { i++; }会在调用方在调用点隐式地将i更改,而不需要显式地传递指针。func (i int) { i++ }则不会更改。

在Go中,你可以查看传递给函数调用的类型,并确定它可以更改的数据。对于C++引用参数或某些语言的“按引用传递”语义,任何调用都可能更改局部变量;你必须查看声明才能确定。

为了避免不必要的数据复制,切片、字符串、映射、接口和通道值的实现中已经使用了指针。在这些类型中,指针、切片和映射实际上可以通过它们来修改数据。此外,就像在C++中一样,Go的类似this的接收器参数可以是一个指针,而不需要在调用代码中显式使用&。关于这一点,Russ Cox的godata帖子这篇关于何时需要指针的总结中有更多的内容。

英文:

You can emulate the AppEngine datastore interface using reflect; usually I say minimize reflection, but you (and AppEngine and other ORMs) have no other great option here to present the interface you want. For something emulating Get you:

  • get a reflect.Value with ValueOf()
  • get the type of the thing you want to create
  • create it with reflect.Zero
  • optionally fill in some data with reflect.Field(), etc.
  • use reflect.Indirect() and Value.Set() to set the original through the pointer.

A trivial example that just zeroes a struct through a pointer is at http://play.golang.org/p/g7dNlrG_vr and copied here:

package main

import (
	&quot;fmt&quot;
	&quot;reflect&quot;
)

func main() {
	i := 1
	clear(&amp;i)
	fmt.Println(i)
}

func clear(dst interface{}) {
	// ValueOf to enter reflect-land
	dstPtrValue := reflect.ValueOf(dst)
	// need the type to create a value
	dstPtrType := dstPtrValue.Type()
	// *T -&gt; T, crashes if not a ptr
	dstType := dstPtrType.Elem()
	// the *dst in *dst = zero
	dstValue := reflect.Indirect(dstPtrValue)
	// the zero in *dst = zero
	zeroValue := reflect.Zero(dstType)
	// the = in *dst = 0
	dstValue.Set(zeroValue)
}

For emulating GetMulti you need more steps to work with the slice. An example is at http://play.golang.org/p/G_6jit2t-2 and below:

package main

import (
	&quot;fmt&quot;
	&quot;reflect&quot;
)

func main() {
	s := []int{}
	getMultiZeroes(&amp;s, 10)
	fmt.Println(s)
}

func getMultiZeroes(slicePtrIface interface{}, howMany int) {
	// enter `reflect`-land
	slicePtrValue := reflect.ValueOf(slicePtrIface)
	// get the type
	slicePtrType := slicePtrValue.Type()
	// navigate from `*[]T` to `T`
	sliceElemType := slicePtrType.Elem().Elem() // crashes if input type not `*[]T`
	// we&#39;ll need this to Append() to
	sliceValue := reflect.Indirect(slicePtrValue)
	// and this to Append()
	sliceElemValue := reflect.Zero(sliceElemType)

	// append requested number of zeroes
	for i := 0; i &lt; howMany; i++ {
		// s := append(s, v)
		sliceValue.Set(reflect.Append(sliceValue, sliceElemValue))
	}
}

In live code (as opposed to testing like you're doing), it'd be faster to use a type switch (as Martin suggested) so that specialized native code runs for each type; that might also be handy if you have different behavior by type. An example for GetMulti is at http://play.golang.org/p/q-9WyUqv6P and below:

package main

import &quot;fmt&quot;

func main() {
	s := []int{}
	getZeroes(&amp;s)
	fmt.Println(s)

	fails := []float32{}
	getZeroes(&amp;fails)
}

func getZeroes(slicePtrIface interface{}) {
	switch sp := slicePtrIface.(type) {
	case *[]int:
		(*sp) = append((*sp), 0, 0)
	case *[]string:
		(*sp) = append((*sp), &quot;&quot;, &quot;&quot;)
	default:
		panic(fmt.Sprintf(&quot;getZeroes: passed type %T, which is not a pointer to a slice of a supported type&quot;, slicePtrIface))
	}
}

You could even trivially combine the two; write custom code for common types and call the slow reflect-based version in the default case. Demo at http://play.golang.org/p/6qw52B7eC3 (not copying because it's a such a simple stitching together of the two above).

There happened to be another recent question on how to make a value to pass to GetMulti, rather than emulating the GetMulti itself, if that comes up.


More for general reference than to answer this:

"Go lacks pass by reference" is useful to know, but also needs some elaboration. Go has pointers, and other types like slices that contain pointers to data. The sense in which there isn't "pass by reference" is just that Go will never change a value argument (int, struct) into a pointer implicitly. C++ reference arguments do exactly that: C++ void f(i int&amp;) { i++; } changes i in the caller without the caller explicitly passing in a pointer at the callsite. func (i int) { i++ } doesn't.

In Go, you can look at the types passed to a function call and tell what data it can change. With C++ reference arguments or some languages' "pass by reference" semantics, any call might change locals; you can't tell without looking up the declarations.

For purposes of avoiding unnecessary copying of data, there are already pointers in the implementations of slice, string, map, interface, and channel values. Of those types, pointers, slices, and maps will actually let you modify data through them. Also, like in C++, Go's this-like receiver parameter can be a pointer without an explicit &amp; in the calling code. There's more about this in Russ Cox's godata post and this summary on when you need a pointer or not.

答案2

得分: 6

《Go编程语言规范》

调用

在函数调用中,函数值和参数按照通常的顺序进行求值。在它们求值之后,调用的参数按值传递给函数,并且被调用的函数开始执行。当函数返回时,函数的返回参数按值传递回调用函数。

在Go语言中,所有的东西都是按值传递的,没有按引用传递的。因此,需要传递一个指针。例如,

package main

import "fmt"

var Stuff map[string]interface{}

func main() {
    Stuff = make(map[string]interface{})
    Stuff["key"] = 9001
    var value interface{}
    get("key", &value)
    num := value.(int)
    fmt.Println("num:", num)
}

func get(k string, v interface{}) {
    *v.(*interface{}) = Stuff[k]
}

输出:

num: 9001
英文:

> The Go Programming Language Specification
>
> Calls
>
> In a function call, the function value and arguments are evaluated in
> the usual order. After they are evaluated, the parameters of the call
> are passed by value to the function and the called function begins
> execution. The return parameters of the function are passed by value
> back to the calling function when the function returns.

In Go everything is passed by value; nothing is passed by reference. Therefore, pass a pointer. For example,

package main

import &quot;fmt&quot;

var Stuff map[string]interface{}

func main() {
	Stuff = make(map[string]interface{})
	Stuff[&quot;key&quot;] = 9001
	var value interface{}
	get(&quot;key&quot;, &amp;value)
	num := value.(int)
	fmt.Println(&quot;num:&quot;, num)
}

func get(k string, v interface{}) {
	*v.(*interface{}) = Stuff[k]
}

Output:

<pre>
num: 9001
</pre>

答案3

得分: 1

首先:Go语言中绝对没有“按引用传递”的概念。没有的。你可以传递一个指针来实现类似的效果。这个指针值是按值传递的,因为Go语言中所有的东西都是按值传递的。

其次:你可以返回值而不是传递一个指向接口的指针并修改指向的值(虽然可行但不太好看)。

第三:如果不使用类型断言,就无法(即使使用反射或不安全的方式)实现这个功能。而且在使用指向接口的指针之前,你应该先掌握Go语言和接口的知识。

第四:如果你的解决方案需要使用interface{},那么可能你做错了什么。你确定你的实体不能通过某个(非空)接口来描述吗?

话虽如此,下面这样的代码是可以工作的。

func main() {
    var num int
    Stuff["key"] = 9001
    num = get("key").(int)
}
func get(k string) interface{}{
    return Stuff[k]
}
英文:

First: There is absolutely no concept of "pass by reference" in Go. There isn't. What you can do is pass around a pointer. This pointer value is passed by value as everything in Go is passed by value.

Second: Instead of passing in a pointer to an interface and modify the pointees value (doable but ugly) you could return the value (much nicer).

Third: It cannot (i.e. not without reflection or unsafe) be done without type assertions.
And you should never (in the sense of "no until you mastered Go and interfaces") use pointer to interface.

Fifth: If your solution requires interface{} you might be doing something wrong. Are you sure your entities are not describable by some (non empty) interface?

That said, something like that works.

func main() {
    var num int
    Stuff[&quot;key&quot;] = 9001
    num = get(&quot;key&quot;).(int)
}
func get(k string) interface{}{
    return Stuff[k]
}

答案4

得分: 1

马丁·加拉格尔的解决方案完美运行,但正如他所说,你不能在Golang中使用泛型,所以代码看起来有点丑陋。我猜另一种解决方案是始终使用interface{}作为类型,然后在程序中进行类型转换(或检查类型)。类似这样的代码:http://play.golang.org/p/0o20jToXHV

英文:

Martin Gallagher solution works perfectly, but as he said, you can't use generics in Golang, so code looks a bit ugly. I guess another solution is to use always interface{} as the type and then cast (or check the type) in your program. Something like this: http://play.golang.org/p/0o20jToXHV

答案5

得分: 0

没有泛型,你将不得不为每个支持的类型实现 switch {}。

英文:

http://play.golang.org/p/kx5HvEiOm9

Without generics you will have to implement the switch {} for each of your supported types.

huangapple
  • 本文由 发表于 2014年11月1日 19:27:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/26688854.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定