v / doc
Raw file

V Work In Progress

This document describes features that are not implemented, yet. Please refer to docs.md for the current state of V

Table of Contents

Concurrency

Variable Declarations

Objects that are supposed to be used to exchange data between coroutines have to be declared with special care. Exactly one of the following 4 kinds of declaration has to be chosen:

a := ...
mut b := ...
shared c := ...
atomic d := ...

To help making the correct decision the following table summarizes the different capabilities:

default mut shared atomic
write access + + +
concurrent access + + +
performance ++ ++ +
sophisticated operations + + +
structured data types + + +

Strengths

default

mut

shared

atomic

Weaknesses

default

mut

shared

atomic

<sup>1</sup> The owning coroutine will also free the memory space used for the object when it is no longer needed.
<sup>2</sup> For shared objects the compiler adds code for reference counting. Once the counter reaches 0 the object is automatically freed.
<sup>3</sup> Since an atomic variable is only a few bytes in size allocation would be an unnecessary overhead. Instead the compiler creates a global.

Compatibility

Outside of lock/rlock blocks function arguments must in general match - with the familiar exception that objects declared mut can be used to call functions expecting immutable arguments:

fn f(x St) {...}
fn g(mut x St) {...}
fn h(shared x St) {...}
fn i(atomic x u64) {...}

a := St{...}
f(a)

mut b := St{...}
f(b)
go g(mut b)
// `b` should not be accessed here any more

shared c := St{...}
h(shared c)

atomic d &u64
i(atomic d)

Inside a lock c {...} block c behaves like a mut, inside an rlock c {...} block like an immutable:

shared c := St{...}
lock c {
    g(mut c)
    f(c)
    // call to h() not allowed inside `lock` block
    // since h() will lock `c` itself
}
rlock c {
    f(c)
    // call to g() or h() not allowed
}

Automatic Lock

In general the compiler will generate an error message when a shared object is accessed outside of any corresponding lock/rlock block. However in simple and obvious cases the necessary lock/unlock can be generated automatically for array/map operations:

shared a := []int{cap: 5}
go h2(shared a)
a &lt;&lt; 3
// keep in mind that `h2()` could change `a` between these statements
a &lt;&lt; 4
x := a[1] // not necessarily `4`

shared b := map[string]int{}
go h3(shared b)
b['apple'] = 3
c['plume'] = 7
y := b['apple'] // not necesarily `3`

// iteration over elements
for k, v in b {
    // concurrently changed k/v pairs may or my not be included
}

This is handy, but since other coroutines might access the array/map concurrently between the automatically locked statements, the results are sometimes surprising. Each statement should be seen as a single transaction that is unrelated to the previous or following statement. Therefore - but also for performance reasons - it's often better to group consecutive coherent statements in an explicit lock block.

Channels