» Quick Introduction to Rust » 1. Basics » 1.9 Functions

Functions

Functions are declared using the fn keyword. Rust code uses snake case as the conventional style for function and variable names, in which all letters are lowercase and underscores separate words.

fn factorial(n: u64) -> u64 {
    if n == 0 {
        1
    } else {
        n * factorial(n - 1)
    }
}

fn main() {
    let n = 5;
    let result = factorial(n);
    println!("Factorial of {} is: {}", n, result);
    // Factorial of 5 is: 120
}

Methods

Some functions are connected to a particular type. These come in two forms: associated functions, and methods.

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

impl Rectangle {
    // Associated function to create a new Rectangle instance
    fn new(width: u32, height: u32) -> Rectangle {
        Rectangle { width, height }
    }
}

fn main() {
    // Call the associated function to create a new Rectangle
    let rectangle = Rectangle::new(10, 20);
    println!("Rectangle width: {}", rectangle.width); // Rectangle width: 10
    println!("Rectangle height: {}", rectangle.height); // Rectangle height: 20
}

new(...) is an associated function because it's associated with a particular type, that is, Rectangle. Associated functions don't need to be called with an instance.

struct Circle {
    radius: f64,
}

impl Circle {
    // Method to calculate the area of a Circle instance
    fn calculate_area(&self) -> f64 {
        std::f64::consts::PI * self.radius * self.radius
    }
}

fn main() {
    let circle = Circle { radius: 5.0 };
    // Call the method to calculate the area of the circle
    let area = circle.calculate_area();
    println!("Circle area: {}", area);
    // Circle area: 78.53981633974483
}

calculate_area() is a method because it's an associated function and is called on a particular instance of type.

Closures

Closures are functions that can capture the enclosing environment. For instance, |v| v + x is a closure that captures the x variable.

In Rust, clousres use || instead of () around input variables. Body delimination {} is optional for single expression closures.

fn main() {
    let outer_var = 58;
    let closure_annotated = |i: i32| -> i32 { i + outer_var };
    let closure_inferred  = |i| i + outer_var  ;
    println!("closure_annotated: {}", closure_annotated(5));
    // closure_annotated: 63
    println!("closure_inferred: {}", closure_inferred(5));
    // closure_inferred: 63
}

When taking a closure as an input parameter, the closure's complete type must be annotated using one of a few traits shown below.

  • Fn: the closure uses the captured value by reference (&T)
  • FnMut: the closure uses the captured value by mutable reference (&mut T)
  • FnOnce: the closure uses the captured value by value (T)
fn apply<F>(f: F) where F: FnOnce() {
    // The closure takes no input and returns nothing.
    f();
}

fn main() {
    let greeting = "hello";
    let c = || {
        println!("I said {}.", greeting);
    };
    apply(c); // I said hello.
}

Diverging Functions

Diverging functions never return. They are marked with !, which is an empty type.

fn bar() -> ! {
    panic!("Something is wrong.")
}

Code Challenge

Try to modify the clousres code provided in the editor to output [4, 16, 36, 64, 100].

Loading...
> code result goes here
Prev
Next