Comparing V and Nim

This article is a draft. It is not finished yet and might never be.

Created on 07/21/2025

This is an answer to an article from the developer of the V programming language, which you can find here (archive).

First issue

Nim gives a lot of options and freedom to developers. For example, in V you would write foo.bar_baz(), but in Nim all of these are valid: foo.barBaz(), foo.bar_baz(), bar_baz(foo), barBaz(foo), barbaz(foo) etc.

There are two concepts shown here for Nim: case insensivity and uniform call syntax.

Case (in)sensitivity

Case insensivity in Nim forces to find better variables and function names, and although you could call barBar(foo) and bar_baz(foo), this shouldn’t prevent you from having a consistent code style. It is actually very helpful when using functions from other libraries, as you don’t need to worry about the case of the variables, functions, and types you will be using.

Quoting from Nim’s wiki:

Why is it case/underscore insensitive?

Uniform function call syntax (UFCS)

Basically, UFCS is a feature introduced by D that enables to call a function in two ways:

ufcs.nim
# Like this
var myArray = [1, 2, 3]
echo len(myArray)

# Or like this
# Parentheses are optional here, you can write myArray.len
echo myArray.len()

No method here, len is simply a function.

The only thing I dislike in Nim is that it is too permissive: parentheses are optional when the function only has one argument, and parentheses are optional in general (as you can see with echo).

In V there’s only one way to return a value from a function: return value. In Nim you can do return value, result = value, value (final expression), or modify a ref argument.

This one is easier to reason about when dealing with strings and seqs.

Tell me what’s best between :

proc repeatGradually(s: string): string =
  var output: string
  for i, c in s:
    for j in 0..i:
      output.add(c)
  return output

…and…

proc repeatGradually(s: string): string =
  for i, c in s:
    for j in 0..i:
      result.add(c)

No need to remember to return the variable (even though the compiler warns you when a variable is unused). It’s only annoying when declaring a proc inside a proc, using a do notation for example.

Unlike V, Nim generates unreadable C code with lots of extra bloat.

In Nim, the generated C code is for machine consumption, and not human consumption. The author then shows C output when compiling a program in %%release%% mode. Release mode enables optimizations for a faster program, at the cost of a longer compilation. So to read the generated code in the best way possible, the program should have been compiled in %%debug%% mode.

Let’s check the generated code in debug mode then. First off, the User type is not defined here, so the code to test should be this one:

type User = object
  name: string
  last_name: string
  age: Natural

var users = [
  User(name: "Carl", last_name: "Black", age: 22),
  User(name: "Sam", last_name: "Johnson", age: 23)
]

I’m using this file structure btw:

letest (directory)
|----- output (directory)
|----- testus.nim (file)

Then I run the following command: nim c --nimcache:output testus.nim

We get this (I’m only showing the important part):

@mtestus.nim.c
struct tyObject_User__yIweK7IYMHUbiRy0ABSYvA {
  NimStringV2 name;
  NimStringV2 last_name;
  NI age;
};

static const struct {
  NI cap; NIM_CHAR data[4+1];
} TM__YPEKt9akeoqMQZ8Cz6qGk4w_3 = { 4 | NIM_STRLIT_FLAG, "Carl" };
static const struct {
  NI cap; NIM_CHAR data[5+1];
} TM__YPEKt9akeoqMQZ8Cz6qGk4w_4 = { 5 | NIM_STRLIT_FLAG, "Black" };
static const struct {
  NI cap; NIM_CHAR data[3+1];
} TM__YPEKt9akeoqMQZ8Cz6qGk4w_5 = { 3 | NIM_STRLIT_FLAG, "Sam" };
static const struct {
  NI cap; NIM_CHAR data[7+1];
} TM__YPEKt9akeoqMQZ8Cz6qGk4w_6 = { 7 | NIM_STRLIT_FLAG, "Johnson" };

static NIM_CONST tyArray__otJc6muQBkFAOAJg7zYB1Q TM__YPEKt9akeoqMQZ8Cz6qGk4w_2 = {{{4, (NimStrPayload*)&TM__YPEKt9akeoqMQZ8Cz6qGk4w_3}, {5, (NimStrPayload*)&TM__YPEKt9akeoqMQZ8Cz6qGk4w_4}, ((NI)22)}
,
{{3, (NimStrPayload*)&TM__YPEKt9akeoqMQZ8Cz6qGk4w_5}, {7, (NimStrPayload*)&TM__YPEKt9akeoqMQZ8Cz6qGk4w_6}, ((NI)23)}
}
;

Again, it is not readable, but it is made for computers. The extra bloat code may be due to debug information.

Note that even in danger mode (the fastest release possible), this structure is kept, only the main and memory allocations logics vary.