Best examples of defining functions in Rust: practical examples for 2025

If you’re learning Rust, you don’t need another dry reference page. You need concrete, working code. This guide focuses on **examples of defining functions in Rust: practical examples** that you can paste into a file, run with `cargo run`, and actually understand. We’ll move from tiny, focused snippets to more realistic patterns you’ll see in production Rust code. Instead of memorizing syntax, you’ll see how functions behave with ownership, borrowing, generics, traits, async, and error handling. These examples include both beginner‑friendly snippets and patterns that show up in modern Rust projects in 2024–2025, from CLI tools to async services. Along the way, I’ll point you to the official Rust documentation and other authoritative resources so you can go deeper when you’re ready. By the end, you’ll not only recognize the syntax—you’ll know when and why to use each style of function in real codebases.
Written by
Jamie
Published

Let’s start with the smallest possible example of a Rust function and build up from there. You’ll see the same pattern repeatedly: name, parameters, return type, and body.

fn add(a: i32, b: i32) -> i32 {
    a + b
}

fn main() {
    let sum = add(2, 3);
    println!("sum = {}", sum);
}

Key points buried in this tiny snippet:

  • fn introduces a function.
  • Parameters have names and types: a: i32.
  • The return type comes after ->.
  • The last expression without a semicolon is the return value.

Another basic example of a function, this time with no parameters and no return value:

fn greet() {
    println!("Hello from Rust!");
}

fn main() {
    greet();
}

When you’re scanning real examples of defining functions in Rust, you’ll notice most follow one of these two shapes: returning a value or doing some side effect (printing, logging, writing to disk) and returning () (the unit type).


Examples of defining functions in Rust with ownership and borrowing

Rust’s ownership model is where things start to feel different from languages like Python or JavaScript. The best examples of function definitions in Rust usually highlight how values move or get borrowed.

Example: Taking ownership

fn consume_string(s: String) {
    println!("Consumed: {}", s);
}

fn main() {
    let name = String::from("Ferris");
    consume_string(name);
    // name can no longer be used here; ownership moved into the function
}

The parameter s: String takes ownership. After consume_string(name), name is no longer valid in main. This is a classic example of defining functions in Rust that take values.

Example: Borrowing immutably

fn print_len(s: &String) {
    println!("Length = {}", s.len());
}

fn main() {
    let name = String::from("Ferris");
    print_len(&name);
    println!("Still can use name: {}", name);
}

Here, &String is an immutable borrow. The function can read but not modify the string, and the caller keeps ownership.

Example: Borrowing mutably

fn push_exclamation(s: &mut String) {
    s.push('!');
}

fn main() {
    let mut msg = String::from("Hello");
    push_exclamation(&mut msg);
    println!("{}", msg); // prints "Hello!"
}

This is one of the most common real examples you’ll see when functions need to update data in place without transferring ownership.

For more on ownership, the Rust Book on ownership is still the gold standard, even in 2025.


Examples include generics and type parameters

If you’re writing library code, generics show up quickly. Here’s an example of a generic function that works for any type that implements PartialOrd:

fn max_value<T: PartialOrd>(a: T, b: T) -> T {
    if a >= b { a } else { b }
}

fn main() {
    let x = max_value(10, 20);
    let y = max_value(3.14, 2.71);
    println!("x = {}, y = {}", x, y);
}

You’ll see this pattern a lot in modern Rust crates: type parameter T, followed by trait bounds like T: PartialOrd. This is one of the best examples of defining functions in Rust that are flexible but still strongly typed.

Another generic example that uses references:

fn first<T>(items: &[T]) -> Option<&T> {
    items.first()
}

fn main() {
    let nums = vec![1, 2, 3];
    if let Some(v) = first(&nums) {
        println!("First = {}", v);
    }
}

This is very close to patterns you’ll see in production Rust code: generic over T, borrowing a slice, and returning an Option reference.


Trait-based examples of defining functions in Rust: practical examples

Traits are Rust’s answer to interfaces. Functions that accept trait bounds are everywhere in 2024–2025 Rust ecosystems.

Example: Function with trait bounds in where clause

use std::fmt::Display;

fn log_pair<T, U>(key: T, value: U)
where
    T: Display,
    U: Display,
{
    println!("{} = {}", key, value);
}

fn main() {
    log_pair("status", 200);
    log_pair("pi", 3.1415);
}

The where clause keeps the function signature readable as constraints grow. Examples of defining functions in Rust like this are common in async code, logging, and configuration helpers.

Example: Taking trait objects

Sometimes you don’t want generics; you want dynamic dispatch:

fn print_anything(value: &dyn Display) {
    println!("{}", value);
}

fn main() {
    let n = 42;
    let s = "hello";
    print_anything(&n);
    print_anything(&s);
}

This pattern shows up in logging, plugin systems, and CLI frameworks.


Async and modern 2025-style examples of defining functions in Rust

Async Rust has matured a lot, and async fn is now standard in server code, microservices, and even some CLI tools.

Example: Basic async function

async fn fetch_data() -> Result<String, reqwest::Error> {
    let body = reqwest::get("https://httpbin.org/get").await?
        .text()
        .await?;
    Ok(body)
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let data = fetch_data().await?;
    println!("Response: {}", &data[..50.min(data.len())]);
    Ok(())
}

This is a real example of defining functions in Rust that you might see in a 2025 codebase: async, returning Result, using ? for error propagation, and integrating with the tokio runtime.

Example: Async function with generics and trait bounds

use std::time::Duration;
use tokio::time::sleep;

