The V programming language, part II
I’ve spent some more time working with V, and my conclusion is that it needs a little more time.
Programming is still enjoyable, and there are some aspects that – when I switch back to Go – I already miss. I do, in the end, like the error handling; it has enough syntactic sugar that isn’t obvious up front that make handling errors quite pleasant, and there are some things that are just harder in Go without any justification. I can’t see a significant difference in compile speed. It may be that V is even faster than Go. The language self-compiles so fast that at first I thought it had silently failed – a second or two, on my machine.
However, there are still some aspects that don’t give me a lot of confidence. First, I discovered a scoping bug, where a name escaped the function in which it was defined. I will say that, after reporting the bug, it was patched within a day or two, so that’s pretty amazing. But there’s this other issue I encountered:
resp := http.post_json(url, payload) or { exit(0) }
println(resp.body)
This code
(1)Don’t mind the error handling; it’s just to keep the
example short.
just reads the entire body, ignoring the Content- Length
of the return header. You can’t easily write a malicious server in
V
(2)You could, but you’d have to build your own http server
code.
, because on the server side the response is also a string,
but in Go? Open /dev/random
and wrap it in the ResponseWriter
interface. I
haven’t tried this; maybe there’s some code down in the guts that cuts it off
before it runs out of memory, but I do know it utterly ignores the Content- Length
. I found it because the snapcast
server’s JSON API has a bug where it
appends the entire header to the body of the response: if you obey the Content- Length
and read only that number of bytes, you get the right JSON payload. If
you just read everything the server sends you, you get JSON and some garbage.
Little details like this make me uncomfortable.
The other thing was my own fault. I read the intro for the V standard library
term
, which mentions that it’s low level and you should use term.ui
because
it’s richer, and I thought: “hey! There’s no snapcast volume contral TUI, I’ll
write one in V!”. Then I found there’s no already-existing snapcast library, so
I had to write that first, on which I spent far too much time on a couple of
hitches like that Content-Length
issue. As usual, I wrote a basic command-line
interface for several operations to test that my library was working with a real
snapcast
server, cracked my knuckles, and opened the term.ui
documentation
to start on a TUI. At which point I discovered that it’s not at all high level:
there are no layout structs, not even any widgets. It’s a very basic 2D terminal
drawing API. In my defense, the windowing ui
library is very rich, and
impressive, and I assumed the TUI library – being generally easier to port –
would be more built-out. In any case, it’s not the fault of V; I should have
scanned the library documentation first.
Furthermore, there’s no TUI library for V, at all, anywhere. The lack of a TUI is a show-stopper for me, because the GUI exists to show images and allow me to switch between terminals and to play Factorio. I don’t use GUIs if there’s a TUI or CLI available.
Finally, the LSP for v-analyzer
is quite bare-bones. In Helix, it oftens stops
working, with Helix merely reporting that the LSP closed the connection. I once
checked ps
and found about a dozen zombie v-analyzer
processes, meaning they
crashed hard while holding onto resources. The fact that V programs can even get
into that state is a little concerning: I’ve never, ever seen a Go program do
that.
Don’t get me wrong; it’s a really nice language, and it really does address many
of Go’s syntactic shortcomings so well that it’s a little painful to go back to
Go. The binaries are tiny and don’t link in any more than Go binaries do, and
even the statically compiled binaries are an order of magnitude smaller than Go.
Compile times are eye-blink fast, it has shebang support, the ability to run
code with almost boilerplate
(3)println('hello world')
is a fully-
defined program that can be run with v run
.
, a built in repl, a
Go-like built in v doc
command, and an official library
repository
(4)Which you’d think you don’t have with Go, but with the
introduction of the Go package server, all your imports, gets, and installs are
all going through that server. If you think your going directly to github with
go install
, you’re in for a shock.
– the ecosystem is pretty
great. It’s just that there’s quite a lot stubbed out, and I run into
limitations on even small projects (which is, so far, all I’ve done).
If V keeps making progress as it has the past year, it may be in a place where I’m more comfortable using it day-to-day. I tend to forget stuff pretty quickly when I’m not using it, and as soon as Go isn’t the language I always reach for, I’m going to start finding it hard to use. V has to be stable for me to “switch” to it (again, my limitation, not V’s), and it’s IMHO not there yet. We’ll see in 2026, if the world is still around then.
Update I #
I dug into the V source a bit, thinking I might be able to fix the issue. It
turns out the way the code is written, the library reads the entire stream to a
buffer and then, when the stream is empty, it parses the headers. There is
code to parse out the Content-Length
, but a) it’s not case insensitive, so
it’s not conformant, and b) it doesn’t actually use tha value. It passes it in
as a parameter to a calling call-back function, but it doesn’t stop the function
from endlessly pulling data from the stream.
Changing this would require a significant refactoring of the code, as the header would have to be parsed inline to separate the header from the body, and you’d only want to do that once; it’d change the API and processing model, and … well, I may tackle that, but not at the moment.