Value & Reference Types
Solidity is statically typed with primitives sized in bytes. Picking the right type controls gas, overflow behaviour, and security — uint8 instead of uint256 can save storage in a struct but cost more in a function call.
Primitives, arrays, structs, mappings
EXAMPLE
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20; // 0.8+ has built-in overflow checks
contract Types {
// 1) Booleans
bool public open = true;
// 2) Integers — sized in bits, 8 to 256, step 8
uint256 public count; // most common — full word, cheapest in functions
uint8 public smallCount; // 0-255; only saves gas when packed in storage
int256 public signed; // -2^255 to 2^255-1
int8 public smallSigned;
// 3) Address — 20 bytes
address public owner;
address payable public payee; // can receive ETH via .transfer/.send/.call
// 4) Fixed-size byte arrays — cheaper than bytes
bytes32 public root; // 32 bytes
bytes4 public selector; // function selector
// 5) Dynamic byte array / string
bytes public blob; // arbitrary bytes
string public name; // UTF-8 text
// 6) Enums — compile to uint8
enum Status { Pending, Active, Closed }
Status public status;
// 7) Fixed-size arrays
uint256[5] public top5;
// 8) Dynamic arrays
address[] public holders;
// 9) Mappings — hash-table
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
// 10) Structs — group related fields
struct Listing {
address seller;
uint96 priceWei; // packs with seller into one slot (20 + 12 = 32 bytes)
uint64 deadline;
bool sold;
}
mapping(uint256 => Listing) public listings;
// 11) Constants and immutables
uint256 public constant FEE_BPS = 250; // baked into bytecode, zero storage
address public immutable creator; // set once in constructor, cheaper than storage
constructor() {
creator = msg.sender;
owner = msg.sender;
}
// ── Storage vs memory vs calldata ──────────────────────────
// calldata — read-only, cheapest for function inputs
function setName(string calldata _name) external {
require(msg.sender == owner, 'not owner');
name = _name; // copy into storage
}
// memory — mutable in-function, freed after
function uppercase(string calldata s) external pure returns (string memory) {
bytes memory b = bytes(s);
for (uint256 i = 0; i < b.length; i++) {
if (b[i] >= 0x61 && b[i] <= 0x7A) b[i] = bytes1(uint8(b[i]) - 32);
}
return string(b);
}
// storage — direct pointer into contract state (powerful, risky)
function _markSold(uint256 id) internal {
Listing storage l = listings[id];
l.sold = true; // writes to chain
}
// ── Conversions ────────────────────────────────────────────
function conversions() external pure returns (uint256, int256, bytes32, address) {
uint8 a = 200;
uint256 b = uint256(a); // safe widening
int256 c = -1;
// uint256(c); // would compile but bit-cast
bytes32 h = bytes32(uint256(1234));
address addr = address(0xdEaD000000000000000000000000000000000000);
return (b, c, h, addr);
}
// ── Sign-extension and casting safety ──────────────────────
function downcast(uint256 big) external pure returns (uint8) {
require(big <= type(uint8).max, 'overflow');
return uint8(big); // safe narrowing
}
// ── Built-in introspection ─────────────────────────────────
function info() external pure returns (uint256 maxU8, uint256 maxU256, int256 minI256) {
return (type(uint8).max, type(uint256).max, type(int256).min);
}
// ── Hashing ────────────────────────────────────────────────
function digest(string calldata s) external pure returns (bytes32) {
return keccak256(abi.encodePacked(s));
}
// ── Common bugs ────────────────────────────────────────────
// • Using uint8 in function args expecting gas savings → no; only saves in packed storage
// • Using `address` instead of `address payable` for transfers → compile error
// • Storing dynamic strings in mappings without realizing how expensive it is
// • Forgetting struct fields pack in DECLARATION order → wrong order wastes slots
// • Casting int256 to uint256 with negative value → huge positive number
// • Using `bytes32` for arbitrary text and truncating silently → use `string` or `bytes`
}
Why it matters
Pick types for storage layout, not just range. A struct with address (20) + uint96 (12) packs into a single 32-byte slot — rearranging fields by size can cut a write by 20,000 gas. For function arguments, prefer uint256 unless there’s a specific encoding reason.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// Value: uint, int, bool, address, bytes1..32, enum // Reference: arrays, bytes, string, mapping, struct // Reference types live in storage / memory / calldata — pick where they reside.Try it Yourself »
Discussion
Loading…