async fn retry<F, Fut, T, E>(mut f: F, attempts: u32) -> Result<T, E>
where
    F: FnMut() -> Fut,
    Fut: std::future::Future<Output = Result<T, E>>,
{
    let mut remaining = attempts;
    loop {
        match f().await {
            Ok(value) => return Ok(value),
            Err(e) if remaining > 1 => {
                remaining -= 1;
                sleep(Duration::from_millis(100)).await;
            }
            Err(e) => return Err(e),
        }
    }
}

This looks intimidating, but it’s one of the best examples of defining functions in Rust that show how far you can push generics and async together. Libraries like reqwest, tokio, and axum use patterns like this heavily.

For deeper async patterns, the Rust Async Book is an authoritative resource maintained by the Rust community.


Error-handling examples of defining functions in Rust: practical examples

Rust’s Result<T, E> type is everywhere. Real examples of defining functions in Rust almost always include some kind of error handling.

Example: Returning Result from a file operation

use std::fs::File;
use std::io::{self, Read};

fn read_file(path: &str) -> Result<String, io::Error> {
    let mut file = File::open(path)?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    Ok(contents)
}

fn main() -> Result<(), io::Error> {
    let data = read_file("Cargo.toml")?;
    println!("First 80 chars: {}", &data[..80.min(data.len())]);
    Ok(())
}

The ? operator keeps this tidy. This is a realistic example of defining functions in Rust you’d see in a CLI tool or small utility.

Example: Custom error type

use std::num::ParseIntError;

#[derive(Debug)]
enum ConfigError {
    MissingEnv,
    ParseError(ParseIntError),
}

fn parse_port(env: Option<String>) -> Result<u16, ConfigError> {
    let raw = env.ok_or(ConfigError::MissingEnv)?;
    let port: u16 = raw.parse().map_err(ConfigError::ParseError)?;
    Ok(port)
}

This kind of function—returning a Result with a custom error enum—is very common in configuration, parsing, and API boundary layers.

For background on error handling patterns, the official Rust error handling section is worth reading end to end.


Real examples of defining functions in Rust for iterators and closures

Rust’s iterator ecosystem is one of its strengths, and functions often appear as arguments to iterator adapters or as closures.

Example: Function as a callback

fn is_even(n: i32) -> bool {
    n % 2 == 0
}

fn main() {
    let nums = vec![1, 2, 3, 4, 5, 6];
    let evens: Vec<_> = nums.into_iter().filter(is_even).collect();
    println!("Evens: {:?}", evens);
}

This is a straightforward example of defining functions in Rust and then passing them around as first-class values.

Example: Defining small functions as closures

Sometimes it’s cleaner to define inline functions as closures:

fn main() {
    let square = |x: i32| x * x;
    let nums = vec![1, 2, 3, 4];
    let sq: Vec<_> = nums.into_iter().map(square).collect();
    println!("Squares: {:?}", sq);
}

In many codebases, you’ll see a mix of named functions and closures depending on how reusable the logic needs to be.


Module-level and associated function examples of defining functions in Rust

So far, everything has been fn in free space. Real projects organize functions into modules and impl blocks.

Example: Module-level functions

mod math {
    pub fn add(a: i32, b: i32) -> i32 {
        a + b
    }

    pub fn mul(a: i32, b: i32) -> i32 {
        a * b
    }
}

fn main() {
    let x = math::add(2, 3);
    let y = math::mul(4, 5);
    println!("x = {}, y = {}", x, y);
}

This is an example of defining functions in Rust inside a module, with pub making them visible to other modules or crates.

Example: Associated functions and methods

struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    // associated function (no self)
    fn new(width: u32, height: u32) -> Self {
        Self { width, height }
    }

    // method (takes &self)
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

fn main() {
    let rect = Rectangle::new(10, 20);
    println!("Area = {}", rect.area());
}

Associated functions like new are idiomatic Rust constructors; methods like area are how you attach behavior to data. These are some of the best examples of defining functions in Rust that feel object-oriented without being class-based.

The Rust reference on methods covers the fine details if you need them.


FAQ: short answers with more examples

Q: Can you show another small example of defining functions in Rust that return multiple values?
Yes. Rust doesn’t have native multiple return values, but tuples get you close:

fn min_max(values: &[i32]) -> Option<(i32, i32)> {
    let mut iter = values.iter();
    let first = *iter.next()?;
    let mut min = first;
    let mut max = first;

    for &v in iter {
        if v < min { min = v; }
        if v > max { max = v; }
    }
    Some((min, max))
}

This is a practical example of defining functions in Rust that return both a minimum and maximum in one shot.

Q: Are there examples of defining functions in Rust that are only used in tests?
Yes. It’s common to define helper functions inside #[cfg(test)] modules:

#[cfg(test)]
mod tests {
    fn make_sample_vec() -> Vec<i32> {
        vec![1, 2, 3]
    }

    #[test]
    fn sample_vec_len() {
        assert_eq!(make_sample_vec().len(), 3);
    }
}

Q: Where can I find more real examples of defining functions in Rust?
The official Rust Book and the Rust by Example site both include many working snippets. You can also browse popular crates on crates.io and GitHub to see how production projects structure their functions.


The bottom line: once you’ve seen enough examples of defining functions in Rust: practical examples like these, the syntax stops feeling mysterious. You’ll start recognizing patterns—ownership vs borrowing, Result for errors, async fn for I/O, trait bounds for flexibility—that repeat across almost every modern Rust project.

Explore More Rust Code Snippets

Discover more examples and insights in this category.

View All Rust Code Snippets