State & storage

State, constants & locals

Where values live. Struct fields are storage, module const is a compile-time constant, and a let inside a method is a local — no storage / memory / calldata keywords.

Solidity makes you think about *where* a value lives: storage, memory, calldata. NEAR doesn’t have those keywords. The rule is structural:

Struct fields on your #[near(contract_state)] type are storage (persisted on-chain). A module-level `const` is a compile-time constant with no storage slot. A `let` inside a method is a local that lives only for that call.

The three kinds of value
Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract Vars {
    // storage: persisted on-chain
    uint256 public total;

    // constant: compile-time, no storage slot
    uint256 public constant MAX = 1_000;

    function bump(uint256 amount) external {
        // local: exists only for this call
        uint256 next = total + amount;
        require(next <= MAX, "over max");
        total = next;
    }
}
Rust
use near_sdk::near;

// constant: compile-time, no storage
const MAX: u64 = 1_000;

#[near(contract_state)]
#[derive(Default)]
pub struct Vars {
    // storage: persisted on-chain
    total: u64,
}

#[near]
impl Vars {
    pub fn bump(&mut self, amount: u64) {
        // local: exists only for this call
        let next = self.total + amount;
        assert!(next <= MAX, "over max");
        self.total = next;
    }

    pub fn get_total(&self) -> u64 {
        self.total
    }
}
Coming from EVM

No storage / memory / calldata. A struct field is storage; a let is a local; a module const is the analog of Solidity’s constant.

require(cond, "msg") maps to assert!(cond, "msg"). Both roll the whole call back on failure and refund the unused gas — same guarantee, different spelling.

Numbers are real Rust integers (u64, u128, …). There’s no single 256-bit default like Solidity’s uint256; you pick the width, and overflow panics in a release build rather than wrapping.