Structs
Concrete struct names can be a single capital letter, such as M. Functions can return these
structs directly, as results (!M), or as options (?M).
Struct fields can re-use reserved keywords:
Heap structs
Structs are allocated on the stack. To allocate a struct on the heap
and get a reference to it, use the & prefix:
The type of p is &Point. It's a reference to Point.
References are similar to Go pointers and C++ references.
see also Stack and Heap
Default field values
All struct fields are zeroed by default during the creation of the struct. Array and map fields are allocated. In case of reference value, see here.
It's also possible to define custom default values.
Required fields
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:
Short struct literal syntax
Here "short" means omitting the field names and relying on the struct field
order, so Point{10, 20} is a shorter form of Point{x: 10, y: 20}.
For imported struct types, both forms report an unknown type using its qualified name and the same suggestions. Dots in field values do not affect which type name is highlighted.
When the expected type is already known, V can also omit the struct name entirely
when returning a struct literal or passing one as a function argument. That is a
separate shorthand from the positional Point{10, 20} syntax above.
Struct update syntax
V makes it easy to return a modified version of an object:
Trailing struct literal arguments
V doesn't have default function arguments or named arguments, for that trailing struct literal syntax can be used instead:
As you can see, both the struct name and braces can be omitted, instead of:
This only works for functions that take a struct for the last argument.
[!NOTE] Note the
[params]tag is used to tell V, that the trailing struct parameter can be omitted entirely, so that you can writebutton := 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:
Access modifiers can also be written before individual fields to preserve their declaration order:
An inline pub, mut, or pub mut applies only to that field (or comma-separated field group).
It overrides the current section for that declaration; following fields still use the section's
modifiers. Without a section, following fields remain private and immutable.
v fmt preserves inline modifiers and aligns their field types.
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. A public function can return a value of a private struct type. The caller can read its public fields without naming the private type; its private fields remain inaccessible.
Anonymous structs
V supports anonymous structs: structs that don't have to be declared separately with a struct name.
Static type methods
V now supports static type methods like User.new(). These are defined on a struct via
fn [Type name].[function name] and allow to organize all functions related to a struct:
This is an alternative to factory functions like fn new_user() User {} and should be used
instead.
Static type methods can also be used as function values by omitting the call parentheses,
such as make_user := User.new. A field selector rooted in a local variable, constant, or
global reads that value's field; it does not name a static type method.
A static type method and an instance method can share a name. User.draw(...) calls the
static method, while user.draw(...) calls the instance method. Each method has its own
visibility: a pub fn User.draw(...) is accessible from other modules as
module_name.User.draw(...), even when the instance method draw is private.
Instance methods can also be used as unbound function values, such as f := User.register.
The receiver becomes the first parameter, so a mutable receiver is passed as f(mut user).
Inside a method reflection loop, T.$method likewise creates an unbound function value.
Unbound and reflected method values follow the same method privacy and mut argument rules.
[!NOTE] Note, that these are not constructors, but simple functions. V doesn't have constructors or classes.
[noinit] structs
V supports [noinit] structs, which are structs that cannot be initialised outside the module
they are defined in. They are either meant to be used internally or they can be used externally
through factory functions.
The restriction applies to empty literals and literals with explicit fields, including aliases and concrete generic instances of the struct.
For an example, consider the following source in a directory sample:
Note that new_information is a factory function. Now when we want to use this struct
outside the module:
Methods
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.
This also applies to receivers with any number of pointer levels
and to types imported under another name.
To add methods for an imported type, declare a local type alias and use it as the receiver.
For a generic receiver such as other.Box[T], the alias must also declare its type parameters:
type LocalBox[T] = other.Box[T]. Use LocalBox[T] as the receiver and repeat T in the method's
generic parameter list.
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
Promoted fields from different nested embeds can be initialized together. Their shared parent is initialized once, retaining the explicitly supplied fields and defaults for omitted fields.
V supports embedded structs.
With embedding, the struct Button will automatically get all the fields and methods from
the struct Size, which allows you to do:
output :
Button{
Size: Size{
width: 3
height: 2
}
title: 'Click me'
}
Unlike inheritance, you cannot type cast between structs and embedded structs (the embedding struct can also have 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.
Optional fields keep their optional type when accessed through multiple embedded structs.
You can unwrap them with an if guard, including after an earlier check against none.
Omitted embedded structs retain their declared field defaults, including interface values.
Conceptually, embedded structs are similar to mixins in OOP, NOT base classes.
You can also initialize an embedded struct:
or assign values:
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.