Перейти к Общему Типу Канала

# #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 .