Skip to content
This guide targets Fe 26.5. Fe is not production-ready. Install Fe 26.5

Built-in Types Reference

This appendix provides a complete reference for Fe’s built-in types.

Unsigned integers cannot be negative. Fe provides sizes matching EVM word boundaries:

TypeBitsBytesRange
u8810 to 255
u161620 to 65,535
u323240 to 4,294,967,295
u646480 to 18,446,744,073,709,551,615
u128128160 to 2¹²⁸ - 1
u256256320 to 2²⁵⁶ - 1
let small: u8 = 255
let balance: u256 = 1000000000000000000 // 1 ETH in wei
let max_supply: u256 = 21000000
  • u256 is the native EVM word size and most efficient for storage
  • Choose integer widths for their range and ABI requirements; do not assume automatic storage packing
  • Arithmetic overflow causes a revert by default

Signed integers can be negative. They use two’s complement representation:

TypeBitsBytesRange
i881-128 to 127
i16162-32,768 to 32,767
i32324-2,147,483,648 to 2,147,483,647
i64648-2⁶³ to 2⁶³ - 1
i12812816-2¹²⁷ to 2¹²⁷ - 1
i25625632-2²⁵⁵ to 2²⁵⁵ - 1
let delta: i256 = -100
let temperature: i32 = -40
let positive: i256 = 1000
  • Use signed integers when negative values are meaningful
  • Most ERC standards use unsigned integers
  • Signed division and comparison use different EVM opcodes

The bool type represents true/false values:

TypeSizeValues
bool1 bit (stored as 1 byte)true, false
let is_active: bool = true
let paused: bool = false
if is_active && !paused {
// ...
}
OperationSyntaxDescription
ANDa && bTrue if both true
ORa || bTrue if either true
NOT!aInverts the value
Equalitya == bTrue if same value

Fixed-size strings with a maximum length:

TypeDescription
String<N>String with max length N bytes
let name: String<31> = "CoolCoin"
let symbol: String<8> = "COOL"
let message: String<31> = "Transfer failed"
  • String length is fixed at compile time
  • Shorter strings are padded
  • Used for token names, symbols, and error messages

Fixed-size collections of heterogeneous types:

TypeDescription
(T1, T2, ...)Tuple of types T1, T2, etc.
let pair: (u256, bool) = (100, true)
let triple: (u256, u256, u256) = (1, 2, 3)
// Destructuring
let (_amount, _success): (u256, bool) = (100, true)
// Access by index
let pair2: (u256, bool) = (100, true)
let first = pair2.0
let pair3: (u256, bool) = (100, true)
let second = pair3.1

Fixed-size collections of homogeneous types:

TypeDescription
[T; N]Array of N elements of type T
let numbers: [u256; 3] = [1, 2, 3]
let first = numbers[0]

Fixed-size arrays also support == and != when their elements implement Eq:

#[test]
fn array_equality() {
let left: [u256; 3] = [1, 2, 3]
let right: [u256; 3] = [1, 2, 3]
assert!(left == right)
}

std::abi::DynArray<T> represents an ABI array with a runtime length. Build the elements in memory with a MemVec<T> (a growable array from the prelude) or a MemArray<T>, then encode them with DynArray::from_slice:

use std::abi::DynArray
#[test]
fn dynamic_array_copy() {
var values = MemVec<u256>::new()
values.push(10)
values.push(20)
let encoded: DynArray<u256> = DynArray::from_slice(values.as_slice())
values.set(index: 0, value: 99)
assert!(encoded.len() == 2)
assert!(encoded.get(0) == 10)
assert!(values.as_slice()[0] == 99)
}

from_slice creates an independent copy suitable for ABI arguments, return values, or event fields. to_mem_array decodes the elements back into a MemArray. DynArray::get works for any ABI element type: one-word values, multi-word static structs and tuples, and dynamic elements such as Bytes. Lengths and indices are usize. Out-of-bounds access reverts with Panic(0x32).

The empty tuple, representing no value:

TypeDescription
()Unit type, no value
fn do_something() {
// Implicitly returns ()
}
fn explicit_unit() -> () {
()
}

Represents an optional value:

TypeDescription
Option<T>Either Some(T) or None
let maybe_value: Option<u256> = Option::Some(42)
let nothing: Option<u256> = Option::None
match maybe_value {
Option::Some(v) => v,
Option::None => 0,
}

std types ETH amounts and block time, so that they cannot be mixed up with each other or with plain numbers. All three are in the prelude and are a single uint256 in the ABI:

TypeDescription
WeiAn amount of native ETH
TimestampA point in time, in unix seconds
DurationA span of time, in seconds
use std::evm::{days, ether, gwei, hours}
const LOCK: Duration = days(2) + hours(12)
#[test]
fn typed_amounts_and_time() {
let price = gwei(30) * 21_000
assert!(price < ether(1))
assert!(price / gwei(1) == 630_000)
assert!(ether(1).mul_div(numerator: 3, denominator: 1000) == gwei(3_000_000))
let start = Timestamp::from_seconds(1_700_000_000)
let unlock = start + LOCK
assert!(unlock - start == LOCK)
assert!(LOCK.as_seconds() == 216_000)
}

Timestamp + Duration is a Timestamp, Timestamp - Timestamp is a Duration, and Wei / Wei is a plain u256 ratio; Timestamp + Timestamp or Wei + Duration do not compile. Wei and Duration add, subtract, scale and divide by a u256, take a remainder, and compare. Build values with wei, gwei, ether, seconds, minutes, hours, days and weeks (all const fns in std::evm), or the constants WEI, GWEI, ETHER, SECOND, MINUTE, HOUR, DAY and WEEK. Use as_wei() or as_seconds() where a plain number is needed.

Key-value storage mapping:

TypeDescription
StorageMap<K, V>Maps keys of type K to values of type V
struct Storage {
balances: StorageMap<Address, u256>,
allowances: StorageMap<(Address, Address), u256>,
}
// Access
let balance = storage.balances.get(account)
// Update
storage.balances.set(key: account, value: new_balance)
  • Maps are storage-only types
  • Non-existent keys return the zero value
  • Tuples can be used as composite keys

Convert between numeric types with as:

let small: u8 = 100
let big: u256 = small as u256
let signed: i256 = -50
let unsigned: u256 = signed.downcast_unchecked() // Caution: reinterprets bits
FromToBehavior
Smaller unsignedLarger unsignedZero-extends
Larger unsignedSmaller unsignedTruncates
SignedUnsignedReinterprets bits
UnsignedSignedReinterprets bits
CategoryTypes
Unsigned integersu8, u16, u32, u64, u128, u256
Signed integersi8, i16, i32, i64, i128, i256
Booleanbool
StringString<N>
Tuple(T1, T2, ...)
Array[T; N]
Unit()
OptionalOption<T>
ETH and timeWei, Timestamp, Duration
Memory collectionsMemVec<T>, MemArray<T>, MemSlice<T>
ABI arraysDynArray<T>
MapStorageMap<K, V>

An EVM storage slot is 32 bytes. Each contract field gets its own root slot, in declaration order. Inside a struct, fields narrower than a word share slots the way Solidity packs struct members. A StorageMap keeps its entry for key at keccak256(key ++ slot), like a Solidity mapping, and stores values in their type’s native layout. Inspect the compiler’s contract layout information when layout matters.

Pointer-bearing memory values cannot be persisted in contract storage.

To construct an ABI array, collect the elements in a MemVec or MemArray and encode them with DynArray::from_slice as shown above. DynArray moved from core::abi to std::abi; import it from there.