Search code examples
gointerfacechannel

Passing a channel of things as a channel of interfaces in Go


My program has a pipeline structure, and I just implemented a caching filter that sends stuff directly to output if the already processed version of data is in the cache.

func Run(in chan downloader.ReadyDownload) chan CCFile {
    out := make(chan CCFile)
    processQueue := make(chan downloader.ReadyDownload)
    go cache.BypassFilter(in, processQueue, out)
        // writes the cached, already processed version to out if it exists
        // otherwise redirects the input to processQueue
    go process(processQueue, out)
    return out
}

The problem is that my program has multiple places like this, and many kind of structs (like ReadyDownload and CCFile in this snippet) are being passed through the channels. They all implement this interface

type ProcessItem interface {
    Source() string
    Target() string
    Key() string
}

so my BypassFilter() function signature looks like this:

func (c Cache) BypassFilter(in chan ProcessItem, process chan ProcessItem, bypass chan ProcessItem)

But this brings about the following error:

cannot use in (type chan downloader.ReadyDownload) as type chan utils.ProcessItem in function argument

Although ReadyDownload certainly implements ProcessItem. For example, this works without problems:

foo := downloader.ReadyDownload{}
var bar utils.ProcessItem
bar = foo

So, my (yet) very limited understanding of Go types and interfaces brings me to ask this question: Is it the fact that they are channels of something and something else, that makes the types incompatible? What should I do to make it work? Let's say that I've got a channel of ReadyDownloads. Is the only way to forward the data to a function that takes, let's say channel of interface{}s as a parameter, to create a new channel of interface{}s, pass that to the function and read stuff from the channel of ReadyDownloads and feed them to the other channel?


Solution

  • These two are different types:

    processQueue chan ReadyDownload
    
    process chan ProcessItem
    

    You can put a ReadyDownloader value in a channel of type chan ProcessItem (if it implements the interface), but you cannot convert one channel type to another, in the same way that you cannot convert a []T slice into a []interface{} slice, another common confusion similar to this one.

    What you need to do is make all the channels of type chan ProcessItem:

    func Run(in chan ProcessItem) chan CCFile {
        out := make(chan CCFile)
        processQueue := make(chan ProcessItem)
        go cache.BypassFilter(in, processQueue, out)
            // writes the cached, already processed version to out if it exists
            // otherwise redirects the input to processQueue
        go process(processQueue, out)
        return out
    }
    

    To read more about why this is (for slices, but the same applies for channels), you can read the following go-wiki page:

    http://code.google.com/p/go-wiki/wiki/InterfaceSlice