Functions in V (Vlang)
Learn how to define and use functions in V (Vlang) - parameters, multiple return values, recursion, closures, higher-order functions, and struct methods with Docker-ready examples
Functions are the primary unit of code organization in V. You have already met one in every program so far – fn main(), the entry point – but V functions go well beyond a place to put statements. They are first-class values you can pass around, return from other functions, and capture into closures.
V’s function design reflects its core philosophy: explicit, simple, and safe. Arguments are immutable by default, just like ordinary variables, so a function cannot quietly mutate the data you hand it unless you opt in with mut. There are no global variables by default, which makes the inputs and outputs of every function obvious from its signature alone. And because V is statically typed, every parameter and return value carries a type the compiler checks for you.
In this tutorial you will learn how to define functions with parameters and return values, return multiple values at once, write recursive functions, treat functions as values (higher-order functions and closures), and attach methods to your own structs. Along the way you’ll see where V deliberately differs from languages like Go and Rust.
Defining and Calling Functions
A function is declared with the fn keyword, followed by its name, a parenthesized parameter list, and an optional return type. Each parameter is written as name type. V also supports returning multiple values as a tuple – a common pattern that avoids out-parameters and special wrapper types.
Create a file named functions.v:
| |
Notice that min and max are declared mut because the loop reassigns them – everything in V is immutable until you say otherwise. The call low, high := min_max(...) unpacks the two returned values into two variables in a single statement.
No default arguments. Unlike Python or C++, V functions do not support default parameter values. The idiomatic alternative is to pass a struct of options (V supports default struct field values), keeping call sites explicit.
Recursion
A function may call itself. V handles recursion naturally, and because there are no hidden control-flow surprises, recursive definitions read exactly like their mathematical counterparts. The classic examples are factorial and the Fibonacci sequence.
Create a file named recursion.v:
| |
The 0 .. 10 range loop runs i from 0 up to (but not including) 10, and series << fib(i) appends each result to the array. String interpolation can hold a full expression, so ${factorial(5)} calls the function inline.
Higher-Order Functions and Closures
In V, functions are first-class values. A function’s type is written fn (ParamTypes) ReturnType, so you can accept functions as parameters, return them, and store them in variables. Closures – functions that capture variables from their surrounding scope – require an explicit capture list in [brackets], keeping V’s “no hidden behavior” promise: you can always see exactly what a closure holds onto.
Create a file named higher_order.v:
| |
The capture list fn [factor] (x int) int is what makes the returned function a closure – it explicitly copies factor into the function so it survives after make_multiplier returns. The array method .map(it * 2) shows that V’s standard library leans on higher-order functions too, with it as the implicit element variable.
Methods on Structs
V has no classes, but you can attach methods to any struct (or other type) by writing a receiver between fn and the method name. A plain receiver is read-only; mark it mut to let the method modify the value it was called on. This is how V models behavior without inheritance.
Create a file named methods.v:
| |
Because increment mutates its receiver, the method uses fn (mut c Counter), the count field is declared under a mut: block, and the variable must itself be declared mut counter. V forces all three to line up, so mutation is never accidental.
Running with Docker
Run each example using the official V image – no local install required:
| |
Expected Output
Running functions.v:
Sum: 7
Product: 12
Hello, V!
Min: 1, Max: 9
Running recursion.v:
5! = 120
10! = 3628800
Fibonacci: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Running higher_order.v:
square(6) = 36
cube of 4 = 64
triple(7) = 21
doubled = [2, 4, 6, 8, 10]
Running methods.v:
Area: 12
Perimeter: 14
Count: 3
Key Concepts
fn name(param type) ReturnType– Functions are declared withfn; every parameter and return value is explicitly typed and checked at compile time.- Multiple return values – Functions can return a tuple like
(int, int), unpacked at the call site withlow, high := ..., avoiding out-parameters and wrapper structs. - Immutable arguments by default – Parameters cannot be modified unless declared
mut, so functions can’t quietly mutate your data. - No default arguments – V omits default parameter values; pass an options struct (struct fields can have defaults) when you need them.
- Recursion is first-class – Functions call themselves naturally; with no hidden control flow, recursive code mirrors its mathematical definition.
- Functions are values – The type
fn (int) intlets you pass functions as arguments, return them, and store them in variables. - Closures capture explicitly – A closure lists captured variables in
[brackets], so what it holds is always visible – consistent with V’s no-hidden-behavior design. - Methods, not classes – Behavior is attached via a receiver
fn (r Type) method(); use amutreceiver to modify the value, which V requires you to opt into at every level.
Running Today
All examples can be run using Docker:
docker pull thevlang/vlang:alpine
Comments
Loading comments...
Leave a Comment