使用不同的运行时类型进行通用容器化

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

Go generic container with different runtime types

问题

我目前正在尝试将数据通过通道发送给一个goroutine,然后进一步处理。我的问题是,我希望通道能够处理任何类型的数据。为了做到这一点,我正在研究Go 1.18中引入的泛型特性。

我的问题是,我需要在启动goroutine时告诉它通道的类型,但由于通道可以保存任何数据,这是不可能的。

这是我目前的代码:

线程:

func StartController[T any](sender chan Packet[T]) {
	go runThread(sender)
}

func runThread[T any](sender chan Packet[T]) {
	fmt.Println("in thread")
	for true {
		data := <-sender

		fmt.Println(data)
	}
}

我的测试函数是这样的:

func main() {
	sender := make(chan Packet)

	StartController(sender)

	sender <- Packet[int]{
		Msg: Message[int]{
			Data: 1,
		},
	}

	sender <- Packet[string]{
		Msg: Message[string]{
			Data: "asd",
		},
	}

	for true {}
}

类型:

type Message[T any] struct {
	Data T
}

type Packet[T any] struct {
	Msg Message[T]
}

目前这段代码无法编译,因为出现了以下错误:

.\test.go:8:22: cannot use generic type Packet[T interface{}] without instantiation

有没有一种正确的方法来实现这个?

我曾考虑过不使用泛型,而是使用interface{}作为类型,但这会使整个逻辑变得混乱,因为它需要解析(而且可能不可能,因为数据可能相当复杂(嵌套结构))。

英文:

I am currently trying to send data over a channel to a goroutine, which will then process it further. My problem is that I want the channel to be able to work with any type. To do this I was looking into the newly introduced generics in Go 1.18.

My problem is that I need to tell the goroutine when I am starting it what type the channel will be, which is impossible to tell since it could hold any data.

This is what I got right now:

Thread:

func StartController[T any](sender chan Packet[T]) {
	go runThread(sender)
}

func runThread[T any](sender chan Packet[T]) {
	fmt.Println(&quot;in thread&quot;)
	for true {
		data := &lt;- sender

		fmt.Println(data)
	}
}

And my test function is this:

func main() {
	sender := make(chan Packet)

	StartController(sender)

	sender &lt;- Packet[int]{
		Msg: Message[int]{
			Data: 1,
		},
	}

	sender &lt;- Packet[string]{
		Msg: Message[string]{
			Data: &quot;asd&quot;,
		},
	}

	for true {}
}

Types:

type Message[T any] struct {
	Data T
}

type Packet[T any] struct {
	Msg Message[T]
}

Right now this code doesn't compile because of

.\test.go:8:22: cannot use generic type Packet[T interface{}] without instantiation

Is there a way to do this properly?

I was looking into not using generics and just using interface{} as the type, but that would make the entire logic messy since it requires parsing (and might not even be possible since the data could be fairly complex (nested structs))

答案1

得分: 9

这是一种错误的泛型使用方式。

一个参数化类型,比如 chan T,在使用之前必须实例化为具体的类型参数。给定一个定义好的 chan 类型:

type GenericChan[T any] chan T

你仍然需要用具体的类型实例化它:

c := make(GenericChan[int])

这使得使用类型参数有点无意义。

我不知道你的背景是什么,但考虑一种泛型一直以来都是稳定存在的语言,比如 Java。考虑一下典型的 Java 泛型集合 List<T>。你通常会用一个类型来实例化它:

var list = new ArrayList<String>(); 

你在这里尝试做的是声明一个可以接受任何类型的通道。在 Java 中,一个可以容纳任何类型的列表是什么样的?

var list = new ArrayList<Object>(); 

在 Go 中,这就是

c := make(chan interface{})

你可以换个角度来看:你期望这个泛型通道在接收操作上如何工作?

c := make(GenericChan) // 错误的语法:没有实例化类型参数
c <- "a string"        // 假设你可以向其中发送任何东西

// ...

foo := <-c 

此时 foo 是什么?是一个 string 吗?还是一个 int?你可以向其中发送任何东西。这就是为什么你的示例中的泛型通道无法按照你的意图工作。它必须是 chan interface{},然后你像现在这样对接收到的项目进行类型断言,就像没有泛型一样。

泛型的目的是编写消费任意类型的代码,同时保持类型安全:

func receiveAny[T any](c chan T) T {
    return <-c
}

你可以用 chan intchan string 调用它。

英文:

That's a mistaken way of using generics.

A parametrized type, like chan T must be instantiated with a concrete type parameter before you can use it. Given a defined chan type:

type GenericChan[T any] chan T

you would still need to instantiate it with a concrete type:

c := make(GenericChan[int])

which makes using type parameters a bit moot.

I don't know what your background is, but consider a language where generics have been a stable presence since a long time. E.g. Java. And consider the typical Java generic collector List&lt;T&gt;. What you usually do is instantiating that with a type:

var list = new ArrayList&lt;String&gt;(); 

What you are attempting to do here is to declare a channel which can take any type. In Java, what would be a list that can hold any type?

var list = new ArrayList&lt;Object&gt;(); 

And in Go that would be nothing else than

c := make(chan interface{})

You can look at it another way: how do you expect this generic chan to work on receive operations?

c := make(GenericChan) // wrong syntax: instantiating without type param
c &lt;- &quot;a string&quot;        // let&#39;s pretend you can send anything into it

// ...

foo := &lt;-c 

At this point what is foo? Is it a string? Or an int? You can send anything into it. That's why a generic chan like in your example can not work the way you intend. It must be chan interface{} and then you type-assert the received item like you do now without generics.

The point of generics is to write code that consumes arbitrary types, while maintaining type safety:

func receiveAny[T any](c chan T) T {
    return &lt;-c
}

which you can call with either a chan int or a chan string.

huangapple
  • 本文由 发表于 2021年10月8日 01:05:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/69485180.html
匿名

发表评论

匿名网友

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

确定