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.