State & storage

Collections: mappings & arrays

mapping and dynamic arrays become explicit types from near_sdk::store — LookupMap, Vector — each with its own storage prefix.

Solidity gives you mapping and T[] as language built-ins. NEAR gives you collection types from near_sdk::store that you pick deliberately: LookupMap, Vector, IterableMap, UnorderedSet. Each is a thin wrapper over on-chain key/value storage.

A points ledger
Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract Points {
    mapping(address => uint32) public points;
    address[] public members;

    function join() external {
        if (points[msg.sender] == 0) {
            members.push(msg.sender);
        }
    }

    function award(address who, uint32 amount) external {
        points[who] += amount;
    }

    function memberCount() external view returns (uint256) {
        return members.length;
    }
}
Rust
use near_sdk::{near, env, AccountId};
use near_sdk::store::{LookupMap, Vector};

#[near(contract_state)]
pub struct Points {
    points: LookupMap<AccountId, u32>,
    members: Vector<AccountId>,
}

impl Default for Points {
    fn default() -> Self {
        Self {
            // each collection needs its own unique storage prefix
            points: LookupMap::new(b"p"),
            members: Vector::new(b"m"),
        }
    }
}

#[near]
impl Points {
    pub fn join(&mut self) {
        let who = env::predecessor_account_id();
        if !self.points.contains_key(&who) {
            self.members.push(who.clone());
            self.points.insert(who, 0);
        }
    }

    pub fn award(&mut self, who: AccountId, amount: u32) {
        let cur = self.points.get(&who).copied().unwrap_or(0);
        self.points.insert(who, cur + amount);
    }

    pub fn points_of(&self, who: AccountId) -> u32 {
        self.points.get(&who).copied().unwrap_or(0)
    }

    pub fn member_count(&self) -> u32 {
        self.members.len()
    }
}
Coming from EVM

mapping(k => v) ↔ LookupMap<K, V>; a dynamic array ↔ Vector<T>. There is no default value magic — reads return Option, so you unwrap_or(..) the "unset" case yourself.

Each collection takes a unique storage prefix (b"p", b"m") — the byte key its entries are stored under. Two collections sharing a prefix silently corrupt each other, so keep them distinct.

LookupMap is get/set by key but not iterable — the cheapest option. Need to iterate entries or list keys? Use IterableMap (or UnorderedMap). Paying only for what you enumerate is the tradeoff NEAR makes for predictable storage gas.