Structs

  1. struct Point {
  2. x int
  3. y int
  4. }
  5. mut p := Point{
  6. x: 10
  7. y: 20
  8. }
  9. println(p.x) // Struct fields are accessed using a dot
  10. // Alternative literal syntax for structs with 3 fields or fewer
  11. p = Point{10, 20}
  12. assert p.x == 10

Heap structs

Structs are allocated on the stack. To allocate a struct on the heap and get a reference to it, use the & prefix:

  1. struct Point {
  2. x int
  3. y int
  4. }
  5. p := &Point{10, 10}
  6. // References have the same syntax for accessing fields
  7. println(p.x)

The type of p is &Point. It’s a reference to Point. References are similar to Go pointers and C++ references.

Embedded structs

V doesn’t allow subclassing, but it supports embedded structs:

  1. struct Widget {
  2. mut:
  3. x int
  4. y int
  5. }
  6. struct Button {
  7. Widget
  8. title string
  9. }
  10. mut button := Button{
  11. title: 'Click me'
  12. }
  13. button.x = 3

Without embedding we’d have to name the Widget field and do:

  1. button.widget.x = 3

Default field values

  1. struct Foo {
  2. n int // n is 0 by default
  3. s string // s is '' by default
  4. a []int // a is `[]int{}` by default
  5. pos int = -1 // custom default value
  6. }

All struct fields are zeroed by default during the creation of the struct. Array and map fields are allocated.

It’s also possible to define custom default values.

Required fields

  1. struct Foo {
  2. n int [required]
  3. }

You can mark a struct field with the [required] attribute, to tell V that that field must be initialized when creating an instance of that struct.

This example will not compile, since the field n isn’t explicitly initialized:

  1. _ = Foo{}

Short struct literal syntax

  1. struct Point {
  2. x int
  3. y int
  4. }
  5. mut p := Point{
  6. x: 10
  7. y: 20
  8. }
  9. // you can omit the struct name when it's already known
  10. p = {
  11. x: 30
  12. y: 4
  13. }
  14. assert p.y == 4

Omitting the struct name also works for returning a struct literal or passing one as a function argument.

Trailing struct literal arguments

V doesn’t have default function arguments or named arguments, for that trailing struct literal syntax can be used instead:

  1. struct ButtonConfig {
  2. text string
  3. is_disabled bool
  4. width int = 70
  5. height int = 20
  6. }
  7. struct Button {
  8. text string
  9. width int
  10. height int
  11. }
  12. fn new_button(c ButtonConfig) &Button {
  13. return &Button{
  14. width: c.width
  15. height: c.height
  16. text: c.text
  17. }
  18. }
  19. button := new_button(text: 'Click me', width: 100)
  20. // the height is unset, so it's the default value
  21. assert button.height == 20

As you can see, both the struct name and braces can be omitted, instead of:

  1. new_button(ButtonConfig{text:'Click me', width:100})

This only works for functions that take a struct for the last argument.

Access modifiers

Struct fields are private and immutable by default (making structs immutable as well). Their access modifiers can be changed with pub and mut. In total, there are 5 possible options:

  1. struct Foo {
  2. a int // private immutable (default)
  3. mut:
  4. b int // private mutable
  5. c int // (you can list multiple fields with the same access modifier)
  6. pub:
  7. d int // public immutable (readonly)
  8. pub mut:
  9. e int // public, but mutable only in parent module
  10. __global:
  11. f int // public and mutable both inside and outside parent module
  12. } // (not recommended to use, that's why the 'global' keyword
  13. // starts with __)

For example, here’s the string type defined in the builtin module:

  1. struct string {
  2. str byteptr
  3. pub:
  4. len int
  5. }

It’s easy to see from this definition that string is an immutable type. The byte pointer with the string data is not accessible outside builtin at all. The len field is public, but immutable:

  1. fn main() {
  2. str := 'hello'
  3. len := str.len // OK
  4. str.len++ // Compilation error
  5. }

This means that defining public readonly fields is very easy in V, no need in getters/setters or properties.

Methods

  1. struct User {
  2. age int
  3. }
  4. fn (u User) can_register() bool {
  5. return u.age > 16
  6. }
  7. user := User{age: 10}
  8. println(user.can_register()) // "false"
  9. user2 := User{age: 20}
  10. println(user2.can_register()) // "true"

V doesn’t have classes, but you can define methods on types. A method is a function with a special receiver argument. The receiver appears in its own argument list between the fn keyword and the method name.

In this example, the can_register method has a receiver of type User named u. The convention is not to use receiver names like self or this, but a short, preferably one letter long, name.