Closures

We've gone over functions before, and while incredibly useful there's a few more tricks worth mentioning.

We'll also be talking about closures, an anonymous function with the ability to close over its environment, allowing the function to use and manipulate things from its environment.

Function pointers

Every function can be converted into a function pointer simply by referencing its name without calling it.

This allows for some really neat tricks, like passing in a function which represents the operation you want another function to use.

fn do_thing(op) {
    op(1, 2)
}

fn add(a, b) {
    a + b
}

fn sub(a, b) {
    a - b
}

#[test]
fn test_function_pointer() {
    assert_eq!(do_thing(add), 3);
    assert_eq!(do_thing(sub), -1);
}

pub fn main() {
    println!("Result: {}", do_thing(add));
    println!("Result: {}", do_thing(sub));
}
$> cargo run --bin rune -- run scripts/book/closures/function_pointers.rn
Result: 3
Result: -1

Closures

Closures are anonymous functions which closes over their environment. This means that they capture any variables used inside of the closure, allowing them to be used when the function is being called.

fn work(op) {
    op(1, 2)
}

#[test]
fn test_basic_closure() {
    let n = 1;
    assert_eq!(work(|a, b| n + a + b), 4);
    assert_eq!(work(|a, b| n + a * b), 3);
}

pub fn main() {
    let n = 1;
    println!("Result: {}", work(|a, b| n + a + b));
    println!("Result: {}", work(|a, b| n + a * b));
}
$> cargo run --bin rune -- run scripts/book/closures/basic_closure.rn
Result: 4
Result: 3

Hint: Closures which do not capture their environment are identical in representation to a function.

Functions outside of the Vm

Now things get really interesting. Runestick, the virtual machine driving Rune, has support for passing function pointers out of the virtual machine using the Function type.

This allows you to write code that takes a function constructed in Rune, and use it for something else.

use rune::runtime::Function;
use rune::termcolor::{ColorChoice, StandardStream};
use rune::{Diagnostics, Vm};

use std::sync::Arc;

fn main() -> rune::support::Result<()> {
    let context = rune_modules::default_context()?;
    let runtime = Arc::new(context.runtime()?);

    let mut sources = rune::sources! {
        entry => {
            fn foo(a, b) {
                a + b
            }

            pub fn main() {
                foo
            }
        }
    };

    let mut diagnostics = Diagnostics::new();

    let result = rune::prepare(&mut sources)
        .with_context(&context)
        .with_diagnostics(&mut diagnostics)
        .build();

    if !diagnostics.is_empty() {
        let mut writer = StandardStream::stderr(ColorChoice::Always);
        diagnostics.emit(&mut writer, &sources)?;
    }

    let unit = result?;

    let mut vm = Vm::new(runtime, Arc::new(unit));
    let output = vm.call(["main"], ())?;
    let output: Function = rune::from_value(output)?;

    println!("{}", output.call::<i64>((1, 3)).into_result()?);
    println!("{}", output.call::<i64>((2, 6)).into_result()?);
    Ok(())
}
$> cargo run --example rune_function
4
8

Note that these functions by necessity have to capture their entire context and can take up quite a bit of space if you keep them around while cycling many contexts or units.

Values used in a closure can also be moved into it using the move keyword, guaranteeing that no one else can use it afterwards. An attempt to do so will cause a compile error.

fn work(op) {
    op(1, 2)
}

pub fn main() {
    let n = 1;
    println!("Result: {}", work(move |a, b| n + a + b));
    assert!(!is_readable(n));
}
$> cargo run --bin rune -- run scripts/book/closures/closure_move.rn.fail
error: compile error
  ┌─ scripts/book/closures/closure_move.rn.fail:7:33
  │
7 │     println!("Result: {}", work(move |a, b| n + a + b));
  │                                 --------------------- moved here
8 │     assert!(!is_readable(n));
  │                          ^ variable moved

Moving indiscriminately applies to types which in principle could be copied (like integers). We simply don't have the necessary type information available right now to make that decision. If you know that the value can be copied and you want to do so: assign it to a separate variable.