Built-in Effects
Unlike some languages with fixed built-in effects, Fe’s effects are user-defined types. This gives you complete control over what capabilities your code requires.
Effects Are User-Defined
Section titled “Effects Are User-Defined”Any struct or trait can serve as an effect:
// Define your own effect typepub struct Logger { pub entries: u256, // Simplified for example}
// Use it as an effectfn log_message(value: u256) uses (mut logger: Logger) { logger.entries = value}This means:
- You define exactly what your effects contain
- Effects are regular types with fields and methods
- No magic: effects are just capability markers
Common Effect Patterns
Section titled “Common Effect Patterns”While Fe doesn’t prescribe specific built-in effects, certain patterns are common:
Context Effect
Section titled “Context Effect”A context effect provides execution environment information:
pub struct Ctx { pub caller: u256, pub block_number: u256, pub timestamp: u256,}
fn only_owner(owner: u256) uses (ctx: Ctx) { require(ctx.caller == owner)}
fn get_timestamp() -> u256 uses (ctx: Ctx) { ctx.timestamp}Storage Effect
Section titled “Storage Effect”A storage effect represents contract state:
pub struct TokenStore { pub balances: Map<u256, u256>, pub total_supply: u256,}
fn balance_of(account: u256) -> u256 uses (store: TokenStore) { store.balances.get(account)}
fn mint(to: u256, amount: u256) uses (mut store: TokenStore) { let current = store.balances.get(to) store.balances.set(to, current + amount) store.total_supply = store.total_supply + amount}Logger Effect
Section titled “Logger Effect”A logger effect tracks events:
pub struct EventLog { // Event logging state pub data: u256,}
fn emit_transfer(from: u256, to: u256, amount: u256) uses (mut log: EventLog) { // Emit transfer event}Config Effect
Section titled “Config Effect”A configuration effect provides settings:
pub struct Config { pub max_transfer: u256, pub fee_rate: u256, pub paused: bool,}
fn check_transfer(amount: u256) -> bool uses (config: Config) { if config.paused { return false } amount <= config.max_transfer}
fn calculate_fee(amount: u256) -> u256 uses (config: Config) { amount * config.fee_rate / 10000}Combining Multiple Effects
Section titled “Combining Multiple Effects”Real contracts often need multiple effects:
fn transfer(from: u256, to: u256, amount: u256) uses (ctx: Ctx, mut store: TokenStore, config: Config, mut log: EventLog){ // Check caller authorization require(ctx.caller == from)
// Check not paused require(config.paused == false)
// Perform transfer let from_balance = store.balances.get(from) let to_balance = store.balances.get(to)
store.balances.set(from, from_balance - amount) store.balances.set(to, to_balance + amount)
// Log the event emit_transfer(from, to, amount)}Generic Effects
Section titled “Generic Effects”Effects can be generic:
pub struct Cache<T> { pub value: T, pub valid: bool,}
fn get_cached<T>() -> Option<T> uses (cache: Cache<T>) { if cache.valid { Option::Some(cache.value) } else { Option::None }}
fn set_cached<T>(value: T) uses (mut cache: Cache<T>) { cache.value = value cache.valid = true}Structs as Effects
Section titled “Structs as Effects”Structs can serve as effects, allowing you to inject behavior:
// Define a validator effectpub struct RangeValidator { pub min: u256, pub max: u256,}
impl RangeValidator { pub fn is_valid(self, value: u256) -> bool { value >= self.min && value <= self.max }}
fn validate(value: u256) -> bool uses (v: RangeValidator) { v.is_valid(value)}Designing Your Effects
Section titled “Designing Your Effects”When creating effects, consider:
Separation of Concerns
Section titled “Separation of Concerns”Keep effects focused on one responsibility:
// Good: separate concernspub struct Balances { pub data: u256 }pub struct Allowances { pub data: u256 }pub struct EventLog { pub data: u256 }
// Less ideal: everything in one effectpub struct TokenState { balances: u256, allowances: u256, events: u256,}Minimal Mutability
Section titled “Minimal Mutability”Only require mut when necessary:
// Read-only checkfn has_balance(account: u256, amount: u256) -> bool uses (balances: Balances) { balances.get(account) >= amount}
// Only this needs mutfn debit(account: u256, amount: u256) uses (mut balances: Balances) { let current = balances.get(account) balances.set(account, current - amount)}Clear Naming
Section titled “Clear Naming”Name effects to reflect their purpose:
// Clear purposepub struct TokenBalances { pub data: u256 }pub struct AccessControl { pub data: u256 }pub struct TransferLog { pub data: u256 }
// Less clearpub struct Data { pub value: u256 }pub struct State { pub value: u256 }Summary
Section titled “Summary”| Pattern | Purpose |
|---|---|
| Context | Execution environment (caller, block info) |
| Storage | Persistent contract state |
| Logger | Event emission |
| Config | Runtime configuration |
| Cache | Temporary computed values |
Effects in Fe are not magic. They’re regular types that declare capabilities. This gives you full control over your contract’s dependency structure.