logo

V Programming Language

Posted on: 2023-1-23

Conclusion

V seems to fit an interesting place between C and Go. It mostly appears to be inspired by C, but has go like features such as channels. The features seem to indicate this is a language that is used by people to solve real problems. There are a few eyebrow raising "not ready" features, that revolve around what appear to be pretty important categories - the "autofree" system perhaps being the most obvious.

Some features seem like they should perhaps be libraries - such as JSON or ORM. The reflection, compile time execution appear more of a work in progress. The JSON support perhaps mitigates some of these issues. It's not clear what kind of structures JSON can serialize - can it serialize references for example?

It think it's fair to say it is a significant improvement on C. It has some nice improvements over go in error handling for example.

Has some nice syntax ideas. I like the use of in for example.

If feels like a slightly more high level Odin-like language.

Good

Interesting

Not sure

Bad

Ugly

Discussion

Looks as if github repro was added to around Jun 2019. Has quite a lot of contributors, and is updated recently.

Appears to use the [] syntax for generics.

Parameters listed as name then type.

Doesn't require braces around if condition.

Uses rune for character code point.

Has a tool to convert C to V

Variables can only be declared via :=. = is used for assigning.

Use of snake_case for variable and function names, not keen.

A mutable variable is introduced via mut as in

mut age := 20
println(age)
age = 21
println(age)

No variable shadowing. No unused variables.

Has usual primitive types. Interestingly int is always 32 bits. Although elsewhere it uses PascalCase for types, built in types do not. Has promotion of types. It appears in in diagram that u64/i64 can promote to ptr!?

Strings are utf8 and immutable array of bytes. Can slice with a[1..3].

String interpolation provides access to variable names. Allows format specifiers.

Has typical literal prefixing 0x, 0b, 0o etc. Has support for _ separator.

Default float type is f64.

Has an unusual syntax for creating an array...

mut a := []int{len: 10000, cap: 30000, init: 3}

Also has an unusual it concept for array initializer, which can be used to have init value vary by it index position. it is also available in filter and map methods.

New types can be introduced with type. Has support for sum types as in.

type ObjectSumType = Line | Point

Array behaviour around slices seems to indicate that a slice is "like" an array, but it aliases the parent array.

Calls # a "gate".

Hmm. When dumping out type names they seem to follow the prefix modifier style. When declaring though.. it doesn't (always) seem to be like that.

Maps. Has a somewhat odd syntax to introduce. It says maps can be "string, rune, integer, float or voidptr". If key is not found zero is returned... hmm that seems awkward. 0 is a kind of useful valid value.

Maps are ordered.

Cyclic module imports are not allowed, like in Go.

[Uses](Cyclic module imports are not allowed, like in Go.) cast to allow access to a contained subtype through the same sum type variable. A little like the option mechanism that is seen elsewhere, were access to the contained value is made available if if proves it is available. This is in contrast to doing a cast (into a new variable), or the contained value needing to be bound to a new variable. Works with match also.

The matching with true or false as a replacement for if else or unless is quite nice.

Inclusive ranges seem to be .... Uses .. for exclusive. I think I prefer swifts ..< for that scenario.

if and match can be used as expressions, where last value is the result.

Also like the use of in as a way to simplify expressions. Looks like it's not necessary to quality an enum if, it can be infered, and can just use .name.

Uses for in style. As in other places (such as using as), necesary to mark variable as mut if intention is to mutate.

Has labelled break/continue. This is similar to as seen elsewhere. Is a little odd, but seems a pretty common convention.

If the function returns a value the defer block is executed after the return expression is evaluated

Has goto, if it jumps past variable initialization it must be marked unsafe.

Has this interesting struct update syntax using .... Not sure about the syntax but seems handy.

In order to have methods, and to differentiate the receiver has a special parameter listed before name. Whilst my gut is to not be super keen, it does make some sense in so far as a method invocation is a.method(..) and the definition then prefixes the variable.

struct User {
	age int
}

fn (u User) can_register() bool {
	return u.age > 16
}

Structs can also be embedded, doing so makes the fields of the embedded struct appear to be part of the container.

Parameters are be default immutable, and V will pass them by reference or by value however is appropriate.

Can use & to mark a variable as pass by reference. Works similarly to go pointers, or C++ references.

Has dump expression, seems useful for simple debugging.

Interfaces

No need to specify intent with something like implements, but that seems to assume I'll only know if it doesn't match the inteface at a use site where it is used as an interface. Not sure about that. Moreover previously we had a special syntax around the this parameter on a method. In an interface it appears we need a mut: section. That seems to jibe with the previous mechanism.

Smart casting seems "flow-sensitive casting", is the name for being able to use the same variable name with expanded capability once it's type has been determined.

Has Result and Option types. Has mechanisms to mark types (with ! and ? which is fairly common), as well as invoking and getting try like behavior with ! at the end of the expression. An example given...

fn f(url string) !string {
	resp := http.get(url)!
	return resp.body
}

!string means it can return error or string. http.get(url)! will return failure if the call fails.

There are other ways described to do different styles of error handling, that are more verbose, but given more control.

The custom error types section is interesting, in that an error can conform to an interface IError. A type doesn't need to claim to implement the interface, it only needs to provide the required functions/types to conform. One nice property of this idea is that to conform a type must always have a mechanism to turn an error into a string. That is kind of important to be able to generally report errors.

In generics, generic types are passed via the [] style after the function name.

Has some nice go-like channel support.

Test system seems a good thing to have baked in. Much like the JSON feature, it has a variety of extra attributes/special functions which cover a bunch of the edge cases. This extra "stuff" tends to indicate these are not experimental features, they are used by real people with real problems.

Memory management

V avoids doing unnecessary allocations in the first place by using value types, string buffers, promoting a simple abstraction-free code style.

Most objects (~90-100%) are freed by V's autofree engine: the compiler inserts necessary free calls automatically during compilation. Remaining small percentage of objects is freed via reference counting.

The developer doesn't need to change anything in their code. "It just works", like in Python, Go, or Java, except there's no heavy GC tracing everything or expensive RC for each object.

Later on a comment says that the "autofree engine" is a work in progress though.

Note 2: Autofree is still WIP. Until it stabilises and becomes the default, please avoid using it. Right now allocations are handled by a minimal and well performing GC until V's autofree engine is production ready.

You can turn autofree off at compilation time.

Performs analysis to determine if a variable needs to be heap or stack allocated. Can mark types to be heap allocated with [heap] attribute.

Has ORM built in(!) I guess akin to Linq in C#.

Has a package manager!

Has compile time code. This uses $ prefix to identify. Can be used for reflection.

Has a variety of backends including js, c and native.

Does not support null pointers (or references in V parlance).

Has support for operator overloading with some interesting restrictions and automated generation.

Has Visual Studio Code debug support. Javascript can produce source maps

Read V Docs

Links