Question

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?

Was it helpful?

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

OTHER TIPS

Changing every channels to struct channels might work here, but in general, you might want to treat your struct type as interfaces for processing down the road. Fortunately, go gives us many solutions. Here is one.

Consider this very simple set up, where we want to use a Object struct type as several interfaces:

// Get the objects
func ParseFile(fileName string, co chan Object) {
    for _, object := range DoStuff(fileName) {
        co <- object
    }
}

// Use some saving functionality that is defined elsewhere as:
func Archive(cs chan Saveable) {
    for saveable := range cs {
        saveable.Save()
    }
}

type Saveable interface {
    Save()
}

//Implement the interfaces...
func (*Object) Save() {
    fmt.Println("Naa, I'm lazy")
}

// Or some throwing functionality?
func ThrowOnTheWall(ct chan Throwable) {
    for throwable := range cs {
        throwable.Throw()
    }
}

//...

co := make(chan Object)
go ParseFile("file.xml", co)
Archive(co) // Will NOT work, co is of the wrong type.

Here, using everywhere some chan Object is not suitable, because you might want to throw on the wall something different than an object (e.g., type Defecation struct {...} that you would implement as a Throwable too.).

You could use a go routine to do the casting in the background:

func ObjectToSaveable(from chan Object) chan Saveable {
    to := make(chan Saveable)
    go func() {
        for object := range from {
            to <- &object
        }
        close(to)
    }()
    return to
}

And then use it to encapsulate the initial channel:

co := make(chan Object)
go ParseFile("file.xml", co)
Archive(ObjectToSaveable(co))
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top