Instance functions

Instance functions are functions that are associated to a specific type of variable. When called they take the form value.foo(), where the instance is the first part value. And the instance function is foo().

These are a bit special in Rune. Since Rune is a dynamic programming language we can't tell at compile time which instance any specific value can be. So instance functions must be looked up at runtime.

struct Foo;

impl Foo {
    fn new() {
        Foo
    }
}

let foo = Foo::new();
foo.bar();
$> cargo run -- run scripts/book/instance_functions/missing_instance_fn.rn
error: virtual machine error
   ┌─ scripts/book/instance_functions/missing_instance_fn.rn:11:5
   │
11 │     foo.bar();
   │     ^^^^^^^^^ missing instance function `0xfb67fa086988a22d` for `type(0xc153807c3ddc98d7)``

Note: The error is currently a bit nondescript. But in the future we will be able to provide better diagnostics by adding debug information.

What you're seeing above are type and function hashes. These uniquely identify the item in the virtual machine and is the result of a deterministic computation based on its item. So the hash for the item Foo::new will always be the same.

In Rust, we can calculate this hash using the Hash::type_hash method:

use rune::{Hash, ItemBuf};

fn main() -> rune::support::Result<()> {
    println!("{}", Hash::type_hash(&ItemBuf::with_item(["Foo", "new"])?));
    println!("{}", Hash::type_hash(["Foo", "new"]));
    Ok(())
}
$> cargo run --example function_hash
0xb5dc92ab43cb37d9
0xb5dc92ab43cb37d9

The exact implementation of the hash function is currently not defined, but will be stabilized and documented in a future release.

Defining instance functions in Rust

Native instance functions are added to a runtime environment using the Module::associated_function function. The type is identified as the first argument of the instance function, and must be a type registered in the module using Module::ty.

use rune::termcolor::{ColorChoice, StandardStream};
use rune::{ContextError, Diagnostics, Module, Vm};

use std::sync::Arc;

#[rune::function(instance)]
fn divide_by_three(value: i64) -> i64 {
    value / 3
}

#[tokio::main]
async fn main() -> rune::support::Result<()> {
    let m = module()?;

    let mut context = rune_modules::default_context()?;
    context.install(m)?;
    let runtime = Arc::new(context.runtime()?);

    let mut sources = rune::sources!(entry => {
        pub fn main(number) {
            number.divide_by_three()
        }
    });

    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.execute(["main"], (33i64,))?.complete().into_result()?;
    let output: i64 = rune::from_value(output)?;

    println!("output: {}", output);
    Ok(())
}

fn module() -> Result<Module, ContextError> {
    let mut m = Module::with_item(["mymodule"])?;
    m.function_meta(divide_by_three)?;
    Ok(m)
}
$> cargo run --example custom_instance_fn
output: 11

For more examples on how modules can be used you can have a look at the source for the rune-modules crate.