Learn about specifiers and attributes, and how to apply additional semantics and behavior to your Verse code.
Specifiers in Verse describe behavior related to semantics, and can add specifiers to identifiers and certain keywords. Specifier syntax uses angle brackets (< and >) with the keyword in between. For example, in IsPuzzleSolved()<decides><transacts>:void, "decides" and "transacts" are specifiers.
Attributes in Verse describe behavior that is used outside of the Verse language (unlike specifiers, which describe Verse semantics). Attribute syntax uses the at sign (@) followed by the keyword, for example @editable.
The following sections describe all of the specifiers and attributes available in Verse and when you can use them.
Effect Specifiers
Effects in Verse indicate categories of behavior that a function can exhibit. You can add effect specifiers to:
After the () in a function definition: name()<specifier>:type = codeblock.
The class keyword: name := class<specifier>(){}.
Specifier
Description
Example
no_rollback
This is the default effect when no exclusive effect is specified. The no_rollback effect indicates that any actions performed by the function cannot be undone, as a result, the function cannot be used in a failure context. This effect cannot be manually specified.
name():type = codeblock
transacts
This effect indicates that any actions performed by the function can be rolled back. The <transacts> effect is required any time a mutable variable (var) is written. The compiler will notify you when you compile your code if the transacts effect was added to a function that can’t be rolled back. Note that this check is not done for functions with the native specifier. The <transacts> effect implies the effects <allocates><reads><writes>, meaning it cannot be combined with these specifiers.
name()<transacts> : type = codeblock
# not allowed because transacts implies reads
name()<transacts><reads>: type = codeblock
# not allowed because transacts implies writes
name()<transacts><writes>: type = codeblock
# not allowed because transacts implies allocates
name()<transacts><allocates>: type = codeblock
computes
This effect requires that the function has no side effects, and is not guaranteed to complete. There’s an unchecked requirement that the function, when provided with the same arguments, produces the same result. Any function that doesn’t have the <native> specifier that would otherwise have the converges effect is a good example of using the <computes> effect.
name()<computes> : type = codeblock
converges
This effect guarantees that not only is there no side effect from the execution of the related function, but that the function completes (does not infinitely recurse). This effect can only appear in functions that have the native specifier, but this isn’t checked by the compiler. Code that provides default values for class fields or values for global variables is required to have this effect.
name()<converges> : type = codeblock
decides
This effect indicates that the function can fail and that calling this function is a failable expression. Because a <decides> function can fail, it is mutually exclusive with the <suspends> effect. It can be useful to combine <decides> with the <transacts> or <computes> effect, which allows the actions performed by this function to be rolled back (as if the actions were never performed) if there’s a failure anywhere in the function.
# allowed
name()<decides><transacts> : type = codeblock
# allowed
name()<decides><computes> : type = codeblock
# not allowed because decides and suspends are mutually exclusive
name()<decides><suspends> : type = codeblock
suspends
This effect indicates that the function is async. Creates an async context for the body of the function. Mutually exclusive with the <decides> effect.
name()<suspends> : type = codeblock
# not allowed because decides and suspends are mutually exclusive
name()<decides><suspends> : type = codeblock
reads
This effect indicates that the same inputs to the function may not always produce the same output. The behavior depends on factors external to the specified inputs, such as memory or the containing package version.
name()<reads> : type = codeblock
writes
This effect indicates that the function may change values in memory
name()<writes> : type = codeblock
allocates
This effect indicates that the function may instantiate an object in memory. Allocating <unique> classes requires <allocates>.
name()<allocates> : type = codeblock
In all cases, calling a function that has a specific effect will require the caller to have that effect as well.
Access Specifiers
Access specifiers define what can interact with a member and how. You can apply access specifiers to the following:
The identifier for a member: name<specifier> : type = value
The keyword var for a member: var<specifier> name : type = value
You can have an access specifier on both the identifier and the var keyword for a variable, to differentiate between who has access to read and write the variable. For example, the following variable MyInteger has the public specifier on the identifier so anyone can read the value, but the var keyword has the protected specifier so only the current class and subtypes can write to the variable.
var<protected> MyInteger<public>:int = 2
Specifier
Description
Usage
Example
public
The identifier is universally accessible.
You can use this specifier on:
module
class
interface
struct
enum
method
data
name<public> : type = value
protected
The identifier can only be accessed by the current class and any subtypes.
You can use this specifier on:
class
interface
struct
functions within a class
enum
non-module method
data
name<protected> : type = value
private
The identifier can only be accessed in the current, immediately enclosing, scope (be it a module, class, struct, etc.).
You can use this specifier on:
class
interface
struct
functions within a class
enum
non-module method
data
name<private> : type = value
internal
The identifier can only be accessed in the current immediately enclosing, module. This is the default access level.
You can use this specifier on:
module
class
interface
struct
enum
method
data
name<internal> : type = value
scoped
The identifier can only be accessed in the current scope and any enclosing scopes. Any assets you expose to Verse that appear in the Assets.digest.Verse file will have the <scoped> specifier.
You can use this specifier on:
module
class
interface
functions
struct
enum
non-module method
data
# Enclosing scope for ModuleB and ModuleC.
ModuleA<public> := module:
ModuleB<public> := module:
# Internal to ModuleB.
class_b1 := class{}
# Allows access from anywhere inside ModuleA.
class_b2<scoped{ModuleA}> := class {}
# Allows access only inside ModuleB.
class_b3<scoped{ModuleB}> := class {}
ModuleC<public> := module:
# Not allowed because class_b1 is internal to ModuleB
class_c1 := class(ModuleB.class_b1) {}
# Allowed because class_b2 is scoped to ModuleA which encloses ModuleB and ModuleC.
class_c2 := class(ModuleB.class_b2) {}
# Not allowed because class_b3 is scoped to only ModuleB which does not enclose ModuleC.
class_c3 := class(ModuleB.class_b3) {}
Class Specifiers
Class specifiers define certain characteristics of classes or their members, such as whether you can create a subclass of a class.
Specifier
Description
Example
abstract
When a class or a class method has the abstract specifier, you cannot create an instance of the class. Abstract classes are intended to be used as a superclass with partial implementation or as a common interface. This is useful when it doesn't make sense to have instances of a superclass but you don't want to duplicate properties and behaviors across similar classes.
Indicates that this type is dynamically castable. The <castable> specifier has a backward compatibility restriction on its use. Once a class or interface is published, the <castable> attribute can be neither added nor removed. Doing so can introduce unsafe casting behaviors, so this is disallowed.
The castable_subtype type functions very similarly to subtype, but requires that any types used with it are also marked <castable>. This increases code safety in places where dynamic casts are used.
my_base := class {}
my_castable_type := class<castable>(my_base) {}
my_child_type := class(my_castable_type) {}
MySubtypeFunction(t:castable_subtype(my_base)):void=
return
Main()<decides>:void =
Value:my_castable_type = my_child_type{}
if (my_child_type[Value]):
# Success
# Will produce an error because my_base is not castable.
MySubtypeFunction(my_base)
# Allowed because my_child_type inherits <castable> from my_castable_type.
MySubtypeFunction(my_child_type)
concrete
When a class has the concrete specifier, you can construct an instance of the class with an empty archetype, which means that every field of the class must have a default value. Every subclass of a concrete class is implicitly concrete. A concrete class can only inherit directly from an abstract class if both classes are defined in the same module.
cat := class<concrete>():
# field must be initialized because the class is concrete
Name : string = "Cat"
unique
A unique class in Verse is assigned a unique identity for each instance. This means that, even if two instances of the same unique class have the same field values, they are not equal since they are distinct instances. This allows instances of unique classes to be compared for equality by comparing their identities. Classes without the unique specifier don't have any such identity, and so can only be compared for equality based on the values of their fields. This means that unique classes can be compared with the = and <> operators, and are subtypes of the comparable type.
unique_class := class<unique>:
Field : int
Main()<decides> : void =
X := unique_class{Field := 1}
X = X # X is equal to itself
Y := unique_class{Field := 1}
X <> Y # X and Y are unique and therefore not equal
final
You can only use the final specifier on classes and members of classes, with the following restrictions:
When a class has the final specifier, you cannot create a subclass of the class.
When a field has the final specifier, you cannot override the field in a subclass.
When a method has the final specifier, you cannot override the method in a subclass.
cat := class<final>():
final_super
The final_super specifier is only applicable to class definitions and requires that the class definition derives from a parent class or interface. This specifier imposes a future compatibility constraint that the given class will always derive from its parent directly; for this and all future published versions of this class definition.
This is necessary in Scene Graph for immediate subtypes of component to limit the number of instances to exactly zero or one per Scene Graph entity. This limit extends to subtypes of those types as well.
component := class {}
my_final_class := class<final_super>(component) {}
# Not allowed since my_final_class has the final_super specifier.
my_subclass_type := class(my_final_class) {}
Persistence Specifier
When a custom type, such as a class, has the persistable specifier, it means that you can use it in your module-scoped weak_map variables and have its values persist across game sessions. For more details on persistence in Verse, check out Using Persistable Data in Verse.
You can use the persistable specifier with the following types. Follow the links for more details.
It's not possible to use implementation specifiers when writing code, but you will see them when looking at the UEFN APIs.
Specifier
Description
Example
native
Indicates that the definition details of the element are implemented in C++. Verse definitions with the native specifier auto-generate C++ definitions. A Verse developer can then fill out its implementation. You can see this specifier used on:
Indicates that an instance method is both native (implemented in C++) and may be called by other C++ code. You can see this specifier used on an instance method. This specifier doesn’t propagate to subclasses and so you don’t need to add it to a definition when overriding a method that has this specifier
Attributes in Verse describe behavior that is used outside of the Verse language (unlike specifiers, which describe Verse semantics). Attributes can be added on the line of code before definitions.
Attribute syntax uses the at sign (@) followed by the keyword.
Attribute
Description
Example
editable
Indicates this field is an exposed property that can be changed directly from UEFN so you don't need to modify the Verse code to change its value. For more details, see Customize Device Properties.
An editable string that displays as a text box in the editor. Editable text boxes currently do not support tooltips or categories. For more details, see Customize Device Properties.
# An editable string that displays as a text box in the editor.
# Editable text boxes currently do not support tooltips or categories.
@editable_text_box:
# Whether this text can span multiple lines.
MultiLine := true
# The maximum amount of characters this text block can display.
MaxLength := 32
MessageBox:string = "This is a short message!"
editable_slider
An editable slider that uses the float type. You can drag the slider in the editor to increase or decrease the value. For more details, see Customize Device Properties.
# An editable slider that uses the float type. You can drag the slider in the editor to increase
# or decrease the value.
@editable_slider(float):
# The categories this editable belongs to.
Categories := array{FloatsCategory}
# The tool tip for this editable.
ToolTip := SliderTip
# The minimum value of each component. You cannot set an editable value for this number lower
# than the MinComponentValue.
MinValue := option{0.0}
# The maximum value of each component. You cannot set an editable value for this number higher
# than the MaxComponentValue.
MaxValue := option{10.0}
# The amount the slider value increases or decreases per delta.
SliderDelta := option{1.0}
# The amount to scale the slider delta by.
SliderExponent := option{1.0}
# The sensitivity of the mouse movement required to cause a delta increase.
MouseLinearDeltaSensitivity := 0.25
# The amount of pixels required to move the mouse to cause a delta increase.
MouseShiftMovePixelPerDelta := 0.25
FloatSlider:float = 1.0
# An editable number with minimum and maximum
@editable_number(int):
# The tool tip for this editable.
ToolTip := EditableIntTip
# The category this editable belongs to.
Categories := array{IntsCategory}
# The minimum value of each component. You cannot set an editable value for this number lower
# than the MinComponentValue.
MinValue := option{0}
# The maximum value of each component. You cannot set an editable value for this number higher
# than the MaxComponentValue.
MaxValue := option{10}
# Snap the spinbox to the nearest delta
SpinBoxDelta:=option{2}
MinAndMaxInt:int = 1
editable_vector_slider
An editable vector slider. You can drag to change the values of each of the vector components. For more details, see Customize Device Properties.
# An editable vector slider. You can drag to change the values of each of the vector components.
@editable_vector_slider(float):
# The tool tip for this editable.
ToolTip := VectorSliderTip
# The categories this editable belongs to.
Categories := array{FloatsCategory}
# Shows the option to preserve the ratio between vector values in the editor.
ShowPreserveRatio := true
# Shows the option to normalize the vector in the editor.
ShowNormalize := true
# The minimum value of each component. You cannot set an editable value for this number lower
# than the MinComponentValue.
MinComponentValue := option{0.0}
# The maximum value of each component. You cannot set an editable value for this number higher
# than the MaxComponentValue.
MaxComponentValue := option{10.0}
# The amount the slider value increases or decreases per delta.
SliderDelta := option{0.5}
# Used to scale a slider exponentially. Common values are found within the range of 1-20.
SliderExponent := option{1.0}
# The sensitivity of the mouse movement required to cause a delta increase.
MouseLinearDeltaSensitivity := 0.25
# The amount of pixels required to move the mouse to cause a delta increase.
MouseShiftMovePixelPerDelta := 0.25
FloatVectorSlider:vector3 = vector3{X := 1.0, Y := 2.0, Z := 3.0}
editable_vector_number
An editable vector number, which can be a vector2, vector2i, or vector3. For more details, see Customize Device Properties.
# An editable vector number, which can be a vector2, vector2i, or vector3.
@editable_vector_number(float):
# The categories this editable belongs to.
Categories := array{FloatsCategory}
# The tool tip for this editable.
ToolTip := VectorFloatTip
# Shows the option to preserve the ratio between vector values in the editor.
ShowPreserveRatio := true
# Shows the option to normalize the vector in the editor.
ShowNormalize := true
# The minimum value of each component. You cannot set an editable value for this number lower
# than the MinComponentValue.
MinComponentValue := option{0.0}
# The maximum value of each component. ou cannot set an editable value for this number higher
# than the MaxComponentValue.
MaxComponentValue := option{2.0}
# Snap the spinbox to the nearest delta.
SpinBoxDelta := option{2.0}
FloatVector:vector2 = vector2{X := 1.0, Y := 2.0}
editable_container
An editable container of values. Currently, this only supports arrays. For more details, see Customize Device Properties.
An editable container of values. Currently, this only supports arrays.
@editable_container:
# The category this editable belongs to.
Categories := array{IntsCategory}
# The tool tip for this editable.
ToolTip := IntArrayTip
# Whether dragging elements to reorder this container is allowed.
AllowReordering := false
IntArray:[]int = array{}