Rust lang series episode #18 — closures (#rust-series)

in rust-series •  8 years ago  (edited)

Hello everyone, new episode of rust series is here, today we will discuss closures in Rust. Closures, also called lambdas, are functions that can capture the enclosing environment.

Closures vs fn functions

  • you don't have to to annotate types (it's optional)
  • you don't have to annotate return type (it's optional)
  • use || instead of ()
  • body {} is optional
  • captures outer environment

Basic closure definition

fn main() {
    let power =  | x | x * x;

    let res = power(2);
    println!("Result: {}", res);
}

# output
Result: 4

This example above is pretty straight and without any issues.

Borrowing and ownership around closures

let mut addition = 2;
let plus_two = | x | x + addition;
let res2 = plus_two(10);
addition  = 3;

# output
error: cannot assign to `addition` because it is borrowed

Changing value of addition cannot be done as addition is already borrowed by closure

Even more aggressive ownership

let nums = vec![1, 2, 3];
let takes_nums = || nums;

println!("{:?}", nums);

# output
error: use of moved value: `nums`

This cannot be done as closure must take ownership of vector content here. Note that this is not an issue with array or other read-only structures or those that can by simply copied.

Ownership of the copy

Rust provides keyword move so closure can take ownership of its environment. Sound strange but basically it creates copy of a value used inside closure.

let mut num = 10;

let mut add_num = move |x: i32| {
    num += x;
    println!("num value in closure: {}", num);
};


println!("num value before calling closure: {}", num);
add_num(10);
println!("num value after calling closure: {}", num);

# output
num value before calling closure: 10
num value in closure: 20
num value after calling closure: 10

Version using the original

Now check this code below that is using the original variable. Note that variable scope is slightly updated not to interfere with ownership protection.

let mut num = 10;

println!("num value before calling closure: {}", num);
{
    let mut add_num = |x: i32| {
        num += x;
        println!("num value in closure: {}", num);
    };


    add_num(10);
}
println!("num value after calling closure: {}", num);

# output
num value before calling closure: 10
num value in closure: 20
num value after calling closure: 20

Now we truly work with original num variable, not a copy.

Closure as function argument

Closure can be used as an regular function argument.

fn main() {
    fn call_with_ten<F>(some_closure: F) -> i32
        where F: Fn(i32) -> i32 {
        some_closure(10)
    }

    let powered_result = call_with_ten(|x| x * x);
    let half_result = call_with_ten(|x| x / 2);
    println!("pow result: {}", powered_result);
    println!("half result: {}", half_result);
}

# output
pow result: 100
half result: 5

We've defined function that will call various closures with argument 10.

Closure as function pointer

We can also use functions as closure input for other function, check code below providing the same functionality as last example.

fn main() {
    fn call_with_ten(some_closure: &Fn(i32) -> i32)  -> i32 {
        some_closure(10)
    }

    fn powered_function(i: i32) -> i32{
        i * i
    }

    fn half_function(i: i32) -> i32 {
        i / 2
    }

    let powered_result = call_with_ten(&powered_function);
    let half_result = call_with_ten(&half_function);

    println!("pow result: {}", powered_result);
    println!("half result: {}", half_result);
}

# output
pow result: 100
half result: 5

Closure factory

Closure factory is some function that returns closure. It's quite common pattern. There are some principle that must be used to cross some limitations

  • we need to Box Fn to cross limited closure environment
  • we need to copy factory scoped variables (if any)

After applying these principles we can create factory like this

fn main() {
    fn factory_function() -> Box<Fn(i32) -> i32> {
        let num = 10;

        Box::new(move |x| x + num)
    }
    let produced_function = factory_function();
    let result = produced_function(10);

    println!("result: {}", result );
}

# output
result: 20

Postfix

That's all for today, thank you for your appreciations, feel free to comment and point out possible mistakes (first 24 hours works the best but any time is fine). May Jesus bless your programming skills, use them wisely and see you next time.

Meanwhile you can also check the official documentation for closures and if you wish.

#rust-series
#rust-lang
#rust

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!