Memory modes and concurrency · Count the Four Modes · Opt In to Ownership · Pass, Then Lock · Label the Unfinished
Count the four modes. Opt in to ownership. Pass, then lock. Label the unfinished.
Count the four modes. Opt in to ownership. Pass, then lock. Label the unfinished.
§I. Frame
Asr V session 4 of 5, Thu 2026-10-08, re-authored late after the 16:20 ET routine run died. Learn-first, contributor-aware, watch-not-adopt. Cites are vlang/v at tag 0.5.2 (7647ce1): doc/docs.md, doc/ownership.md, README.md, ROADMAP.md, and source files at the tag. The two live pages, https://vlang.io/compare and the vlang.io homepage, were fetched at 18:05 ET today and are quoted as fetched. The lab ran on the lab Mac with the Duha #6 compiler (~/src/vlang-duha6/v/v, V 0.5.2 7647ce1, called by absolute path). Every output quoted below comes from lab/lab.log.
This row has two halves. The first is what V does with memory and threads. The second is what its authors say about safety, which is still marked unfinished in most of the places that say it.
§II. Four techniques
- Count the Four Modes. Default tracing GC,
-autofree,-gc none,-prealloc. Know which one you are running and which one the docs tell you to avoid. - Opt In to Ownership.
-ownershipis off by default, works on strings, and runs through a second compiler binary. - Pass, Then Lock.
spawna thread, pass values over a channel, and put alockaround anything two threads write. - Label the Unfinished. Data-race freedom and no undefined behavior are author claims marked wip. Quote the source and the date.
§III. Count the Four Modes
docs.md, Memory management: "There are 4 ways to manage memory in V. The default is a minimal and a well performing tracing GC." The other three are -autofree (the compiler inserts free calls and the GC takes the rest), -gc none for manual control, and arena allocation with -prealloc. docs.md limits -prealloc to "short lived, single-threaded, batch-like programs (like compilers)". v help build-c names the GC: Boehm is the only one supported, with sub-modes from -gc boehm to -gc none.
01_alloc_loop.v builds 200,000 strings of about 211 bytes each and keeps only a running length. All four builds printed total bytes built: 42288890. Peak resident memory from /usr/bin/time -l, one run each:
| Mode | Flag | Max RSS (bytes) |
|---|---|---|
| Default tracing GC | none | 4,603,904 |
| No GC | -gc none | 98,304,000 |
| Arena | -prealloc | 94,535,680 |
| Autofree | -autofree | 50,118,656 |
Read it plainly. The default GC kept the peak near 4.6 MB because it reclaimed the dead strings. RSS says -gc none and -prealloc kept them until exit, which is what those modes promise for a short program. -autofree landed at about eleven times the default on this loop. One program and one run each is a demonstration, not a benchmark.
Why autofree is do-not-use: docs.md, Control, in a note box: "Autofree is still WIP. Until it stabilises and becomes the default, please avoid using it." ROADMAP.md at 0.5.2 lists "Autofree memory management option ready for production" as an unchecked 1.0 item. The homepage says the same: "not production ready yet. That's planned for V 1.0." The paragraph above the note still says autofree "just works" and handles "~90-100%" of objects. That figure is an author claim with no method attached.
Stack and heap: docs.md says V puts objects on the stack when it can and on the heap "when obviously necessary". Storing a reference that outlives its function trips an error that the value "might refer to an object stored on stack" unless the struct carries @[heap]. That is escape analysis plus an attribute, with the GC behind it.
§IV. Opt In to Ownership
doc/ownership.md: "V has an optional ownership system inspired by Rust that tracks owned values and prevents use-after-move bugs at compile time. It is currently focused on strings and is enabled with the -ownership flag." Only strings made with .to_owned() are tracked. Literals and primitives are not.
The plain compiler does not know the method. 02_owned_string.v is the doc's quick start, and without the flag it failed with error: unknown method or field: `string.to_owned`. The opt-in is total: the default compiler has no ownership vocabulary at all.
With the flag, cmd/v/v.v hands the job to a separate v3_ownership binary. If none is cached, it compiles one on first use from vlib/v3/v3.v with -gc none -d ownership and stores it under the V temp folder. The lab Mac had no cached copy (0 entries under /tmp/v_501/v/delegated_v3). This lesson did not build it, so the move error from ownership.md was not reproduced here. One thing did show: v -ownership run is refused with v: `-v2`/`-ownership` currently support direct compilation only. Use v -ownership file.v, as the doc does.
§V. Pass, Then Lock
docs.md: "go foo() runs foo() concurrently in a lightweight thread managed by the V runtime. spawn foo() runs foo() concurrently in a different thread." The compiler at 0.5.2 does something narrower. The parser builds a coroutine go only when -use-coroutines is set (vlib/v/parser/expr.v and parser.v); otherwise go becomes spawn. 04_go_keyword.v printed from go, and its generated C calls pthread_create with no photon_thread_create. docs.md never mentions the flag. In a default build, go is an OS thread.
spawn with a handle (03): four spawn square(i) calls into a []thread int, then threads.wait() printed [1, 4, 9, 16].
Channels (05): docs.md calls them "the preferred way to communicate between threads". The first draft pushed with ch <- i 10 and got error: mismatched types `void` and `int literal`. The message reads as if ch <- i bound first and its void result was multiplied. docs.md's own example writes ch <- (i + 1000) with parentheses and never says why. With ch <- (i 10), a producer filled a chan int{cap: 2}, closed it, and the reader's <-ch or { break } loop printed received [0, 10, 20, 30, 40].
Shared objects: a shared struct carries a hidden mutex, and access goes through lock (read/write) or rlock (read). 06 is docs.md's example as written. main spawns two increment() calls and reads with rlock without waiting for either thread. Five runs on the lab Mac each printed Final value: 2. Nothing in that code orders the read after the writes; the 2 came from timing. 07 waits on its handles: four threads each took lock c 100,000 times and the total was 400000.
08 is the race. Four threads get mut c to one plain struct, no shared, no lock, and each adds 100,000. The 0.5.2 compiler built it with no error or warning. Three runs printed 142371, 134572, and 151513 against an expected 400000. Lost updates, every run.
Memory-unsafe code (09): pointer indexing outside unsafe is refused (error: pointer indexing is only allowed in `unsafe` blocks). docs.md closes that section with "This is work in progress."
§VI. Label the Unfinished
Each line below is an author claim. Source and wording as found:
- vlang.io/compare (fetched 2026-10-08 18:05 ET): "Fearless concurrency (no data race guarantee at compilation) wip".
- Same page: V offers "lack of undefined behavior (wip, e.g. overflowing can still result in UB)".
- Same page, in the opening list of what V set out to combine: "Safety (immutability, no null, option types, free from data races)", with no wip mark.
- README.md at 0.5.2: "Safety: no null, no globals, no undefined behavior (wip), immutability by default".
- vlang.io homepage (fetched the same minute): "No undefined behavior" with a "new!" tag and no wip mark.
They disagree. Three say wip, two say done, and the compare page says both. The pinned sources side with wip: ROADMAP.md at 0.5.2 still lists an unchecked "Runtime race detector" and "Thread safe maps" for 0.6, docs.md warns that manual channel checks "can introduce data races", and 08 compiled a race and lost updates. The lab did not test undefined behavior. The claim stands as the compare page's own wip, on the authors' word.
§VII. Lab
Safety callout: export V_C_ERROR_BUG_REPORT_DISABLED=1 before any v command. V 0.5.2 uploads a report to bugs.vlang.io on every C compile error by default (vlib/v/builder/c_error_report.v). Each report carries up to 40 V source lines on each side of the failing line, the C context around the error, and the build options. v help build-c documents the switch; docs.md does not. lab/lab.sh sets it on its first command line. No C compile error occurred in either lab run, and the log shows no upload.
- Build
01_alloc_loop.vfour ways and compare max RSS. Predict the order first. - Run
08three times, then addsharedand alockto fix it the way07does. - Emit C for
04and confirmgobecamepthread_create.
Evidence: lab/lab.sh, lab/lab.log (run 2), lab/lab.run1.log (run 1, before the channel fix and the 04 C check), and the .v files.
§VIII. Contributor note
Four docs gaps, each with a one-file reproduction in lab/:
govsspawn. docs.md describesgoas a runtime-managed lightweight thread; without-use-coroutinesit compiles topthread_create(04).- Shared-object example. The example reads the counter without waiting on the threads (
06). Adding handles andwait()would make it correct by construction. - Channel push precedence.
ch <- i * 10fails; the docs use parentheses silently (05a). - Autofree paragraph. "It just works" sits above "please avoid using it" in the same section.
Search open issues before drafting any of them. Nothing goes upstream from a lesson.
§IX. Common mistakes
- Shipping
-autofreebecause a demo worked. - Expecting
-preallocor-gc noneto free anything during a run. - Writing
goand assuming a coroutine. - Reading a
sharedvalue beforewait(). - Repeating "no data races" or "no UB" without the wip and the date.
- Running V labs without
V_C_ERROR_BUG_REPORT_DISABLED=1.
§X. V next to Go and Rust, session 4
Next to Go, the shape is familiar: a GC by default, a go keyword, channels with close and select. Next to Rust, 08 is the sharp edge. Rust refuses to hand one &mut to several threads at compile time; V 0.5.2 built it and lost about two thirds of the writes. V's ownership mode borrows Rust's vocabulary for strings only, behind a flag and a second binary. Verdict: the concurrency surface is pleasant and the safety story is marked unfinished by its own authors. Learn it, adopt nothing. Asr #5 takes build modes, C interop, and the watch-not-adopt wrap tomorrow.
Related:
Count the four modes. Opt in to ownership. Pass, then lock. Close the editor.