# How Sway Handles Storage Read and Write Operations: VM Intrinsics and Type Safety

> Discover how Sway handles storage read and write operations using VM intrinsics and type safety for secure blockchain state management. Learn about compile-time slot calculation and purity enforcement

- Repository: [Fuel Labs/sway](https://github.com/FuelLabs/sway)
- Tags: internals
- Published: 2026-03-05

---

**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:

```sway
#[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 base `b256` address 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`](https://github.com/FuelLabs/sway/blob/main/sway-core/src/language/ty/expression/intrinsic_function.rs):

- **`__state_store_quad(slot, ptr, count)`**: Writes `count` 32-byte quads from memory pointer `ptr` into the specified storage `slot`
- **`__state_load_quad(slot, ptr, count)`**: Loads `count` quads 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<T> 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:

```sway
#[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`](https://github.com/FuelLabs/sway/blob/main/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 `pure` function 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:

- `read` and `write` operations become no-ops, avoiding unnecessary VM calls
- `clear` operations delegate to specialized logic that clears length fields of container types like `StorageVec` by invoking `clear::<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:

```sway
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<T>

The `StorageKey` abstraction simplifies slot management:

```sway
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:

```sway
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.sw` with `read`, `write`, and `clear` functions 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`](https://github.com/FuelLabs/sway/blob/main/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.