Fe for Rust Developers
Fe draws heavy inspiration from Rust. This guide highlights what’s familiar and what’s different for Rust developers.
Familiar Concepts
Section titled “Familiar Concepts”Structs
Section titled “Structs”Fe structs work like Rust structs:
struct Point { x: u256, y: u256,}
let p = Point { x: 10, y: 20 }let x = p.xImpl Blocks
Section titled “Impl Blocks”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
Section titled “Traits”Traits define shared behavior:
trait Hashable { fn hash(self) -> u256}
impl Hashable for Point { fn hash(self) -> u256 { self.x ^ self.y }}Generics
Section titled “Generics”Type parameters work similarly:
fn identity<T>(value: own T) -> T { value}
struct Wrapper<T> { value: T,}
impl<T> Wrapper<T> { fn get(own self) -> T { self.value }}Trait Bounds
Section titled “Trait Bounds”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 and Pattern Matching
Section titled “Enums and Pattern Matching”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, }}Option Type
Section titled “Option Type”Optional values use Option<T>:
let maybe: Option<u256> = Option::Some(42)
match maybe { Option::Some(v) => v, Option::None => 0,}Expression-Based
Section titled “Expression-Based”Most constructs are expressions:
let value: u256 = if condition { 10 } else { 20 }
let result = match status { Status::Active => true, _ => false,}Type Inference
Section titled “Type Inference”Types are inferred where possible:
let x = true // Type inferred as boollet y: u8 = 42 // Explicit annotationMutability
Section titled “Mutability”Variables are immutable by default:
let x: u256 = 10 // Immutablevar y: u256 = 10 // Mutable (Rust: let mut)y = 20 // OKKey Differences
Section titled “Key Differences”Mutable Value Semantics
Section titled “Mutable Value Semantics”Fe has ownership concepts similar to Rust (own, ref, mut), but references are not values: Fe has mutable value semantics. Key differences:
- Default is view mode (no keyword): the function can read the value but not move or modify it. Unlike Rust, view-mode parameters can be used multiple times without explicit borrowing:
fn process(data: MyStruct) { // View mode: read-only access, no ownership transfer}
let a = MyStruct { x: 1 }process(data: a)process(data: a) // Fine: view mode doesn't consume the valueowntransfers ownership (like Rust’s move): the caller cannot use the value after passing itmutgrants mutable access to the caller’s place for the duration of a call or binding (let h = mut p.x), similar to&mut Trefgrants read access to a place (let r = ref p.x), similar to&Tvardeclares a mutable value, replacing Rust’slet mut
Unlike Rust’s &T and &mut T, ref and mut are not types. An access cannot be stored in a struct field or returned from an ordinary function; a function that returns ref T or mut T is a projection that grants its caller access to a place. An access checker (instead of a borrow checker) rejects overlapping accesses unless both only read, and accesses to different fields of one struct never overlap.
No Lifetimes
Section titled “No Lifetimes”Fe does not require lifetime annotations. Since accesses cannot be stored, an access simply lasts until its last use.
See Ownership & Mutability for details on own, ref, and mut.
Iteration
Section titled “Iteration”Fe’s for loop works over ranges, arrays, MemVec, DynArray and any type implementing the core::iter::Collection trait. Collections are not consumed by a loop; instead the loop’s pattern chooses how each element is bound: for x in xs copies a Copy element, for ref x in xs reads it in place and for mut x in xs modifies it in place.
Collections provide adapters such as enumerate, filter, map, take, skip and reversed for loops, and fold, any, count and find:
// Rust: items.iter().map(|x| x + 1).sum()let sum = items.fold(0, |acc, x| acc + x + 1)
// Rust: for (i, x) in items.iter().enumerate().filter(..)for (i, x) in items.enumerate() {}See For Loops.
Closures
Section titled “Closures”Closures look like Rust’s, |a, b| a + b. A closure captures what it uses by copy or move and cannot modify its captures; state is threaded through parameters and results instead, as fold does. Functions take callables with bounds such as F: Fn(u256) -> u256:
fn apply<F: Fn(u256) -> u256>(_ f: F, _ x: u256) -> u256 uses F::E { f(x)}
#[test]fn closures() { let add = |a: u256, b: u256| a + b assert!(add(2, 3) == 5) assert!(apply(|x| x * 2, 21) == 42)}uses F::E forwards the effects the closure’s body needs. See Closures.
Modules and Ingots
Section titled “Modules and Ingots”Fe has modules, but uses different terminology:
| Rust | Fe |
|---|---|
| Crate | Ingot |
| Module | Module |
// In fe.toml[dependencies]my_lib = { path = "../my_lib" }Fe organizes code into ingots (packages) containing modules, similar to Rust’s crate/module system.
EVM-Specific Features
Section titled “EVM-Specific Features”Contract Declarations
Section titled “Contract Declarations”Fe has first-class contract support:
contract Token { mut store: TokenStorage
init(supply: u256) uses (mut store) { }
recv TokenMsg { }}Message Types
Section titled “Message Types”External interfaces are defined separately:
use std::abi::sol
msg TokenMsg { #[selector = sol("transfer(address,uint256)")] Transfer { to: Address, amount: u256 } -> bool,}Storage Maps
Section titled “Storage Maps”Persistent key-value storage:
struct Storage { balances: StorageMap<Address, u256>,}Events
Section titled “Events”Blockchain events for logging:
#[event]struct Transfer { #[indexed] from: Address, #[indexed] to: Address, value: u256,}Effect System
Section titled “Effect System”Explicit capability tracking:
fn transfer(from: Address, to: Address, amount: u256) uses (store: mut TokenStore, log: mut Log){ // Function declares what it accesses}Syntax Comparison
Section titled “Syntax Comparison”| Concept | Rust | Fe |
|---|---|---|
| Function | fn foo() {} | fn foo() {} |
| Struct | struct Foo {} | struct Foo {} |
| Impl | impl Foo {} | impl Foo {} |
| Trait | trait Bar {} | trait Bar {} |
| Generics | fn foo<T>() | fn foo<T>() |
| Bounds | T: Trait | T: Trait |
| Match | match x {} | match x {} |
| If | if x {} | if x {} |
| Loop | loop {} | while true {} |
| For | for x in iter {} | for x in iter {} |
| Let | let x = 1; | let x = 1 |
| Mut | let mut x = 1; | var x = 1 |
| Return | return x | return x |
| Self | self | self |
| Self type | Self | Self |
What You’ll Miss from Rust
Section titled “What You’ll Miss from Rust”| Feature | Status in Fe |
|---|---|
| Ownership/Borrowing | Mutable value semantics: own, ref, and mut modes, no reference types, no lifetimes (see Mutable Value Semantics) |
| Lifetimes | Not needed |
| Closures | Available; captures are read-only copies or moves |
| Iterators | for over collections with adapters (enumerate, filter, map, …) and fold/any/count/find; no Iterator trait |
async/await | Not applicable |
| Macros | No user-defined macros; assert! is built in |
What’s New in Fe
Section titled “What’s New in Fe”| Feature | Description |
|---|---|
contract | Smart contract declarations |
msg | Message type definitions |
recv | Message handlers |
init | Contract constructors |
uses | Effect declarations |
with | Effect binding |
var | Mutable binding (instead of let mut) |
#[selector] | ABI selector attributes |
#[indexed] | Event indexing |
StorageMap<K, V> | Storage mappings |