Function Declaration and Error Handling

1. Welcome and Icebreaker:

Welcome back!

If you could create a custom error message for a real-life situation, what situation would it be, and what would the error message say?

2. Outline the Session:

Here’s our plan:

  1. Concept Explanation: We’ll cover function syntax, parameters, return types, and the different ways functions can be defined. Then, we’ll discuss unrecoverable errors and how to use panic! to signal them.
  2. Live Coding Demonstration: We’ll write code examples to demonstrate various function definitions and how to use panic! appropriately.
  3. Tasks: You’ll get exercises to practice defining functions and handling error conditions.

Learning Outcomes: By the end of this session, you will be able to:

  • Declare and define functions with different parameter types and return types.
  • Call functions and pass arguments.
  • Understand the concept of function scope.
  • Use the panic! macro to signal unrecoverable errors.
  • Understand the difference between recoverable and unrecoverable errors (we’ll cover recoverable errors in more detail later).

Expectations: Experiment with the code! Try different function signatures and see how panic! affects program execution.

3. Concept Explanation:

Function Declarations:

  • Functions are declared using the fn keyword.
  • Function names follow the snake_case convention (lowercase words separated by underscores).
  • Parameters are specified within parentheses (), with their types.
  • The return type is specified after an arrow >. If a function doesn’t return a value, the return type is omitted (it’s implicitly (), the unit type).
  • The function body is enclosed in curly braces {}.
fn add(x: i32, y: i32) -> i32 { // Function signature
    x + y  // Return value (implicit return - no semicolon)
}

fn greet(name: &str) { // No return value
    println!("Hello, {}!", name);
}

fn do_nothing() {}  //Equivalent to fn do_nothing() -> () {}

Function Scope:

  • Variables declared inside a function are only accessible within that function’s scope.
  • Functions can access variables from outer scopes (e.g., global variables), but it’s generally better to pass data as arguments.

Unrecoverable Errors and panic!:

  • panic! is a macro used to signal unrecoverable errors – situations where the program cannot continue execution safely.
  • When panic! is called, the program will:
    • Print an error message.
    • Unwind the stack (clean up resources).
    • Terminate.
  • When to use panic!:
    • Logic Errors: When your code encounters a situation that should never happen according to your program’s logic (e.g., an invalid index into an array, a null pointer dereference).
    • Unrecoverable External Errors: When an external resource (like a file or network connection) is unavailable in a way that you cannot recover from.
    • During Development/Testing: To quickly identify and halt execution when unexpected conditions occur.
    • Prototypes/Examples: It’s sometimes acceptable to use panic! in quick prototypes or examples where robust error handling is not the primary focus.
  • When not to use panic!:
    • Expected Errors: For errors that you can reasonably anticipate and handle (e.g., user input validation, file not found). Use Result for these (covered in a later session).

4. Live Coding Demonstration:

fn add(x: i32, y: i32) -> i32 {
    x + y // Implicit return
}

fn subtract(x: i32, y: i32) -> i32 {
    return x - y; // Explicit return
}

fn greet(name: &str) {
    println!("Hello, {}!", name);
}

fn divide(x: f64, y: f64) -> f64 {
    if y == 0.0 {
        panic!("Division by zero!"); // Unrecoverable error
    }
    x / y
}
// Function with multiple return
fn get_name_and_age() -> (&'static str, u32) {
    ("Alice", 30)
}
// Function with no parameters and no return
fn print_message() {
    println!("This function has no parameters and returns nothing.");
}

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

    let difference = subtract(10, 4);
    println!("Difference: {}", difference);

    greet("Bob");

    let result = divide(10.0, 2.0);
    println!("Result: {}", result);

    // Uncommenting this line will cause a panic:
    // let result2 = divide(5.0, 0.0);

     let (name, age) = get_name_and_age();
    println!("Name: {}, Age: {}", name, age);

    print_message();

    // Example of accessing an array out of bounds (will panic)
    let numbers = [1, 2, 3];
    // let invalid = numbers[5]; // This would panic!
}

Key Takeaways:

  • Function Definitions: We demonstrate various function signatures with and without return types.
  • Implicit vs. Explicit Return: The last expression in a function without a semicolon is implicitly returned. You can also use the return keyword.
  • panic!: We use panic! to handle the unrecoverable error of division by zero.
  • Out-of-Bounds Access: Accessing an array element with an invalid index is a common cause of panics.
  • Multiple Return: Return multiple value by wrapping with Tuples
  • No Parameter and Return: We can create function without no parameter and return.

5. Tasks

  1. Calculator Functions: Write functions for multiplication (multiply), integer division (divide_int), and modulo (modulo) operations. Each function should take two integer arguments and return an integer. The divide_int function should panic! if the second argument is zero.

  2. String Length Function: Write a function called string_length that takes a string slice (&str) as an argument and returns its length as a usize.

  3. Greeting Function with Optional Age: Write a function called greet_person that takes a name (&str) and an optional age (Option<u32>) as arguments. If the age is provided (i.e., Some(age)), the function should print “Hello, {name}! You are {age} years old.”. If the age is not provided (i.e., None), it should print “Hello, {name}!”. (We haven’t covered Option in detail yet, so here’s how to use it for this task):

    fn greet_person(name: &str, age: Option<u32>) {
        match age {
            Some(age) => println!("Hello, {}! You are {} years old.", name, age),
            None => println!("Hello, {}!", name),
        }
    }
    
    // Example usage
    greet_person("Alice", Some(30));
    greet_person("Bob", None);
    
  4. Array Access with Panic: Write a function called get_element that takes an array of integers ([i32; N], where N is a constant size) and an index (usize) as arguments. The function should return the element at the given index. If the index is out of bounds, the function should panic! with a descriptive error message.

  5. Intentional Panic: Write a small program that intentionally calls panic! with a custom error message. Run the program and observe the output.

  6. Multiple return: Write a function that given two number and do addition and multiplication. this function return two type.