Functions in Rust

About This Module

This module covers Rust function syntax, return values, parameters, and the unit type. Functions are fundamental building blocks in Rust programming, and understanding their syntax and behavior is essential for writing well-structured Rust programs.

Prework

Prework Readings

Read the following sections from "The Rust Programming Language" book:

Pre-lecture Reflections

Before class, consider these questions:

  1. How do functions help organize and structure code?
  2. What are the benefits of explicit type annotations in function signatures?
  3. How do return values differ from side effects in functions?
  4. What is the difference between expressions and statements in function bodies?
  5. How might Rust's approach to functions differ from other languages you know?

Learning Objectives

By the end of this module, you should be able to:

  • Define functions with proper Rust syntax
  • Understand parameter types and return type annotations
  • Use both explicit return statements and implicit returns
  • Work with functions that return no value (unit type)
  • Apply best practices for function design and readability
  • Understand the difference between expressions and statements in function bodies

Function Syntax

Syntax:

fn function_name(argname_1:type_1,argname_2:type_2) -> type_ret {
    DO-SOMETHING-HERE-AND-RETURN-A-VALUE
}
  • No need to write "return x * y"
  • Last expression is returned
  • No semicolon after the last expression
fn multiply(x:i32, y:i32) -> i32 {
    // note: no need to write "return x * y"
    x * y
}

fn main() {
    println!("{}", multiply(10,20))
}

Exercise: Try putting a semicolon after the last expression. What happens?

Functions returns

  • But if you add a return then you need a semicolon
    • unless it is the last statement in the function
  • Recommend using returns and add semicolons everywhere.
    • It's easier to read.
fn and(p:bool, q:bool, r:bool) -> bool {
    if !p {
        println!("p is false");
        return false;
    }
    if !q {
        println!("q is false");
        return false;
    }
    println!("r is {}", r);
    r // return r without the semicolon also works here
}

fn main() {
    println!("{}", and(true,false,true))
}

Functions: returning no value

How: skip the type of returned value part

fn say_hello(who:&str) {
    println!("Hello, {}!",who);
}

fn main() {
    say_hello("world");
    say_hello("Boston");
    say_hello("DS210");
}

Nothing returned equivalent to the unit type, ()

fn say_good_night(who:&str) -> () {
    println!("Good night {}",who);
}

fn main() {
    say_good_night("room");
    say_good_night("moon");
    let z = say_good_night("cow jumping over the moon");
    println!("The function returned {:?}", z)
}

Unit Type Characteristics:

  • Empty tuple: ()
  • Zero size: Takes no memory
  • Default return: When no value is explicitly returned
  • Side effects only: Functions that only perform actions (printing, file I/O, etc.)

Parameter Handling

Multiple Parameters:

#![allow(unused)]
fn main() {
fn calculate_area(length: f64, width: f64) -> f64 {
    length * width
}

fn greet_person(first_name: &str, last_name: &str, age: u32) {
    println!("Hello, {} {}! You are {} years old.", 
             first_name, last_name, age);
}
}

Parameter Types:

  • Ownership: Parameters can take ownership (String)
  • References: Parameters can borrow (&str, &i32)
  • Primitive types: Copied by default (i32, bool, f64)

Function Design Principles

Single Responsibility:

// Good: Single purpose
fn calculate_tax(price: f64, tax_rate: f64) -> f64 {
    price * tax_rate
}

// Good: Clear separation of concerns
fn format_currency(amount: f64) -> String {
    format!("${:.2}", amount)
}

fn display_total(subtotal: f64, tax_rate: f64) {
    let tax = calculate_tax(subtotal, tax_rate);
    let total = subtotal + tax;
    println!("Total: {}", format_currency(total));
}

fn main() {
    display_total(100.0, 0.08);
}

Pure Functions vs. Side Effects:

#![allow(unused)]
fn main() {
// Pure function: No side effects, deterministic
fn add(x: i32, y: i32) -> i32 {
    x + y
}

// Function with side effects: Prints to console
fn add_and_print(x: i32, y: i32) -> i32 {
    let result = x + y;
    println!("{} + {} = {}", x, y, result);
    result
}
}

Common Patterns

Validation Functions:

#![allow(unused)]
fn main() {
fn is_valid_age(age: i32) -> bool {
    age >= 0 && age <= 150
}

fn is_valid_email(email: &str) -> bool {
    email.contains('@') && email.contains('.')
}
}

Conversion Functions:

#![allow(unused)]
fn main() {
fn celsius_to_fahrenheit(celsius: f64) -> f64 {
    celsius * 9.0 / 5.0 + 32.0
}

fn fahrenheit_to_celsius(fahrenheit: f64) -> f64 {
    (fahrenheit - 32.0) * 5.0 / 9.0
}
}

Helper Functions:

#![allow(unused)]
fn main() {
fn get_absolute_value(x: i32) -> i32 {
    if x < 0 { -x } else { x }
}

fn max_of_three(a: i32, b: i32, c: i32) -> i32 {
    if a >= b && a >= c {
        a
    } else if b >= c {
        b
    } else {
        c
    }
}
}

Function Naming Conventions

Rust Naming Guidelines:

  • snake_case: For function names
  • Descriptive names: Clear indication of purpose
  • Verb phrases: For functions that perform actions
  • Predicate functions: Start with is_, has_, can_

Examples:

#![allow(unused)]
fn main() {
fn calculate_distance(x1: f64, y1: f64, x2: f64, y2: f64) -> f64 { /* ... */ }
fn is_prime(n: u32) -> bool { /* ... */ }
fn has_permission(user: &str, resource: &str) -> bool { /* ... */ }
fn can_access(user_level: u32, required_level: u32) -> bool { /* ... */ }
}

Exercise

Write a function called greet_user that takes a name and a time of day (morning, afternoon, evening) as parameters and returns an appropriate greeting string.

The function should:

  1. Take two parameters: name: &str and time: &str
  2. Return a String with a customized greeting
  3. Follow Rust naming conventions
  4. Use proper parameter types
  5. Include error handling for invalid times

Example output:

Good evening, Dumbledore!

Hint: You can format the string using the format! macro, which uses the same syntax as println!.

// Returns a String
format!("Good morning, {}!", name)
// Your code here