# #go #generics #beta
Вопрос:
В настоящее время я пытаюсь отправить данные по каналу в гороутину, которая затем обработает их дальше. Моя проблема в том, что я хочу, чтобы канал мог работать с любым типом. Для этого я изучал недавно представленные дженерики в 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
Есть ли способ сделать это правильно?
Я рассматривал возможность не использовать универсальные методы и просто использовать интерфейс{} в качестве типа, но это сделало бы всю логику запутанной, поскольку она требует синтаксического анализа (и, возможно, даже невозможна, поскольку данные могут быть довольно сложными (вложенные структуры))
Ответ №1:
Это ошибочный способ использования дженериков.
Параметризованный тип, например chan T
, должен быть создан с конкретным параметром типа, прежде чем вы сможете его использовать. Учитывая определенный тип чана, как предложено в комментариях:
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>();
И в Го это было бы не чем иным, как
c := make(chan interface{})
Вы можете взглянуть на это с другой стороны: как вы ожидаете, что этот универсальный чан будет работать с операциями приема?
c := make(GenericChan) // wrong syntax: instantiating without type param
c <- "a string" // let's pretend you can send anything into it
// ...
foo := <-c
На данный момент что такое foo
? Является ли это string
? Или ан int
? Вы можете отправить в него все, что угодно. Вот почему универсальное решение, подобное вашему примеру, не может работать так, как вы задумали. Это должно быть chan interface{}
, а затем вы вводите полученный элемент, как сейчас, без дженериков.
Смысл дженериков состоит в написании кода, который использует произвольные типы, сохраняя при этом безопасность типов:
func receiveAny[T any](c chan T) T {
return <-c
}
который вы можете назвать либо a chan int
, либо a chan string
.