A funny thing happened on the way to Rust

25 Jun 2021

"V lang logo" MIT License Copyright (c) 2019 Don Alfons Nisnoni

I have recently began investing time in learning Rust as it has many of the features I want in a language outside of Dart. For me Rust provides a compile-to-native language to can also be used without a large standard library/runtime enabling nice compact binaries and able to run in "bare-metal" environments, both on large devices like Raspberry Pi's all the way down to much smaller microcontrollers such as ARM cortex-M's and ESP32's, etc. It's also a modern language with functional features, immutable data, good memory management without GC and also importantly not the horrendously complicated C++. In addition to that, it also makes interop with existing C libraries easy and likewise allows easily creating libraries that expose C APIs. And finally ease of cross-compiling, a rich package ecosystem and great tooling make a very compelling option for a programmer to learn and use.

Yet despite all that, Rust is not exaclty an easy language to just pick up and start using, so I was pleasently surprised to run across, just by sheer chance, another new language in a similar vein to Rust called "V".

However, unlike with Rust, I was able to pickup the core concepts of using V in a matter of an hour or so, which seems to be one of the key aims of the language. It actually very much reminds me of Dart, which aims to be easy and familiar for programmers that already know another C-like language.

Installing and using V

Another key selling point of V is its simple install method and lightning quick compile times. This claim at least proved to be spot on, with it being very easy to install it on Debian following it's Linux install instructions and I found it quite impressive that V bootstraps its own compiler using a single C file implementation of itself.

Writing a hello world example is straight forward, and the V tooling provides functionality for scaffolding a new project, a easy command for running code as you work on it, etc that a Dart programmer (or even a Node, Deno, Rust user etc) would also find very familiar and have come to expect as standard.

FFI or bust

After learning the basics of the language To quickly try out what using V would be like, I decided to try to quickly make a simple dynamic library that I could call from a Dart application via FFI.

It was at this point that I ran into one of the rough edges of V, still being a fairly new language and small project. There was no mention in the documentation of how to create a dynamic library and non even in the v tooling help. I eventually tracked down a github issue which happened to have the required information and that you need to do the following command:

v -shared -prod fib.v 

That issue also appraised me of the quite "light" name mangling scheme that V uses for it's C ABI function naming as well as the new functionality that is now available in V to set the C abi name of a function using what V call a function attribute:

[export: 'fibv']
pub fn fib(n int) int {
...

Calling the function from Dart is then just the standard Dart FFI machinery:

import 'dart:ffi';

// Fib function from V
typedef FibFunc = Int32 Function(Int32 a);
typedef Fib = int Function(int a);

void main() async {
  // expect this being run from same dir that contains the dyn shared-lib file
  final dylib = DynamicLibrary.open('./fibrec.so');

  final fibPointer = dylib.lookup<NativeFunction<FibFunc>>('fibv');
  final fib = fibPointer.asFunction<Fib>();
  print('Fib 7 = ${fib(7)}');
}

A full example containing the above code samples can be found in my github repo.

No unicorns

As with all languages and technologies, there are a number of cons as well as pros to using V. While it is simple and reasonably well documented, I did manage to find even in my brief foray into using V gaps in the compilers documentation. Also because of it's relative newness, the community and ecosystem is only just developing, so for instance while there is a binding to a cross-platform audio library, it itself is quite immature and very incomplete (no audio input) and support for other things I need for a current project such as Midi and OLED I2C displays is non-existent.

Another issue is that while V is proud that it produces binaries that are much smaller than those of comparable language Go (and Dart for that matter) it still seems to require a reasonably large standard library/runtime to be included in its binaries. Even using the -prod flag, I found my almost hello-world like code produced a shared dynamic library of around ~300kb, a executable of ~100kb.While these are very small, they are no where near the tiny sizes that can be produced by Rust when using its #![no_std]where even a few kb's would be vital when using with small microcontrollers.

So in the end, will I use V instead of Rust? Well, no.

While I am very impressed with V and will continue to check on it's progress in the future, the need to have a mature and well tested cross-compilation toolchain for ARM and embedded devices and the huge community and package ecosystem that Rust now has means that despite the huge appeal of V's simplicity, for now I remain on the path to learning Rust and plan to use it for a number of future projects both on desktop, mobile, wasm and embedded microcontrollers.