Variables and Types in V (Vlang)
Learn how V handles variables, primitive types, immutability, type inference, and conversions with practical Docker-ready examples
V is a statically typed, strongly typed language that leans heavily on type inference. You rarely need to write type names, yet every value still has a fixed compile-time type. V also takes a strong stance on mutability: every binding is immutable unless you explicitly mark it mut. This combination makes V code feel as light as Go or Python while keeping the safety of a typed compiler.
In this tutorial you will learn how to declare variables, work with V’s primitive types, convert between numeric types, and use module-level constants. We will also look at how V handles the absence of a value – there is no null in V, so optional results use a different mechanism.
The examples below use V’s preferred style: short variable declarations with :=, single-quoted strings, and tab indentation (which v fmt would enforce automatically).
Declaring Variables
V uses the walrus-style := operator to declare and initialize a variable in one step. The type is inferred from the right-hand side. Reassignment is only allowed when the variable was declared mut.
Create a file named variables.v:
| |
Try removing mut from score and the V compiler will refuse to build the program. Immutability is not a warning in V – it is a hard rule.
Primitive Types and Type Inference
V infers a default type for every literal: integers become int (a 32-bit signed integer), floating-point literals become f64, string literals become string, and true/false become bool. You can override the default with an explicit type annotation or a type conversion.
Create a file named types.v:
| |
V offers a full set of sized numeric types: i8, i16, int (32-bit), i64, u8, u16, u32, u64, plus f32 and f64. Underscores in literals (9_000_000_000) are purely cosmetic and improve readability.
Type Conversions
V does not perform implicit numeric conversions. Mixing an int and an f64 directly is a compile-time error – you must convert one of the operands. Conversions look like a function call: int(x), f64(x), i64(x), and so on.
Create a file named conversions.v:
| |
The .int() method on a string parses it (returning 0 if the string is not a valid integer), while .hex() on an integer formats it as a lowercase hexadecimal string. These string methods are part of V’s built-in string and int types, so no import is needed.
Constants
Variables in V are scoped to a function body. To define a value that lives at module scope and is visible to every function in the file, use a const block. Constants must be initialized with a compile-time-known expression.
Create a file named constants.v:
| |
Constants are always immutable. You cannot mark them mut, and there is no way to reassign them at runtime. Their type is inferred from the initializer just like a regular := binding.
Optional Values Instead of Null
V has no null, no nil, and no undefined. When a value may legitimately be missing, the type is wrapped with ? and the caller uses an or block to handle the absent case. This forces you to deal with missing values at the type-system level rather than with a runtime exception.
Create a file named optionals.v:
| |
The function’s return type is ?string, meaning “a string or nothing.” Returning none signals absence, and the or { ... } block runs only when the result is none. There is no way to forget to handle the empty case – the compiler will not let you assign ?string to a string directly.
Running with Docker
Each example is an independent program. Pull the V image once and run any of the files above.
| |
Expected Output
Running variables.v:
name = Ada
age = 36
height = 1.7
score = 15
Running types.v:
count is int
ratio is f64
label is string
ready is bool
big_number is i64
small_byte is u8
precise is f32
Running conversions.v:
average = 3.5
rounded = 3
port = 8080
hex of 255 = ff
Running constants.v:
app: codearchaeology
max_retries: 5
area(2.0) = 12.56636
Running optionals.v:
id 1 -> Alice
id 99 -> unknown
Key Concepts
- Immutable by default:
name := 'Ada'creates an immutable binding. Usemut name := ...and reassign with=only when mutation is intended. - Strong static typing with inference: Every variable has a fixed compile-time type, but you almost never write the type yourself – V infers it from the initializer.
- No implicit numeric conversions: Mixing
intandf64is a compile-time error. Convert explicitly withf64(x)orint(x). - Sized numeric types: V offers
i8,i16,int,i64,u8…u64,f32, andf64. The default for an integer literal isint; for a float literal it isf64. constfor module-level values: Constants live outside any function and must be initialized with a compile-time expression.- No null, ever: Optional values use
?Typeandnone. The caller must handle the empty case with anorblock, which makes missing values impossible to forget. - String interpolation with
${}: Any expression – including method calls liketypeof(x).name– can be embedded directly in a single-quoted string. - No unused variables: V’s compiler rejects programs that declare a variable and never read it, helping keep code intentional.
Running Today
All examples can be run using Docker:
docker pull thevlang/vlang:alpine
Comments
Loading comments...
Leave a Comment