Home Wiki Programming & Logic Functions in Rust: Building Reusable Software Components
Programming & Logic

Functions in Rust: Building Reusable Software Components

Defining and Calling Functions

Functions are the fundamental building blocks for organizing code. In Rust, they are defined with fn and require explicit types for parameters and return values:

fn read_temperature(sensor_id: u8) -> f64 {
    // simulate sensor reading
    let raw_value = sensor_id as f64 * 10.0 + 22.5;
    raw_value
}

fn main() {
    let temp = read_temperature(3);
    println!("Temperature: {:.1}°C", temp);
}

Basic rules:

  • Function names use snake_case: read_temperature not readTemperature
  • Every parameter must have an explicit type: sensor_id: u8
  • Return type is specified after ->: -> f64
  • If nothing is returned, omit -> (implicitly returns ())

Parameters and Return Values

Multiple Parameters

fn calibrate_reading(raw: f64, offset: f64, scale: f64) -> f64 {
    (raw + offset) * scale
}

fn main() {
    let calibrated = calibrate_reading(1024.0, -50.0, 0.1);
    println!("Calibrated reading: {:.2}", calibrated); // 97.40
}

Functions Without Return Values

fn log_alarm(machine_id: u32, message: &str) {
    println!("[ALARM] Machine {}: {}", machine_id, message);
}

Returning Multiple Values with Tuples

fn sensor_stats(readings: &[f64]) -> (f64, f64) {
    let min = readings.iter().cloned().fold(f64::INFINITY, f64::min);
    let max = readings.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
    (min, max)
}

fn main() {
    let data = [23.5, 24.1, 22.8, 25.0, 23.9];
    let (min, max) = sensor_stats(&data);
    println!("Min: {:.1}°C, Max: {:.1}°C", min, max);
}

Expressions as Return Values

In Rust, the last expression in a function without a semicolon is automatically the return value:

fn is_overheating(temp: f64) -> bool {
    temp > 80.0  // no semicolon = this is the return value
}

// equivalent to:
fn is_overheating_explicit(temp: f64) -> bool {
    return temp > 80.0; // explicit return with semicolon
}

The convention: avoid return unless you need to exit early from the middle of a function:

fn validate_reading(value: f64) -> Result<f64, String> {
    if value < -40.0 {
        return Err("Reading below sensor minimum".to_string());
    }
    if value > 150.0 {
        return Err("Reading above sensor maximum".to_string());
    }
    Ok(value) // normal return — no return keyword
}

Closures: Anonymous Functions

Closures are compact functions that can capture variables from their surrounding environment:

fn main() {
    let calibration_factor = 0.1;
    let offset = -50.0;

    // closure captures calibration_factor and offset from the environment
    let calibrate = |raw: f64| -> f64 {
        (raw + offset) * calibration_factor
    };

    let readings = vec![1024.0, 1050.0, 980.0, 1100.0];

    for raw in &readings {
        println!("Raw: {} → Calibrated: {:.2}", raw, calibrate(*raw));
    }
}

Closures are especially useful with iterators:

let readings = vec![23.5, 85.2, 24.1, 90.0, 22.8];

// filter only high readings
let alarms: Vec<&f64> = readings
    .iter()
    .filter(|&&temp| temp > 80.0)
    .collect();

println!("Alarms: {:?}", alarms); // [85.2, 90.0]

Introduction to Modules: Organizing Code

As a project grows, you need to split code into sections. Rust uses modules for organization:

// defining a module in the same file
mod sensors {
    pub fn read_temperature(id: u8) -> f64 {
        id as f64 * 10.0 + 22.5
    }

    pub fn read_pressure(id: u8) -> f64 {
        id as f64 * 5.0 + 1013.0
    }

    // private function — cannot be called from outside the module
    fn raw_to_calibrated(raw: u16) -> f64 {
        raw as f64 / 10.0
    }
}

mod alarms {
    pub fn check_temperature(temp: f64) -> bool {
        temp > 80.0
    }
}

fn main() {
    let temp = sensors::read_temperature(3);
    if alarms::check_temperature(temp) {
        println!("Temperature alarm!");
    }
}
  • pub: makes the function visible from outside the module
  • Without pub: the function is private (default)
  • :: accesses items within a module

We will explore modules and crates in depth in a later lesson. The core idea: separate each responsibility (sensors, alarms, logging) into its own module.

Summary

Functions in Rust require explicit types for parameters and return values — this catches errors at compile time. The last expression without a semicolon is the return value. Closures capture variables from their environment and are heavily used with iterators. Modules organize code into clear sections. In the next lesson, we will dive into the most important concept in Rust — the ownership system.

functions parameters return closures modules scope الدوال المعاملات القيمة المرجعة النطاق الوحدات البرمجية الإغلاقات