Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Consuming all elements of a channel into a slice

Tags:

go

channels

How can I construct a slice out of all of elements consumed from a channel (like Python's list does)? I can use this helper function:

func ToSlice(c chan int) []int {
    s := make([]int, 0)
    for i := range c {
        s = append(s, i)
    }
    return s
}

but due to the lack of generics, I'll have to write that for every type, won't I? Is there a builtin function that implements this? If not, how can I avoid copying and pasting the above code for every single type I'm using?

like image 769
phihag Avatar asked Dec 04 '13 20:12

phihag


2 Answers

If there's only a few instances in your code where the conversion is needed, then there's absolutely nothing wrong with copying the 7 lines of code a few times (or even inlining it where it's used, which reduces it to 4 lines of code and is probably the most readable solution).

If you've really got conversions between lots and lots of types of channels and slices and want something generic, then you can do this with reflection at the cost of ugliness and lack of static typing at the callsite of ChanToSlice.

Here's complete example code for how you can use reflect to solve this problem with a demonstration of it working for an int channel.

package main

import (
    "fmt"
    "reflect"
)

// ChanToSlice reads all data from ch (which must be a chan), returning a
// slice of the data. If ch is a 'T chan' then the return value is of type
// []T inside the returned interface.
// A typical call would be sl := ChanToSlice(ch).([]int)
func ChanToSlice(ch interface{}) interface{} {
    chv := reflect.ValueOf(ch)
    slv := reflect.MakeSlice(reflect.SliceOf(reflect.TypeOf(ch).Elem()), 0, 0)
    for {
        v, ok := chv.Recv()
        if !ok {
            return slv.Interface()
        }
        slv = reflect.Append(slv, v)
    }
}

func main() {
    ch := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            ch <- i
        }
        close(ch)
    }()
    sl := ChanToSlice(ch).([]int)
    fmt.Println(sl)
}
like image 55
Paul Hankin Avatar answered Sep 21 '22 13:09

Paul Hankin


You could make ToSlice() just work on interface{}'s, but the amount of code you save here will likely cost you in complexity elsewhere.

func ToSlice(c chan interface{}) []interface{} {
    s := make([]interface{}, 0)
    for i := range c {
        s = append(s, i)
    }
    return s
}

Full example at http://play.golang.org/p/wxx-Yf5ESN

That being said: As @Volker said in the comments from the slice (haha) of code you showed it seems like it'd be saner to either process the results in a streaming fashion or "buffer them up" at the generator and just send the slice down the channel.

like image 23
Ask Bjørn Hansen Avatar answered Sep 19 '22 13:09

Ask Bjørn Hansen