Channels are the preferred way to communicate between coroutines. V’s channels work basically like those in Go. You can push objects into a channel on one end and pop objects from the other end. Channels can be buffered or unbuffered and it is possible to select from multiple channels.

    Syntax and Usage

    Channels have the type chan objtype. An optional buffer length can specified as the cap property in the declaration:

    1. ch := chan int{} // unbuffered - "synchronous"
    2. ch2 := chan f64{cap: 100} // buffer length 100

    Channels do not have to be declared as mut. The buffer length is not part of the type but a property of the individual channel object. Channels can be passed to coroutines like normal variables:

    1. import sync
    2. fn f(ch chan int) {
    3. // ...
    4. }
    5. fn main() {
    6. ch := chan int{}
    7. go f(ch)
    8. // ...
    9. }

    A channel can be closed to indicate that no further objects can be pushed. Any attempt to do so will then result in a runtime panic (with the exception of select and try_push() - see below). Attempts to pop will return immediately if the associated channel has been closed and the buffer is empty. This situation can be handled using an or branch (see ).

    1. mut ch := chan int{}
    2. mut ch2 := chan f64{}
    3. // ...
    4. ch.close()
    5. // ...
    6. m := <-ch or {
    7. println('channel has been closed')
    8. }
    9. // propagate error
    10. y := <-ch2 ?

    Channel Select

    The select command allows monitoring several channels at the same time without noticeable CPU load. It consists of a list of possible transfers and associated branches of statements - similar to the match command:

    1. import time
    2. fn main () {
    3. mut ch := chan f64{}
    4. mut ch3 := chan f64{}
    5. mut b := 0.0
    6. // ...
    7. select {
    8. a := <-ch {
    9. // do something with `a`
    10. }
    11. b = <-ch2 {
    12. // do something with predeclared variable `b`
    13. }
    14. ch3 <- c {
    15. // do something if `c` was sent
    16. }
    17. > 500 * time.millisecond {
    18. // do something if no channel has become ready within 0.5s
    19. }
    20. }
    21. }

    The timeout branch is optional. If it is absent select waits for an unlimited amount of time. It is also possible to proceed immediately if no channel is ready in the moment select is called by adding an else { ... } branch. else and > timeout are mutually exclusive.

    Special Channel Features

    For special purposes there are some builtin properties and methods:

    1. import sync
    2. struct Abc{x int}
    3. a := 2.13
    4. mut ch := chan f64{}
    5. res := ch.try_push(a) // try to perform `ch <- a`
    6. println(res)
    7. l := ch.len // number of elements in queue
    8. println(l)
    9. println(c)
    10. // mut b := Abc{}
    11. // mut ch2 := chan f64{}
    12. // res2 := ch2.try_pop(mut b) // try to perform `b = <-ch2

    The try_push/pop() methods will return immediately with one of the results .success, .not_ready or .closed - dependent on whether the object has been transferred or the reason why not. Usage of these methods and properties in production is not recommended - algorithms based on them are often subject to race conditions. Use select instead.

    Data can be exchanged between a coroutine and the calling thread via a shared variable. Such variables should be created as references and passed to the coroutine as mut. The underlying struct should also contain a mutex to lock concurrent access:

    1. import sync
    2. struct St {
    3. mut:
    4. x int // share data
    5. mtx &sync.Mutex
    6. }
    7. fn (mut b St) g() {
    8. b.mtx.m_lock()
    9. // read/modify/write b.x
    10. b.mtx.unlock()
    11. }
    12. fn caller() {
    13. mut a := &St{ // create as reference so it's on the heap
    14. x: 10
    15. mtx: sync.new_mutex()
    16. }
    17. go a.g()
    18. a.mtx.m_lock()
    19. // read/modify/write a.x
    20. }