Structs
Structs can be used for custom data types packaging multiple related values, like objects in most object-oriented languages.
Defining Structs
Section titled “Defining Structs”struct Person { name: Str, age: Int, email: Str,}Creating Struct Instances
Section titled “Creating Struct Instances”struct Person { name: Str, age: Int, email: Str,}
let person = Person{ name: "Alice", age: 30, email: "alice@example.com",}Accessing Fields
Section titled “Accessing Fields”Use dot notation to access struct fields:
use go:fmt
struct Person { name: Str, age: Int,}
let person = Person{name: "Alice", age: 30}
let name = person.name // "Alice"let age = person.age // 30fmt::Println("Hello, {person.name}!")Nullable Fields
Section titled “Nullable Fields”Struct fields can be nullable using the ? suffix. Nullable fields can be omitted when creating an instance, in which case they default to none:
struct Config { name: Str, timeout: Int?, retries: Int?,}
// Omit nullable fields — they become nonelet default_config = Config{name: "app"}
// Provide values directly — they are automatically wrappedlet custom = Config{name: "app", timeout: 30, retries: 3}
// You can still use Maybe::new() explicitly if you preferlet explicit = Config{name: "app", timeout: Maybe::new(30)}This is the same implicit wrapping behavior available for nullable function parameters.
Methods
Section titled “Methods”Methods are like normal functions and are only available on instances of a struct.
Use impl blocks to define struct methods.
struct Rectangle { width: Float64, height: Float64}
impl Rectangle { fn area() Float64 { self.width * self.height }
fn perimeter() Float64 { 2.0 * (self.width + self.height) }
fn is_square() Bool { self.width == self.height }}The self Receiver
Section titled “The self Receiver”Within methods, use self to reference the current instance’s fields:
struct Person { name: Str, age: Int,}
impl Person { fn get_intro() Str { "My name is {self.name} and I am {self.age} years old" }
fn is_adult() Bool { self.age >= 18 }}Mutating methods
Section titled “Mutating methods”Because Ard requires explicit data mutation, methods that can change the struct must be marked as mutating, with the mut keyword after fn.
struct Person { name: Str, age: Int,}
impl Person { fn mut grow_older() { self.age =+ 1 }}This signature lets the compiler require an actual mut Person reference receiver. A writable ordinary binding is not enough: mut bob = Person{...} permits replacing bob, but it still stores an ordinary value.
struct Person { name: Str, age: Int,}
impl Person { fn mut grow_older() { self.age =+ 1 }}
let bob = Person{name: "Bob", age: 30}let bob_reference = mut bobbob_reference.grow_older() // OK
mut alice = Person{name: "Alice", age: 30}// alice.grow_older() // Error: alice stores an ordinary Personalice = Person{name: "Alice", age: 31} // OK: replaces alice's slotReference-valued fields
Section titled “Reference-valued fields”A field typed as mut T stores a reference handle. Initialization and rebinding require actual references:
struct Session { user: mut Person,}
let first = Person{name: "Ada", age: 30}let second = Person{name: "Grace", age: 35}let session = mut Session{user: mut first}
session.user.grow_older()session.user = mut secondCopying session.user copies its current reference handle. Rebinding the field changes only that field slot; previously copied references keep their original pointee. Use deref session.user when an ordinary Person field or value is required.
Method Privacy
Section titled “Method Privacy”Methods can be made private with the private keyword:
struct User { username: Str,}
impl User { private fn format_name(name: Str) Str { "User: {name}" }
fn get_display_name() Str { self.format_name(self.username) // Calls private method }}Private methods can only be called from within the same module. Read more about modules.
Static Functions
Section titled “Static Functions”Static functions are functions declared in a struct’s namespace. These functions are distinct from methods because they do not operate on an instance. They are primarily a way to organize code and signal related functionality.
The most common use of static functions is for constructors or factory helpers.
struct Todo { title: Str, completed: Bool,}
// Static constructor functionfn Todo::new(title: Str) Todo { Todo{title: title, completed: false}}
let todo = Todo::new("Learn Ard")