Rust's Closure

@shulhi

What's a closure?

A function that can directly use variables from the scope in which it is defined

fn main() {
    let text = String::from("Hello");
    let my_closure = || {
        // text is called _captured_ variable
        do_something(&text)
    };

    my_closure();
}
fn add_one(x: i32) -> impl Fn() -> i32 {
    let one = 1;
    || x + one
}

fn main() {
    let five = 5;
    let calculate = add_one(five);
    println!("{}", calculate());
}
function add_one(x) {
    var one = 1;
    return function() {
        one + x;
    }
}

function main() {
    var five = 5;
    var closure = add_one(five);
    var result = closure();
    print(result);
    ...
}

Language with GC

Rust (No GC)

demo: step-1

function add_one(x) {
    var one = 1;
    return one + x;
}

function main() {
    var five = 5;
    var result = add_one(five);
    print(result);
    ...
}

add_one()

one: 1

result: 6

one

main()

result

five

five: 5

Stack Frames

Heap

function add_one(x) {
    var one = 1;
    return function() {
        return one + x;
    }
}

function main() {
    var five = 5;
    var calculate = add_one(five);
    print(calculate());
    ...
    something();
}

add_one()

one: 1

calculate: fn

one

main()

calculate

five

five: 5

Stack Frames

Heap

add_one()

calculate: fn

one

main()

calculate

five

five: 5

Stack Frames

*Heap

Rust's solution: Borrow (default) or Move

one: 1

*depends where you initialize the data

one: 1

calculate: fn

main()

calculate

five

five: 5

Stack Frames

*Heap

Rust's solution: Move & Borrow

*depends where you initialize the data

Closure's Implementation

How it really works?

Fn: &self
FnOnce: self
FnMut: &mut self

Traits

How Rust determines the trait to implement?

Consume its captured variables?

Yes

No

Doesn't mutate its captured variables?

Fn

FnMut

FnOnce

FnOnce

How Rust determines the trait to implement?

let s = String::from("hello world");
let my_fnonce = move || { s };
let s = "hello world";
let my_fn = move || { s.length() }

The move modifier controls how captures are moved into the closure when it's created. FnMut membership is determined by how captures are moved out of the closure (or consumed in some other way) when it's executed.

fn main() {
    let some_num = 5;

    let some_closure = || {
        let x = some_num;
        x + 1;
    };
}
fn main::{{closure}}(_1: &[closure@src/main.rs:4:24: 7:6 some_num:&i32]) -> (){
    let mut _0: ();                      // return place
    scope 1 {
    }
    scope 2 {
        let _2: i32;                     // "x" in scope 2 at src/main.rs:5:13: 5:14
    }
    let mut _3: i32;
    let mut _4: i32;
    let mut _5: (i32, bool);

    bb0: {                              
        StorageLive(_2);                 // bb0[0]: scope 0 at src/main.rs:5:13: 5:14
        _2 = (*((*_1).0: &i32));         // bb0[1]: scope 0 at src/main.rs:5:17: 5:25
        StorageLive(_4);                 // bb0[2]: scope 1 at src/main.rs:6:9: 6:10
        _4 = _2;                         // bb0[3]: scope 1 at src/main.rs:6:9: 6:10
        _5 = CheckedAdd(move _4, const 1i32); // bb0[4]: scope 1 at src/main.rs:6:9: 6:14
    }

MIR output

fn main() {
    let some_num: i32 = 5;

    let some_closure = || {
        let x: ??? = some_num;
        x + 1;
    };
}

What is the type of x?

&i32 or i32

fn main() {
    let some_num = 5;

    let some_closure = move || {
        let x = some_num;
        x + 1;
    };
}
fn main::{{closure}}(_1: &[closure@src/main.rs:4:24: 7:6 some_num:i32]) -> (){
    let mut _0: ();                      // return place
    scope 1 {
    }
    scope 2 {
        let _2: i32;                     // "x" in scope 2 at src/main.rs:5:13: 5:14
    }
    let mut _3: i32;
    let mut _4: i32;
    let mut _5: (i32, bool);

    bb0: {                              
        StorageLive(_2);                 // bb0[0]: scope 0 at src/main.rs:5:13: 5:14
        _2 = ((*_1).0: i32);             // bb0[1]: scope 0 at src/main.rs:5:17: 5:25
        StorageLive(_4);                 // bb0[2]: scope 1 at src/main.rs:6:9: 6:10
        _4 = _2;                         // bb0[3]: scope 1 at src/main.rs:6:9: 6:10
        _5 = CheckedAdd(move _4, const 1i32); // bb0[4]: scope 1 at src/main.rs:6:9: 6:14
    }

MIR output

Closure in Rust

By Shulhi Sapli

Closure in Rust

  • 1,023