Rust functions

From wikinotes

Expressions vs Statements

  • statements include actions without a return value (ends in ;)
  • expressions include actions with a return value (no ;)

statement

{
    let y = 1;
    y += 1;
} // no return val

expression

let x = {
    let y = 1;
    y += 1       // <-- no semicolon
} // returns 2

Function Signatures

Params

fn main(num: u8) {
    println!("{}", num);
}

Return Values

// return void
fn foo() {
    println!("hi");
}

// single return value
fn foo() -> i32 {
    123 // <-- return value (no semicolon)
}

// multiple return values
fn foo() -> (i32, String) {
    (123, String::from("abc"))
}

Note that rust supports unpacking multiple variables.

fn foo() -> (i32, String) {
    (123, String::from("abc"))
}

let (mynum, mystr) = foo();

Returning Traits, and dyn

// Rust needs to know how much space each return object requires.
// You can't return a trait, different implementations will require different amounts of memory.
// Where you would return a trait, instead return a `Box<T>` (pointer)
fn foo() -> Box<dyn MyTrait> {
    if foo {
        Box::new(ImplA{})
    } else {
        Box::new(ImplB{})
    }
}

References

References let you pass an argument to a function without transferring ownership.
By default, references are not mutable, but the mut keyword makes it so.

// pass reference to string
fn len(s: &String) -> usize {
    s.len()
}

// if mutable, '&' placed before the keyword
fn len(s: &mut str) -> usize {
    s.len()
}

Generics

fn do_thing<T>(val: T) { ... }

See more details in rust generics.

Closures

  • closures support type inference
  • closures can refer to objects defined in their scope -- but be careful of ownership semantics

Syntax

// closure w/o params
let say_hi = || println!("hi");
say_hi();

// closure w/ params
let say_hi_to = |name| println!("hi {}", name)
let say_hi_to = |name: &str| println!("hi {}", name)
say_hi_to("alex");

// closure w/ return value
let get_name = || -> String { String::from("vaderd") }

// multiline closure
let print_three_times = |x| {
    println!("{}", x);
    println!("{}", x);
    println!("{}", x);
};

OuterScope Access/Ownership

You can access variables from the scope the closure was created in.
Closures can reference or take ownership of these variables.

// immutable reference
let list = vec![1, 2, 3];
let my_closure = || println!("{:?}", list);     // immutable closure, immutable borrow
my_closure();

// mutable reference
let mut list = vec![1, 2, 3];
let mut my_closure = || list.push(4);           // mutable closure, mutable borrow
my_closure();

// assign take ownership (ex. move to thread)
let list = vec![1, 2, 3];
let my_closure = move || println!(":?", list);  // `move` means take ownership
my_closure();

Closure Traits

Closure traits determine

  • if repeated calls are allowed
  • it's scope-variable ownership semantics

You can use them in method signatures, like other rust traits.

  • FnOnce all closures have this trait. unless another trait is applied, only callable once.
  • FnMut mutable closures without move. supports multiple calls
  • Fn immutable closures without move. supports multiple calls