GGistDev

Functions in Rust

Functions declare inputs and outputs with explicit types. Rust has expression-based returns and strong inference at call sites.

Signatures and return values

fn add(a: i32, b: i32) -> i32 {
    a + b // last expression returns
}

fn greet(name: &str) -> String {
    format!("Hello, {name}!")
}

Use return expr; to return early.

Ownership and borrowing parameters

Choose by-value or by-reference based on ownership.

fn len(s: &str) -> usize { s.len() }      // borrow
fn take(v: Vec<i32>) { /* consumes */ }   // move ownership

Prefer borrowing &T/&mut T to avoid unnecessary moves.

Lifetimes (when needed)

Elision covers many cases; add lifetimes to relate inputs/outputs.

fn first<'a>(s: &'a str) -> &'a str { &s[..1] }

Generics and trait bounds

fn max<T: Ord>(a: T, b: T) -> T { if a >= b { a } else { b } }

fn print_all<T: std::fmt::Display>(xs: &[T]) {
    for x in xs { println!("{x}"); }
}

Use where for readability with many bounds.

Result and error propagation

Return Result for fallible functions and use ? to propagate.

use std::fs::File; use std::io::{self, Read};

fn read_all(path: &str) -> io::Result<String> {
    let mut s = String::new();
    File::open(path)?.read_to_string(&mut s)?;
    Ok(s)
}

Associated functions vs methods

Associated functions live in impl blocks; self, &self, or &mut self are method receivers.

struct Counter { n: u64 }
impl Counter {
    fn new() -> Self { Self { n: 0 } }        // associated
    fn inc(&mut self) { self.n += 1 }         // method
}

Diverging functions and never type

Use -> ! for functions that never return.

fn fail(msg: &str) -> ! { panic!("{msg}") }

Closures (preview)

Inline anonymous functions capture environment by reference or move.

let add = |x: i32, y: i32| x + y;

Summary

  • Choose ownership vs borrowing in parameters deliberately
  • Use generics and trait bounds to write reusable functions
  • Return Result and use ?; implement methods/associated fns in impl