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.

  1. struct Foo {
  2. mut:
  3. x int
  4. }
  5. fa := Foo{1}
  6. mut a := fa
  7. a.x = 2
  8. assert fa.x == 1
  9. assert a.x == 2
  10. // fb := Foo{ 1 }
  11. // mut b := &fb // error: `fb` is immutable, cannot have a mutable reference to it
  12. // b.x = 2
  13. mut fc := Foo{1}
  14. mut c := &fc
  15. c.x = 2
  16. assert fc.x == 2
  17. assert c.x == 2
  18. println(fc) // Foo{ x: 2 }
  19. println(c) // &Foo{ x: 2 } // Note `&` prefixed.

see also Stack and Heap

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. // failcompile
  2. _ = 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. p = Point{
  10. x: 30
  11. y: 4
  12. }
  13. assert p.y == 4
  14. //
  15. // array: first element defines type of array
  16. points := [Point{10, 20}, Point{20, 30}, Point{40, 50}]
  17. println(points) // [Point{x: 10, y: 20}, Point{x: 20, y: 30}, Point{x: 40,y: 50}]

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

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

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

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

NB: the [params] tag is used to tell V, that the trailing struct parameter can be omitted entirely, so that you can write button := new_button(). Without it, you have to specify at least one of the field names, even if it has its default value, otherwise the compiler will produce this error message, when you call the function with no parameters: error: expected 1 arguments, but got 0.

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. // (not recommended to use, that's why the 'global' keyword starts with __)
  12. f int // public and mutable both inside and outside parent module
  13. }

Private fields are available only inside the same module, any attempt to directly access them from another module will cause an error during compilation. Public immutable fields are readonly everywhere.

Methods

  1. struct User {
  2. age int
  3. }
  4. fn (u User) can_register() bool {
  5. return u.age > 16
  6. }
  7. user := User{
  8. age: 10
  9. }
  10. println(user.can_register()) // "false"
  11. user2 := User{
  12. age: 20
  13. }
  14. 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. Methods must be in the same module as the receiver type.

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.

Embedded structs

V support embedded structs .

  1. struct Size {
  2. mut:
  3. width int
  4. height int
  5. }
  6. fn (s &Size) area() int {
  7. return s.width * s.height
  8. }
  9. struct Button {
  10. Size
  11. title string
  12. }

With embedding, the struct Button will automatically have get all the fields and methods from the struct Size, which allows you to do:

  1. // oksyntax
  2. mut button := Button{
  3. title: 'Click me'
  4. height: 2
  5. }
  6. button.width = 3
  7. assert button.area() == 6
  8. assert button.Size.area() == 6
  9. print(button)

output :

  1. Button{
  2. Size: Size{
  3. width: 3
  4. height: 2
  5. }
  6. title: 'Click me'
  7. }

Unlike inheritance, you cannot type cast between structs and embedded structs (the embedding struct can also has its own fields, and it can also embed multiple structs).

If you need to access embedded structs directly, use an explicit reference like button.Size.

Conceptually, embedded structs are similar to mixins in OOP, NOT base classes.

You can also initialize an embedded struct:

  1. // oksyntax
  2. mut button := Button{
  3. Size: Size{
  4. width: 3
  5. height: 2
  6. }
  7. }

or assign values:

  1. // oksyntax
  2. button.Size = Size{
  3. width: 4
  4. height: 5
  5. }

If multiple embedded structs have methods or fields with the same name, or if methods or fields with the same name are defined in the struct, you can call methods or assign to variables in the embedded struct like button.Size.area(). When you do not specify the embedded struct name, the method of the outermost struct will be targeted.