Struct Types
Structs group related fields into a named type. The compiler handles storage slot packing and region-aware layout.
Declaration
struct Point {
x: u32;
y: u32;
}
struct User {
name: string;
age: u8;
balance: u256;
location: Point;
}
Storage Structs
Storage structs are packed into 32-byte EVM storage slots. Small fields that fit within a single slot are packed together automatically.
contract TokenContract {
storage var user_data: User;
storage var origin: Point;
pub fn init() {
user_data = User {
name: "Alice",
age: 25,
balance: 1000000,
location: Point { x: 100, y: 200 }
};
}
}
Packing Strategy
Fields are grouped by size to minimize slot usage:
struct OptimizedData {
// Small fields packed together (1 storage slot)
active: bool; // 1 byte
level: u8; // 1 byte
flags: u16; // 2 bytes
counter: u32; // 4 bytes
// Large fields get dedicated slots
balance: u256; // 32 bytes (1 slot)
metadata: string; // Dynamic size
}
Nested Structs
Structs can contain other struct types:
struct Address {
street: string;
city: string;
postal_code: u32;
}
struct Employee {
id: u256;
name: string;
home_address: Address;
work_address: Address;
}
contract Company {
storage var employees: map<u256, Employee>;
pub fn add_employee(id: u256, name: string) {
employees[id] = Employee {
id: id,
name: name,
home_address: Address {
street: "",
city: "",
postal_code: 0
},
work_address: Address {
street: "Main St",
city: "Tech City",
postal_code: 12345
}
};
}
}
Field Access
Dot notation for reading and writing fields:
pub fn update_user_balance(new_balance: u256) {
user_data.balance = new_balance;
// Nested field access
user_data.location.x = user_data.location.x + 10;
// Conditional logic with struct fields
if (user_data.age >= 18) {
user_data.balance = user_data.balance * 2;
}
}
pub fn get_user_location() -> (u32, u32) {
return (user_data.location.x, user_data.location.y);
}
Structs with Verification
Struct fields can appear in specification clauses:
struct BankAccount {
balance: u256;
frozen: bool;
owner: address;
}
contract Bank {
storage var account: BankAccount;
pub fn withdraw(amount: u256)
requires !account.frozen && account.balance >= amount
ensures account.balance == old(account.balance) - amount
{
account.balance = account.balance - amount;
}
pub fn freeze_account()
requires account.owner == std.msg.sender()
ensures account.frozen == true
{
account.frozen = true;
}
}
Relationship to Bitfields
- Structs pack at byte granularity across one or more storage slots.
- Bitfields pack at bit granularity within a single word.
A struct field can itself be a bitfield for maximum density:
packed struct Position {
owner: address, // 20 bytes
flags: PositionFlags, // 1 byte (bitfield over u8)
token_id: u32, // 4 bytes
}
See Bitfield Types for the bitfield system.