State & storage

Structs & enums

struct maps to a Rust struct; enum becomes a far more powerful Rust enum. Types stored in state or crossing the ABI need serializer derives.

Solidity struct and enum map to Rust’s — except Rust enums can carry data per variant, making them far more expressive. Any custom type that lives in storage or crosses the method boundary needs serializers, which the #[near(...)] macro adds for you.

A small registry
Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract Registry {
    enum Status { Active, Paused, Closed }

    struct Item {
        string name;
        Status status;
    }

    Item[] public items;

    function add(string calldata name) external {
        items.push(Item(name, Status.Active));
    }
}
Rust
use near_sdk::near;

#[near(serializers = [json, borsh])]
#[derive(Clone)]
pub enum Status {
    Active,
    Paused,
    Closed,
}

#[near(serializers = [json, borsh])]
#[derive(Clone)]
pub struct Item {
    name: String,
    status: Status,
}

#[near(contract_state)]
#[derive(Default)]
pub struct Registry {
    items: Vec<Item>,
}

#[near]
impl Registry {
    pub fn add(&mut self, name: String) {
        self.items.push(Item { name, status: Status::Active });
    }
}
Coming from EVM

struct ↔ struct, enum ↔ enum. But a Rust enum variant can hold fields (Closed { reason: String }), so many Solidity "struct + status code" pairs collapse into one enum.

#[near(serializers = [json, borsh])] derives the two encodings a custom type needs: borsh for storage, json for method arguments and returns. The contract state struct uses #[near(contract_state)], which includes them.

The contract struct fields are storage; a Vec<Item> here holds everything in one state entry. For large or independently-accessed sets, reach for the store collections instead.