Defining and Instantiating Structs
Structs are one of the fundamental ways to create custom types in Oxide. A struct, short for "structure," lets you package together related values under a single name. If you're coming from object-oriented languages, a struct is similar to a class's data attributes.
Defining Structs
To define a struct, use the struct keyword followed by the struct name and curly
braces containing the field definitions. Each field has a name and a type, using
Oxide's camelCase naming convention for field names.
struct User {
active: Bool,
username: String,
email: String,
signInCount: UInt64,
}
Notice that:
- Field names use camelCase (e.g.,
signInCount, notsign_in_count) - Types use Oxide's type aliases (
Bool,UInt64) or standard types (String) - The struct body uses curly braces, just like Rust
Public Structs and Fields
To make a struct accessible from other modules, use the public keyword:
public struct User {
public active: Bool,
public username: String,
email: String, // Private by default
signInCount: UInt64, // Private by default
}
The public keyword replaces Rust's pub for visibility. You can apply it to
both the struct itself and individual fields.
Creating Instances
To create an instance of a struct, specify the struct name followed by curly braces containing the field values:
fn main() {
let user = User {
active: true,
username: "alice123".toString(),
email: "alice@example.com".toString(),
signInCount: 1,
}
}
The order of fields doesn't matter when creating an instance, but all fields must be provided (unless they have default values through other mechanisms).
Mutable Instances
If you need to modify a struct instance after creation, use var instead of let:
fn main() {
var user = User {
active: true,
username: "alice123".toString(),
email: "alice@example.com".toString(),
signInCount: 1,
}
// Now we can modify fields
user.email = "newemail@example.com".toString()
user.signInCount += 1
}
Note that in Oxide, the entire instance must be mutable to change any field. You cannot mark only certain fields as mutable.
Accessing Field Values
Use dot notation to access struct fields:
fn main() {
let user = User {
active: true,
username: "alice123".toString(),
email: "alice@example.com".toString(),
signInCount: 1,
}
println!("Username: \(user.username)")
println!("Email: \(user.email)")
println!("Sign-in count: \(user.signInCount)")
}
Oxide's string interpolation with \(expression) makes it easy to embed field
values directly in output strings.
Field Init Shorthand
When variable names match field names, you can use the shorthand syntax:
fn createUser(username: String, email: String): User {
User {
active: true,
username, // Same as username: username
email, // Same as email: email
signInCount: 1,
}
}
This shorthand reduces repetition when the parameter names match the field names.
Struct Update Syntax
When creating a new instance that reuses most values from an existing instance,
use the struct update syntax with ..:
fn main() {
let user1 = User {
active: true,
username: "alice123".toString(),
email: "alice@example.com".toString(),
signInCount: 1,
}
let user2 = User {
email: "bob@example.com".toString(),
..user1 // Use remaining fields from user1
}
// user2 has bob's email but alice's username, active status, and signInCount
}
The ..user1 must come last in the struct literal. It copies all remaining fields
from the source instance.
Important ownership note: The struct update syntax moves data from the source.
After the update, user1 cannot be used if any of its fields were moved (like
String fields). However, if only copyable types (like Bool or UInt64) are
transferred, the source remains valid.
Tuple Structs
Oxide supports tuple structs, which are structs without named fields. These are useful when you want to give a tuple a distinct type name:
struct Color(Int, Int, Int)
struct Point(Int, Int, Int)
fn main() {
let black = Color(0, 0, 0)
let origin = Point(0, 0, 0)
// Access fields by index
let red = black.0
let green = black.1
let blue = black.2
}
Even though Color and Point have the same field types, they are different
types. A function expecting a Color won't accept a Point.
Unit-Like Structs
You can also define structs with no fields, called unit-like structs:
struct AlwaysEqual
fn main() {
let subject = AlwaysEqual
}
Unit-like structs are useful when you need to implement a trait on a type but don't need to store any data.
Adding Attributes
Structs commonly use derive attributes to automatically implement traits:
#[derive(Debug, Clone, PartialEq)]
public struct User {
active: Bool,
username: String,
email: String,
signInCount: UInt64,
}
The #[derive(...)] attribute automatically generates implementations for common
traits:
Debug: Enables printing with{:?}format specifierClone: Enables creating deep copies with.clone()PartialEq: Enables comparison with==and!=
Complete Example
Here's a complete example showing struct definition and usage:
#[derive(Debug, Clone)]
public struct Rectangle {
width: Int,
height: Int,
}
fn main() {
let rect = Rectangle {
width: 30,
height: 50,
}
println!("Rectangle: {:?}", rect)
println!("Width: \(rect.width)")
println!("Height: \(rect.height)")
// Create a modified copy
let wider = Rectangle {
width: 60,
..rect
}
println!("Wider rectangle: {:?}", wider)
}
Rust Comparison
| Aspect | Rust | Oxide |
|---|---|---|
| Visibility | pub | public |
| Field naming | snake_case | camelCase |
| Struct syntax | struct { } | struct { } (same) |
| Tuple struct | struct Point(i32, i32) | struct Point(Int, Int) |
| Types | i32, bool, u64 | Int, Bool, UInt64 |
The underlying semantics are identical. Oxide structs are Rust structs with different naming conventions and type aliases. The compiled binary is exactly the same as equivalent Rust code.
Summary
Structs let you create custom types that package related data together:
- Use
structwith curly braces for named fields - Use camelCase for field names
- Use
publicfor visibility - Create instances with struct literals
- Use
varfor mutable instances - Derive common traits with
#[derive(...)]
In the next section, we'll build a complete program using structs to see how they work in practice.