Skip to content
Pre-Release: Fe is under active development. This documentation covers the upcoming release. Follow progress on GitHub

Fe for Rust Developers

Fe draws heavy inspiration from Rust. This guide highlights what’s familiar and what’s different for Rust developers.

Fe structs work like Rust structs:

struct Point {
x: u256,
y: u256,
}
let p = Point { x: 10, y: 20 }
let x = p.x

Methods are defined in impl blocks:

struct Counter {
value: u256,
}
impl Counter {
fn new() -> Self {
Counter { value: 0 }
}
fn increment(mut self) {
self.value += 1
}
fn get(self) -> u256 {
self.value
}
}

Traits define shared behavior:

trait Hashable {
fn hash(self) -> u256
}
impl Hashable for Point {
fn hash(self) -> u256 {
self.x ^ self.y
}
}

Type parameters work similarly:

fn identity<T>(value: T) -> T {
value
}
struct Wrapper<T> {
value: T,
}
impl<T> Wrapper<T> {
fn get(self) -> T {
self.value
}
}

Constrain generics with trait bounds:

fn process<T: Hashable>(item: T) -> u256 {
item.hash()
}
fn complex<T: Hashable + Printable>(item: T) {
// T must implement both traits
}

Enums with match expressions:

enum Status {
Pending,
Active,
Completed { result: u256 },
}
fn handle(status: Status) -> u256 {
match status {
Status::Pending => 0,
Status::Active => 1,
Status::Completed { result } => result,
}
}

Optional values use Option<T>:

let maybe: Option<u256> = Option::Some(42)
match maybe {
Option::Some(v) => v,
Option::None => 0,
}

Most constructs are expressions:

let value = if condition { 10 } else { 20 }
let result = match status {
Status::Active => true,
_ => false,
}

Types are inferred where possible:

let x = 42 // u256 inferred
let y: u8 = 42 // Explicit annotation

Variables are immutable by default:

let x = 10 // Immutable
let mut y = 10 // Mutable
y = 20 // OK

Fe doesn’t have Rust’s ownership system. All values are copied or have reference semantics based on context:

// Rust would require borrowing
fn process(data: MyStruct) {
// In Fe, no ownership concerns
}
let a = MyStruct { ... }
process(a)
process(a) // Fine in Fe, would be error in Rust

No lifetime annotations needed:

// Rust: fn longest<'a>(a: &'a str, b: &'a str) -> &'a str
// Fe: Just works
fn longest(a: String<32>, b: String<32>) -> String<32> {
if a.len() > b.len() { a } else { b }
}

Fe uses an effect system instead of borrowing:

// Rust: fn modify(data: &mut Storage)
// Fe: Effect declaration
fn modify() uses (mut storage: Storage) {
}

Fe has a standard library, but EVM constraints mean some Rust types aren’t available:

RustFeReason
Vec<T>Fixed arrays [T; N]No dynamic heap allocation
HashMap<K, V>Map<K, V>Storage-only, uses EVM storage slots
StringString<N>Fixed size for predictable gas costs
Box<T>Not availableNo heap
Rc<T>, Arc<T>Not availableNo heap, no threading

Fe prefers fixed-size types for EVM efficiency:

// Rust: String, Vec<u8>
// Fe: Fixed-size
let name: String<32> = "Token"
let data: [u8; 32] = [0; 32]

Currently, Fe has basic loop constructs but not yet the full iterator pattern:

// Rust: items.iter().map(|x| x + 1).collect()
// Fe: Currently uses manual while loops
let mut i: u256 = 0
while i < items.len() {
// process items[i]
i = i + 1
}

Error handling primarily uses assertions:

// Rust: Result<T, E>
// Fe: Assertions and revert
assert(balance >= amount, "Insufficient balance")

Fe doesn’t support closures:

// Rust: let add = |a, b| a + b;
// Fe: Use named functions
fn add(a: u256, b: u256) -> u256 {
a + b
}

Fe has modules, but uses different terminology:

RustFe
CrateIngot
ModuleModule
// In fe.toml
[dependencies]
my_lib = { path = "../my_lib" }

Fe organizes code into ingots (packages) containing modules, similar to Rust’s crate/module system.

Fe has first-class contract support:

contract Token {
store: TokenStorage,
init(supply: u256) {
// Constructor
}
recv TokenMsg {
// Message handlers
}
}

External interfaces are defined separately:

msg TokenMsg {
#[selector = 0xa9059cbb]
Transfer { to: Address, amount: u256 } -> bool,
}

Persistent key-value storage:

struct Storage {
balances: Map<Address, u256>,
}

Blockchain events for logging:

struct Transfer {
#[indexed]
from: Address,
#[indexed]
to: Address,
value: u256,
}

Explicit capability tracking:

fn transfer(from: Address, to: Address, amount: u256)
uses (mut store: TokenStore, mut log: Log)
{
// Function declares what it accesses
}
ConceptRustFe
Functionfn foo() {}fn foo() {}
Structstruct Foo {}struct Foo {}
Implimpl Foo {}impl Foo {}
Traittrait Bar {}trait Bar {}
Genericsfn foo<T>()fn foo<T>()
BoundsT: TraitT: Trait
Matchmatch x {}match x {}
Ifif x {}if x {}
Looploop {}loop {}
Forfor x in iter {}for x in iter {}
Letlet x = 1;let x = 1
Mutlet mut x = 1;let mut x = 1
Returnreturn xreturn x
Selfselfself
Self typeSelfSelf
FeatureStatus in Fe
Ownership/BorrowingNot applicable (effects instead)
LifetimesNot needed
Result<T, E>Use assertions/revert
ClosuresNot available
IteratorsPlanned (trait-based)
Vec<T>Fixed arrays only
Pattern guardsLimited
async/awaitNot applicable
MacrosNot available
FeatureDescription
contractSmart contract declarations
msgMessage type definitions
recvMessage handlers
initContract constructors
usesEffect declarations
withEffect binding
#[selector]ABI selector attributes
#[indexed]Event indexing
Map<K, V>Storage mappings
  1. Forget ownership - Fe doesn’t have borrow checking; think in terms of effects

  2. Use effects - Instead of &mut, declare effects with uses mut

  3. Fixed sizes - Plan for fixed-size data structures upfront

  4. No heap - EVM has no heap; design accordingly

  5. Storage is special - Map only works in storage, not memory

  6. Selectors matter - External interfaces need explicit ABI selectors

  7. Events are logs - Use events for off-chain observability