在Go语言中,将数据解组(Unmarshalling)到一个切片类型中。

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

Unmarshalling in-place into a slice type in Go

问题

通常在使用Go语言时,我经常会有这样的冲动,想要写出类似以下代码的内容:

type data []event

尤其是当我知道在程序的大部分时间里,我将会传递这个切片,而不会过多考虑其内容。迟早会有时候需要将一些数据解包到这个事件切片中,然后我就会写出以下代码:

func (d *data) Unmarshal(b []byte) {
  //... 很多永远不起作用的痛苦代码
}

无论我怎么做,我始终无法弄清楚如何为我的切片类型添加一个解包方法,以原地将一些字节转换为data类型。

当我放弃时,要么写一个更简单的函数,比如func UnmarshalData(b []byte) data,这感觉像是在退缩,而且很难编写接口;要么在一开始就改变类型,创建一个结构体,比如:

type data struct {
  actuallyTheData []event
}

这感觉像是为了弥补我对Go语言的理解不足而添加的样板代码。

所以我的问题是:是否可能编写一个带有指针接收器的函数,其中接收器是一个切片类型,并且允许我原地解包(Unmarshal)?

尽管我尽力了,但它仍然不起作用(而且,让我们面对现实,相当丑陋),以下是我能做到的最接近的代码:

type foo []int

func (f *foo) Unmarshal(s string) {
	numbers := strings.Split(s, ",")
	integers := make([]int, len(numbers))
	for i, n := range numbers {
		integer, err := strconv.Atoi(n)
		if err != nil {
			log.Fatal(err)
		}
		integers[i] = integer
	}
	my_f := foo(integers)
	f = &my_f
}

这是完整的示例:https://go.dev/play/p/3q7qehoW9tm。为什么它不起作用?我对此有什么误解?

英文:

Often when using go, not sure why, I get the urge to write something like

type data []event

especially when I know I'm going to be passing the slice around without thinking too much about its contents for much of the program. Sooner or later it's going to be time to unpack some data into that slice of events and I end up writing something like:

func (d *data)Unmarshal(b []byte){
  //... lots of sad code that never works
}

No matter what I do I can never quite figure out how to bless my slice type with an unmarshal method that turns some bytes into the data type in-place.

When I give up, I either write a simpler function like func UnmarshalData(b []byte) data which feels like a retreat and makes it hard to write interfaces, or change the type in the first place and make a struct like

type data struct {
  actuallyTheData []event
}

which feels like boilerplate purely to compensate for my lack of understanding.

So my question is: is it possible to write a function with a pointer receiver where the receiver is a slice type and that allows me to e.g. Unmarshal in-place?

The closest I can get, though it still doesn't work (and, let's face it, is pretty ugly), is something like:

type foo []int

func (f *foo) Unmarshal(s string) {
	numbers := strings.Split(s, ",")
	integers := make([]int, len(numbers))
	for i, n := range numbers {
		integer, err := strconv.Atoi(n)
		if err != nil {
			log.Fatal(err)
		}
		integers[i] = integer
	}
	my_f := foo(integers)
	f = &my_f
}

Here's the full example: https://go.dev/play/p/3q7qehoW9tm. Why doesn't it work? What am I misunderstanding?

答案1

得分: 3

在你的Unmarshal函数中,最后一行代码覆盖了接收器本身,即它的地址:

f = &my_f // 改变指针的值

更新后的值不会传播给调用者。根据声明和作用域

表示方法接收器、函数参数或结果变量的标识符的作用域是函数体

你必须改变被指向的值,然后调用者在解引用时才能看到它。(事实上,你不需要转换为定义的切片类型)

func (f *foo) Unmarshal(s string) {
    // ...
    integers := make([]int, len(numbers))
    *f = integers
}

修复后的 playground:https://go.dev/play/p/3JayxQMClt-

英文:

The last line in your Unmarshal function is overwriting the receiver itself, i.e. its address:

f = &my_f // changing the value of the pointer

The updated value won't be propagated to callers. From Declarations and Scope:

> The scope of an identifier denoting a method receiver, function parameter, or result variable is the function body.

You must mutate the value that is being pointed to, then callers will see it upon dereference. (As a matter of fact, you don't have to convert to the defined slice type)

func (f *foo) Unmarshal(s string) {
    // ...
    integers := make([]int, len(numbers))
    *f = integers
}

Fixed playground: https://go.dev/play/p/3JayxQMClt-

huangapple
  • 本文由 发表于 2022年1月15日 15:01:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/70719481.html
匿名

发表评论

匿名网友

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

确定