Functions 2

Immutable function args by default

In V function arguments are immutable by default, and mutable args have to be marked on call.

Since there are also no globals, that means that the return values of the functions, are a function of their arguments only, and their evaluation has no side effects (unless the function uses I/O).

Function arguments are immutable by default, even when references are passed.

Note that V is not a purely functional language however.

There is a compiler flag to enable global variables (-enable-globals), but this is intended for low-level applications like kernels and drivers.

Mutable arguments

It is possible to modify function arguments by declaring them with the keyword mut:

  1. struct User {
  2. name string
  3. mut:
  4. is_registered bool
  5. }
  6. fn (mut u User) register() {
  7. u.is_registered = true
  8. }
  9. mut user := User{}
  10. println(user.is_registered) // "false"
  11. user.register()
  12. println(user.is_registered) // "true"

In this example, the receiver (which is just the first argument) is explicitly marked as mutable, so register() can change the user object. The same works with non-receiver arguments:

  1. fn multiply_by_2(mut arr []int) {
  2. for i in 0 .. arr.len {
  3. arr[i] *= 2
  4. }
  5. }
  6. mut nums := [1, 2, 3]
  7. multiply_by_2(mut nums)
  8. println(nums)
  9. // "[2, 4, 6]"

Note, that you have to add mut before nums when calling this function. This makes it clear that the function being called will modify the value.

It is preferable to return values instead of modifying arguments, e.g. user = register(user) (or user.register()) instead of register(mut user). Modifying arguments should only be done in performance-critical parts of your application to reduce allocations and copying.

For this reason V doesn’t allow the modification of arguments with primitive types (e.g. integers). Only more complex types such as arrays and maps may be modified.

Struct update syntax

V makes it easy to return a modified version of an object:

  1. struct User {
  2. name string
  3. age int
  4. is_registered bool
  5. }
  6. fn register(u User) User {
  7. return User{
  8. ...u
  9. is_registered: true
  10. }
  11. }
  12. mut user := User{
  13. name: 'abc'
  14. age: 23
  15. }
  16. user = register(user)
  17. println(user)

Variable number of arguments

  1. fn sum(a ...int) int {
  2. mut total := 0
  3. for x in a {
  4. total += x
  5. }
  6. return total
  7. }
  8. println(sum()) // 0
  9. println(sum(1)) // 1
  10. println(sum(2, 3)) // 5
  11. // using array decomposition
  12. a := [2, 3, 4]
  13. println(sum(...a)) // <-- using prefix ... here. output: 9
  14. b := [5, 6, 7]
  15. println(sum(...b)) // output: 18

Anonymous & higher order functions

  1. fn sqr(n int) int {
  2. return n * n
  3. }
  4. fn cube(n int) int {
  5. return n * n * n
  6. }
  7. fn run(value int, op fn (int) int) int {
  8. return op(value)
  9. }
  10. fn main() {
  11. // Functions can be passed to other functions
  12. println(run(5, sqr)) // "25"
  13. // Anonymous functions can be declared inside other functions:
  14. double_fn := fn (n int) int {
  15. return n + n
  16. }
  17. println(run(5, double_fn)) // "10"
  18. // Functions can be passed around without assigning them to variables:
  19. res := run(5, fn (n int) int {
  20. return n + n
  21. })
  22. println(res) // "10"
  23. // You can even have an array/map of functions:
  24. fns := [sqr, cube]
  25. println(fns[0](10)) // "100"
  26. fns_map := {
  27. 'sqr': sqr
  28. 'cube': cube
  29. }
  30. println(fns_map['cube'](2)) // "8"
  31. }

Closures

V supports closures too. This means that anonymous functions can inherit variables from the scope they were created in. They must do so explicitly by listing all variables that are inherited.

  1. // oksyntax
  2. my_int := 1
  3. my_closure := fn [my_int] () {
  4. println(my_int)
  5. }
  6. my_closure() // prints 1

Inherited variables are copied when the anonymous function is created. This means that if the original variable is modified after the creation of the function, the modification won’t be reflected in the function.

  1. // oksyntax
  2. mut i := 1
  3. func := fn [i] () int {
  4. return i
  5. }
  6. println(func() == 1) // true
  7. i = 123
  8. println(func() == 1) // still true

However, the variable can be modified inside the anonymous function. The change won’t be reflected outside, but will be in the later function calls.

  1. // oksyntax
  2. fn new_counter() fn () int {
  3. mut i := 0
  4. return fn [mut i] () int {
  5. i++
  6. return i
  7. }
  8. }
  9. c := new_counter()
  10. println(c()) // 1
  11. println(c()) // 2
  12. println(c()) // 3

If you need the value to be modified outside the function, use a reference. Warning: you need to make sure the reference is always valid, otherwise this can result in undefined behavior.

  1. // oksyntax
  2. mut i := 0
  3. mut ref := &i
  4. print_counter := fn [ref] () {
  5. println(*ref)
  6. }
  7. print_counter() // 0
  8. i = 10
  9. print_counter() // 10