Fe is a Rust-like language for the EVM, with explicit effects, message-passing contracts, and an integrated toolchain.

$ curl -fsSL https://raw.githubusercontent.com/argotorg/fe/master/feup/feup.sh | bash
$ brew install fe-lang/tap/fe

Explicit Effects

The uses clause declares all capabilities a function requires: storage access, event emission, external calls, contract creation. Side effects become explicit.

Explicit Mutability

All bindings, storage fields, and effect parameters are immutable unless marked mut. The compiler rejects writes to immutable state.

Message Passing

Contracts interact via msg types and recv handlers, mirroring the EVM's transaction-based execution model.

Pattern Matching

match expressions destructure enums, tuples, and nested data. Extract variant payloads and branch on values. The compiler checks that all cases are covered.

Traits

Define shared interfaces with trait and implement them for any type. Used throughout the standard library for Encode, Decode, Default, and more.

Generics

Type parameters on struct, enum, trait, and fn with trait bounds. Monomorphized at compile time with zero runtime overhead.

Higher-Kinded Types

Supports higher-kinded types via * -> * bounds on traits. Enables abstractions like Functor, Applicative, and Monad, generic over Option<T>, Result<E, T>, or any type constructor.

Const Functions

Functions marked const fn are evaluated at compile time. The sol("deposit()") selector in the code computes its 4-byte hash during compilation, not at runtime.

Standard Library

Ships with Option<T>, Result<E, T>, StorageMap<K, V>, and traits like Default, Encode, Decode.

Fe at a glance
/// A vault contract with traits, pattern matching, /// and explicit effects. use std::abi::sol // Enums as error types enum VaultError { InsufficientFunds, ZeroAmount, } // Traits define shared behavior trait Validate { fn validate(self) -> Result<VaultError, u256> } struct Withdrawal { balance: u256, amount: u256, } impl Validate for Withdrawal { fn validate(self) -> Result<VaultError, u256> { if self.amount == 0 { return Result::Err(VaultError::ZeroAmount) } if self.balance < self.amount { return Result::Err(VaultError::InsufficientFunds) } Result::Ok(self.balance - self.amount) } } // Events with indexed fields for efficient filtering #[event] struct Deposited { #[indexed] owner: Address, amount: u256, } struct VaultStore { balances: StorageMap<Address, u256>, } // Message interface defines the contract's public ABI msg VaultMsg { #[selector = sol("deposit()")] Deposit {}, #[selector = sol("withdraw(uint256)")] Withdraw { amount: u256 }, #[selector = sol("balanceOf(address)")] BalanceOf { addr: Address } -> u256, } // Effects declared explicitly, no hidden state access pub contract Vault uses (ctx: Ctx, log: mut Log) { mut store: VaultStore recv VaultMsg { Deposit {} uses (ctx, mut store, mut log) { let who = ctx.caller() store.balances.set( key: who, value: store.balances.get(key: who) + ctx.value() ) log.emit(event: Deposited { owner: who, amount: ctx.value() }) } // Pattern match on Result for control flow Withdraw { amount } uses (ctx, mut store) { let who = ctx.caller() let req = Withdrawal { balance: store.balances.get(key: who), amount } match req.validate() { Ok(new_bal) => { store.balances.set(key: who, value: new_bal) } Err(e) => { revert(e) } } } BalanceOf { addr } -> u256 uses (store) { store.balances.get(key: addr) } } }

Built-in Testing

Run fe test with integrated EVM simulation. No external frameworks needed.

Package Manager

Organize code into ingots. Add dependencies and share reusable contracts.

Code Formatter

Run fe fmt for consistent, opinionated formatting.

Language Server

LSP support with diagnostics, go-to-definition, and completions for many popular editors.

Docs Generator

Generate browsable HTML docs from your code with fe doc.

Dual Backend

Compiles through Sonatina, Fe's custom SSA-based backend, or via Yul for Solidity toolchain compatibility.

Explore more contracts written in Fe

See examples →