How to create func on a struct that has chan param which accepts multiple types?

Issue

Been playing around with Go, Goroutines and Channels for some days now.

After lots of practice and some help in here, related to Goroutines and error handling (How to handle errors and terminate Goroutine using WaitGroup), I have now hit an issue that I can’t figure out how to solve.

The issue is that I want to create a Mapper, that has a function which accepts a chan of any type. I have tried to solve this the only way that I can think of, by passing in chan interface{} instead of the real type, but this does clearly not work. (Shown in the example below)

So I am looking for the correct way of doing this and some confirmation that this is even the correct way to go.

package main

import (
    "fmt"
    "golang.org/x/sync/errgroup"
)

var g errgroup.Group

// ItemOne struct
type ItemOne struct {
    N int
}

func main() {
    m := &Mapper{}

    itemsOne, err := mapItemOne(m)
    if err != nil {
        // Handle error...
    }
    fmt.Println(itemsOne)

    itemsTwo, err := mapItemTwo(m)
    if err != nil {
        // Handle error...
    }
    fmt.Println(itemsOne)
}

func mapItemOne(m *Mapper) ([]*ItemOne, error){
    c := make(chan *ItemOne, 10)
    data := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    g.Go(func() error {
        return m.mapItems(c, data)
    })

    mappedItems := make([]*ItemOne, 0)
    for elem := range c {
        mappedItems = append(mappedItems, elem)
    }

    err := g.Wait()
    if err != nil {
        return nil, err
    }

    return mappedItems, nil
}


// ItemTwo struct
type ItemTwo struct {
    N int
}

func mapItemTwo(m *Mapper) ([]*ItemTwo, error){
    c := make(chan *ItemTwo, 10)
    data := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    g.Go(func() error {
        return m.mapItems(c, data)
    })

    mappedItems := make([]*ItemTwo, 0)
    for elem := range c {
        mappedItems = append(mappedItems, elem)
    }

    err := g.Wait()
    if err != nil {
        return nil, err
    }

    return mappedItems, nil
}

// Mapper struct
type Mapper struct {}

func (m *Mapper) mapItems(c chan *interface{}, data []int) error {
    defer close(c)

    // Imagine that something here can error...
    for _, v := range data {
        fmt.Println(v)
        item := &ItemOne{
            N: v,
        }

        c <- item
    } 

    return nil  
}

Example that works with (ItemTwo code commented out):
https://play.golang.org/p/PlqPflP7Yf7

Example that does not work (ItemTwo code commented in):
https://play.golang.org/p/xM89GVY2BoX

I have included two examples at playground here. First one works, but second is broken due to ItemTwo code being active.

Hope someone can point me in the right direction here. Thanks.

Solution

The issue is that I want to create a Mapper, that has a function which accepts a chan of any type.

You simply cannot do that in Go’s type system. You have to decide on the type and if the type is interface you are stuck with that exact type and all function signature must match. There is no co/contravariance in Go.

Best advice: Stop trying to write generic code. This is doable but it is ugly, slow and complicated.

Answered By – Volker

Answer Checked By – David Goodson (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.