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

Generic Functions

Generic functions work with multiple types using type parameters. Instead of writing separate functions for each type, you write one function that works for any type meeting certain requirements.

Define a generic function with type parameters in angle brackets:

fn identity<T>(value: T) -> T {
value
}

T is a type parameter, a placeholder for any concrete type. When called, the compiler substitutes the actual type:

let x: u256 = identity(42) // T is u256
let y: bool = identity(true) // T is bool

Functions can have multiple type parameters:

fn pair<A, B>(first: A, second: B) -> (A, B) {
(first, second)
}
let p: (u256, bool) = pair(1, true) // (u256, bool)

Usually, you need to constrain what types are allowed. Use trait bounds:

trait Printable {
fn to_string(self) -> String
}
fn print_value<T: Printable>(value: T) -> String {
value.to_string()
}

Now print_value only accepts types that implement Printable:

struct Message {
text: String,
}
impl Printable for Message {
fn to_string(self) -> String {
self.text
}
}
let msg = Message { text: "Hello" }
print_value(msg) // Works: Message implements Printable

Structs can also be generic:

struct Wrapper<T> {
value: T,
}
impl<T> Wrapper<T> {
fn new(value: T) -> Wrapper<T> {
Wrapper { value }
}
fn get(self) -> T {
self.value
}
}
let w: Wrapper<u256> = Wrapper::new(42)
let v = w.get() // 42

Methods can introduce their own type parameters:

struct Container<T> {
item: T,
}
impl<T> Container<T> {
fn get(self) -> T {
self.item
}
// Method with its own type parameter
fn map<U>(self, f: fn(T) -> U) -> Container<U> {
Container { item: f(self.item) }
}
}

Write once, use with many types:

// Without generics: separate functions for each type
fn max_u256(a: u256, b: u256) -> u256 {
if a > b { a } else { b }
}
fn max_i256(a: i256, b: i256) -> i256 {
if a > b { a } else { b }
}
// With generics: one function
fn max<T: Comparable>(a: T, b: T) -> T {
if a.greater_than(b) { a } else { b }
}

Generics preserve type information:

fn first<T>(items: Array<T>) -> T {
items[0]
}
let numbers: Array<u256> = [1, 2, 3]
let n = first(numbers) // n is u256, not a generic "any" type

Usually the compiler infers types:

let x = identity(42) // Compiler infers T = u256

Sometimes you need to specify types explicitly:

let x = identity::<u256>(42)
fn swap<T>(a: T, b: T) -> (T, T) {
(b, a)
}
let (x, y): (u256, u256) = swap(1, 2) // (2, 1)
fn or_default<T: Default>(value: Option<T>) -> T {
match value {
Option::Some(v) => v,
Option::None => T::default(),
}
}
trait Transform {
fn transform(self) -> Self
}
fn apply_twice<T: Transform>(value: T) -> T {
value.transform().transform()
}
fn process<T: Hashable>(item: T) -> u256 {
item.hash()
}
fn process<T: Hashable + Printable>(item: T) -> String<256> {
let hash = item.hash()
item.to_string()
}

See Trait Bounds for more on constraining generics.

Generics and effects serve different purposes:

GenericsEffects
Type polymorphismCapability tracking
Compile-time resolutionRuntime behavior
fn foo<T>(x: T)fn foo() uses (s: Storage)

They can be combined:

fn get_value<T: Readable>(key: u256) -> T uses (storage: Storage) {
// Generic return type with storage effect
storage.get(key)
}
SyntaxDescription
fn foo<T>()Generic function with type parameter
fn foo<T: Trait>()Bounded type parameter
fn foo<A, B>()Multiple type parameters
foo::<Type>()Explicit type argument
struct Foo<T>Generic struct
impl<T> Foo<T>Generic implementation