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.
// 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));
}
}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 });
}
}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.