How Sway Handles Storage Read and Write Operations: VM Intrinsics and Type Safety
Sway handles storage read and write operations by compiling high-level std::storage API calls into low-level Fuel VM intrinsics (__state_load_quad, __state_store_quad, __state_clear), with compile-time slot calculation and purity enforcement ensuring type-safe blockchain state management.
Sway, the domain-specific language for the Fuel blockchain maintained by FuelLabs, provides a sophisticated storage model that bridges ergonomic developer experience with bare-metal VM performance. This article examines how Sway handles storage read and write operations, tracing the path from the std::storage library through compile-time analysis to the underlying state manipulation intrinsics.
The High-Level Storage API
In sway-lib-std/src/storage/storage_api.sw, Sway exposes three core functions for state manipulation: read, write, and clear. Each function is annotated with purity attributes (#[storage(read)], #[storage(write)], or #[storage(read, write)]) that declare their side effects to the compiler's type checker.
The write function signature demonstrates this pattern:
#[storage(read, write)]
pub fn write<T>(slot: b256, offset: u64, value: T) { … }
Similarly, read returns Option<T> and clear returns bool, with each generic implementation handling type-size calculations via __size_of::<T>() before invoking VM primitives.
Slot Calculation and Memory Layout
Before any VM interaction, Sway calculates precise storage locations through the private slot_calculator<T> function in storage_api.sw. This function determines three critical values for any generic type T:
- Starting slot (
offset_slot): The baseb256address where the value begins - Number of slots (
number_of_slots): How many 32-byte quads the type occupies - Word index (
place_in_slot): The specific position within the starting slot
The calculator uses alignment rules and __size_of::<T>() to guarantee correct packing, returning a tuple (b256, u64, u64) that the compiler uses to generate the correct intrinsic arguments.
Low-Level VM Intrinsics
The Sway compiler translates high-level storage calls into three Fuel VM intrinsics defined in sway-core/src/language/ty/expression/intrinsic_function.rs:
__state_store_quad(slot, ptr, count): Writescount32-byte quads from memory pointerptrinto the specified storageslot__state_load_quad(slot, ptr, count): Loadscountquads into memory, returning a boolean success indicator__state_clear(slot, count): Clears the specified number of slots from state
These intrinsics represent the actual boundary where Sway code manipulates persistent blockchain state, operating on raw memory pointers and quad-word (32-byte) alignment.
The StorageKey Abstraction
For ergonomic state management, sway-lib-std/src/storage/storage_key.sw provides the StorageKey<T> struct. This wrapper bundles a slot, offset, and field identifier (used for zero-size types) into a single type-safe handle.
StorageKey<T> exposes methods that forward to the low-level API while preserving purity attributes:
#[storage(read)]
pub fn read(self) -> T {
read::<T>(self.slot(), self.offset()).unwrap()
}
The struct also provides try_read(), write(), and clear() methods, offering a convenient object-oriented interface over the procedural intrinsics.
Compile-Time Safety and Purity Enforcement
Sway's type checker enforces storage safety through the #[storage(...)] attribute system analyzed in sway-core/src/language/ty/expression/storage.rs. These attributes function as effects that constrain function composition:
- A function marked
#[storage(read)]cannot modify state - A
purefunction cannot call any storage operation - The compiler validates these constraints during semantic analysis, preventing accidental state pollution
This system ensures that read-only operations remain pure and that storage writes are explicitly declared, making side effects visible in function signatures.
Zero-Size Type Handling
Sway optimizes storage operations for zero-size types (ZSTs) such as empty structs. When __size_of::<T>() returns zero:
readandwriteoperations become no-ops, avoiding unnecessary VM callsclearoperations delegate to specialized logic that clears length fields of container types likeStorageVecby invokingclear::<u64>(self.field_id(), 0)
This optimization prevents wasted gas on storage operations that have no material effect on state.
Practical Code Examples
Basic Read and Write Operations
Direct usage of the storage API requires specifying the slot and offset explicitly:
use std::storage::storage_api::{read, write};
fn demo() {
// Store a u64 value at slot 0, word offset 2
write(b256::zero(), 2, 42_u64);
// Retrieve it – the call returns Option<u64>
let stored = read::<u64>(b256::zero(), 2).unwrap();
assert(stored == 42_u64);
}
Execution flow: write → slot_calculator → __state_store_quad; read → slot_calculator → __state_load_quad.
Using StorageKey
The StorageKey abstraction simplifies slot management:
use std::storage::storage_key::StorageKey;
use std::hash::sha256;
fn demo_key() {
// Create a key for a u64 located at slot 0, offset 1
let key = StorageKey::<u64>::new(b256::zero(), 1, sha256(b256::zero()));
// Write a value
key.write(7_u64);
// Read it back (panic if absent)
let v = key.read();
assert(v == 7_u64);
}
Clearing Storage Entries
To remove data from persistent storage:
use std::storage::storage_api::clear;
fn demo_clear() {
// Assume a u128 was stored at slot X, offset 0
let cleared = clear::<u128>(b256::zero(), 0);
assert(cleared); // true if the slots existed
}
Summary
- Sway provides a high-level storage API in
sway-lib-std/src/storage/storage_api.swwithread,write, andclearfunctions that operate on generic types. - Slot calculation happens at compile time via
slot_calculator<T>, determining exact memory layouts using__size_of::<T>()and alignment rules. - The compiler lowers storage operations to VM intrinsics (
__state_store_quad,__state_load_quad,__state_clear) that manipulate 32-byte quads directly. StorageKey<T>offers a type-safe wrapper around raw slot/offset pairs, handling zero-size types and providing ergonomic methods.- Purity attributes (
#[storage(read)],#[storage(write)]) enforce compile-time guarantees about side effects, preventing unauthorized state modification.
Frequently Asked Questions
What is the difference between read() and try_read() in Sway storage operations?
In sway-lib-std/src/storage/storage_key.sw, read() returns the stored value T directly and panics if the slot is empty, while try_read() returns Option<T> to allow graceful handling of unset storage slots. Use read() when you expect the value to exist, and try_read() when you need to check for presence before processing.
How does Sway calculate storage slots for complex nested types?
Sway uses the slot_calculator<T> function in storage_api.sw to compute three values: the starting b256 slot, the number of 32-byte quads occupied (number_of_slots), and the specific word index within the slot (place_in_slot). The calculator applies alignment rules based on __size_of::<T>(), ensuring that complex structs and arrays pack correctly without overlapping adjacent storage.
Can I mix StorageKey<T> with direct storage_api calls in the same contract?
Yes. According to the source code in sway-lib-std, StorageKey<T> methods are thin wrappers that invoke the same underlying functions (read, write, clear) from storage_api.sw. Both approaches compile to identical VM intrinsics, so you can use StorageKey for ergonomic field access while using direct API calls for low-level optimization or batch operations without performance penalty.
How does Sway prevent storage operations in pure functions?
The Sway compiler enforces purity through the #[storage(...)] attribute system analyzed in sway-core/src/language/ty/expression/storage.rs. Functions marked pure cannot call any function with #[storage(read)] or #[storage(write)] attributes. The type checker validates these constraints during semantic analysis, ensuring that storage side effects are explicitly declared and cannot leak into computational contexts that should be deterministic and stateless.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →