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 method signature signals helps the compiler enforce the immutability constraints. Mutating methods can only be called on mutable instances.
struct Person { name: Str, age: Int,}
impl Person { fn mut grow_older() { self.age =+ 1 }}
mut bob = Person{name: "Bob", age: 30}bob.grow_older() // Ok
let alice = Person{name: "Alice", age: 30}// alice.grow_older() would be an error: cannot mutate immutable 'alice'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")