[go: up one dir, main page]

Functions

Named Functions

  • Named functions are declared with the keyword fn
  • When using arguments, we must declare the data types.
  • By default, functions return an empty tuple/ (). If you want to return a value, the return type must be specified after ->

Hello world

fn main() {
    println!("Hello, world!");
}

Passing Arguments

fn print_sum(a: i8, b: i8) {
    println!("sum is: {}", a + b);
}

Returning Values

  • Without the return keyword. Only the last expression returns.

    fn plus_one(a: i32) -> i32 {
        a + 1
        // There is no ending ; in the above line.
        // It means this is an expression which equals to `return a + 1;`.
    }
    
  • With the return keyword.

    fn plus_two(a: i32) -> i32 {
        return a + 2;
        // Should use return keyword only on conditional/ early returns.
        // Using return keyword in the last expression is a bad practice.
    }
    

Function Pointers as a Data Type

fn main() {
    let p1 = plus_one; // Without type declarations
    let a = p1(5); // 6

    let p1: fn(i32) -> i32 = plus_one; // With the type declarations
    let b = p1(5); // 6
}

fn plus_one(i: i32) -> i32 {
    i + 1
}

Closures

  • Also known as anonymous functions or lambda functions.
  • The data types of arguments and returns are optional ⃰ⁱᡛ.

Example with a named function, before using closures.

fn main() {
    let x = 2;
    println!("{}", get_square_value(x));
}

fn get_square_value(i: i32) -> i32 {
    i * i
}

With Optional Type Annotations

fn main() {
    let x = 2;
    let square = |i: i32| -> i32 { // Input parameters are passed inside | | and expression body is wrapped within { }
        i * i
    };
    println!("{}", square(x));
}

Without Type Annotations

fn main() {
    let x = 2;
    let square = |i| i * i; // { } are optional for single-lined closures
    println!("{}", square(x));
}

With Optional Type Annotations; Define and Call Together

fn main() {
    let x = 2;
    let x_square = |i: i32| -> i32 { i * i }(x); // { } are mandatory while creating and calling same time.
    println!("{}", x_square);
}

Without Type Annotations; Define and Call Together

fn main() {
    let x = 2;
    let x_square = |i| -> i32 { i * i }(x); // ⭐️ The return type is mandatory.
    println!("{}", x_square);
}

Test Functions

  • Start the function name with the test_ prefix.
  • Add with the #[test] attribute, inside a tests module with the #[cfg(test)] attribute.
fn greet() -> String {
    "Hello, world!".to_string()
}

#[cfg(test)]
mod tests {
    use super::greet; // πŸ’‘ Reimport the greet() function from the parent module.

    #[test]
    fn test_greet() { // The test function of greet()
        assert_eq!("Hello, world!", greet());
    }
}

πŸ‘¨β€πŸ« Before going to the next…

  • πŸ’― Usage of :: and . to call functions in different modules,

    πŸ’­ This is a quick reference about the usage of :: and . operators while calling functions. So, please don’t worry about structs, enums, traits, or impls for now. We will discuss them later.

    • Functions are standalone blocks of code, declare with the fn keyword.

    • Associated functions are functions that are associated with a particular data type such as structs, enums, or traits via an impl block.

    • Methods are associated functions with a receiver of self, &self, &mut self, self: Box<Self> etc.

    ⭐️ To call methods: use the . operator from an instance. ex. steve.intro_name()

    ⭐️ To call associated functions that are not methods: use the :: operator from the data type. ex. Person::new(), String::from()

    struct Person {
        name: String,
        company_name: String,
    }
    
    impl Person { // πŸ’‘ impls are used to define functions in Rust structs, enums, etc.
        // πŸ’‘ The constructor (new` is a conventional name, not a keyword)
        fn new(name: String, company_name: String) -> Person { // an associated function and not a method
            Person { name, company_name }
        }
    
        fn intro_name(&self) -> String { // πŸ’‘ a method
            format!("I'm {}", self.name) // πŸ’‘ access fields via `.` operator
        }
    
        fn intro_company(&self) -> String { // πŸ’‘ a method
            format!("I'm from {}", self.company_name)
        }
    }
    
    fn main() {
        // πŸ’‘ calling associated functions with `::` operator
        let steve = Person::new(String::from("Steve Jobs"), String::from("Apple"));
    
        // πŸ’‘ calling methods with `.` operator
        println!("{}. {}.", steve.intro_name(), steve.intro_company()); // I'm Steve Jobs. I'm from Apple.
    
        // ⭐️ methods are also associated functions. So, we can call them with `::` operator as well but need to pass the instance as a parameter.
        println!("{}. {}.", Person::intro_name(&steve), Person::intro_company(&steve)); // I'm Steve Jobs. I'm from Apple.
    }
    
    • Other than that, :: operator is used to call functions in different modules.
    mod my_mod {
        pub fn greet(name: &str) {
            println!("Hello, {name}!")
        }
    }
    
    fn main() {
        my_mod::greet("Steve Jobs"); // Hello, Steve Jobs!
    }
    

    πŸ”Ž Refer path separator and member access operators for more information about the usage of the :: and . operators.