How would you access the underlying array passed to a function expecting an empty interface in Go?

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

How would you access the underlying array passed to a function expecting an empty interface in Go?

问题

假设我们有一个以下形式的函数:

func WorkMagic(obj interface{}) interface{} {
    switch t := obj.(type) { 
    case string:
        // 进行字符串操作
    default:
        // 进行切片操作
    }
    ...
}

我期望obj可以是字符串或切片,可以通过switch语句确定。对于切片,我希望能够对任意类型的切片进行排序操作。似乎使用unsafe包以类似于这篇文章中讨论的方式是实现这一目标的最佳方法。

然而,在这里,该函数接受特定类型的切片([]string),而我希望能够处理任意类型的切片。所以问题是,鉴于我接受一个空接口作为输入,我如何使用unsafe.Pointer访问底层的切片/数组,以便能够循环遍历并修改与哪个索引关联的值?

英文:

So let's say that we have a function of the following form:

func WorkMagic(obj interface{}) interface{} {
    switch t := obj.(type) { 
    case string:
        // Do string magic
    default:
        // Do slice magic
    }
    ...
}

I am expecting obj to be either a string or a slice, which I can ascertain via the switch. In the case of a slice, I want to be able to do ordering work on any arbitrary slice, regardless of type. Seems like the best way to accomplish this is using the unsafe package in a similar fashion to that discussed in this article.

Here however, the function accepts a specific type of slice ([]string), whereas I would like to be able to work on any slice. So the question is, given that I am accepting an empty interface as input, how might I access the underlying slice / array using unsafe.Pointer so as to be able to loop through and modify which value is associate with which index?

答案1

得分: 2

你需要使用反射。它使你能够以通用的方式工作,而不会放弃类型和内存安全性,就像unsafe一样。阅读Go博客的反射法则

func actOnSlices(i interface{}) {
    v := reflect.ValueOf(i)
    for v.Kind() == reflect.Ptr { // 解引用指针
        v = v.Elem()
    }
    if v.Kind() != reflect.Slice { // 确保你得到的是一个切片
        panic("给定的参数不是一个切片")
    }
    // 做切片操作
}

回答你的第二个问题:

是的,这是可以做到的:切片的元素是可寻址的,因此可以进行设置。参见以下工作示例

package main

import (
	"fmt"
	"reflect"
)

func main() {
	s := []string{"foo", "bar"}
	fmt.Println(swapIndexes(s, 0, 1)) // 输出 [bar foo]
}

func swapIndexes(i interface{}, x, y int) interface{} {
	v := reflect.ValueOf(i)
	for v.Kind() == reflect.Ptr { // 解引用指针
		v = v.Elem()
	}
	if v.Kind() != reflect.Slice { // 确保你得到的是一个切片
		panic("给定的参数不是一个切片")
	}
	t := v.Index(x).Interface()
	v.Index(x).Set(v.Index(y))
	v.Index(y).Set(reflect.ValueOf(t))
	return v.Interface()
}

回答你的第三个问题:

unsafe包在用户代码中不常见。它存在的目的是实现某些功能(例如反射、与C的交互),这些功能需要绕过Go的安全性保证才能工作。使用unsafe是不安全的,正如其名称所暗示的那样,因为你甚至可能在不知情的情况下造成严重问题。通过使用unsafe,你正在进行一项巨大的权衡,所以最好确保它是值得的。引用@twotwotwo的话:

使用unsafe的缺点是,如果你搞砸了,你就会回到旧日的段错误、内存损坏和缓冲区溢出安全漏洞。

此外,正如@twotwotwo建议的那样,与其使用反射来实现通用性,不如重复代码更符合Go的风格。

对于Go的类型系统来说,[]string[]int是两种完全独立且不相关的类型,就像intstring一样。这种关系(它们都是切片)只对程序员来说是显而易见的。没有办法在不指定“切片的类型”时表达“一个切片”。

英文:

You'll want to use reflection. It enables you to work generically without giving up type and memory safety like unsafe would. Read the Go blog's Laws of Reflection.

func actOnSlices(i interface{}) {

    v := reflect.ValueOf(i)

    for v.Kind() == reflect.Ptr { // dereference pointers
        v = v.Elem()
    }

    if v.Kind() != reflect.Slice { // ensure you actually got a slice
        panic("given argument is not a slice")
    }

    // do slice stuff

}

Edit to answer your second question:

Yes – this can be done: elements of a slice are adressable and hence settable. See the following working example:

package main

import (
	"fmt"
	"reflect"
)

func main() {
	s := []string{"foo", "bar"}
	fmt.Println(swapIndexes(s, 0, 1)) // prints [bar foo]
}

func swapIndexes(i interface{}, x, y int) interface{} {

	v := reflect.ValueOf(i)

	for v.Kind() == reflect.Ptr { // dereference pointers
		v = v.Elem()
	}

	if v.Kind() != reflect.Slice { // ensure you actually got a slice
		panic("given argument is not a slice")
	}

	t := v.Index(x).Interface()

	v.Index(x).Set(v.Index(y))

	v.Index(y).Set(reflect.ValueOf(t))

	return v.Interface()

}

Edit to answer your third question:

The unsafe package is not something you'll encounter much in user-land code. It exists to implement certain features (e.g. reflection, C interaction) that need to circumvent Go's safety guarantees to work. Using unsafe is unsafe, as the name suggests, because you can mess up big time without even realizing. By using unsafe, you're incurring in a big trade-off, so it better be worth it. Quoting @twotwotwo:

> The downside of unsafe is that if you mess up you're in the old days of segfaults, memory corruption, and buffer-overflow security holes.

Also, as @twotwotwo suggested; it's more "Go-like" to repeat code than using reflection to achieve genericity.

To Go's type-system, []string and []int are two completely separate and unrelated types. just as int and string would be. The relation (both are slices) is obvious only to the programmer. There is no way of expressing "a slice" without saying a slice of what.

huangapple
  • 本文由 发表于 2014年8月28日 22:17:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/25551082.html
匿名

发表评论

匿名网友

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

确定