Field functions

Field functions are special operations which operate on fields. These are distinct from associated functions, because they are invoked by using the operation associated with the kind of the field function.

The most common forms of fields functions are getters and setters, which are defined through the Protocol::GET and Protocol::SET protocols.

The Any derive can also generate default implementations of these through various #[rune(...)] attributes:

#[derive(Any)]
struct External {
    #[rune(get, set, add_assign, copy)]
    number: i64,
    #[rune(get, set)]
    string: String,
}

Once registered, this allows External to be used like this in Rune:

pub fn main(external) {
    external.number = external.number + 1;
    external.number += 1;
    external.string = `${external.string} World`;
}

The full list of available field functions and their corresponding attributes are:

ProtocolAttribute
Protocol::GET#[rune(get)]For getters, like external.field.
Protocol::SET#[rune(set)]For setters, like external.field = 42.
Protocol::ADD_ASSIGN#[rune(add_assign)]The += operation.
Protocol::SUB_ASSIGN#[rune(sub_assign)]The -= operation.
Protocol::MUL_ASSIGN#[rune(mul_assign)]The *= operation.
Protocol::DIV_ASSIGN#[rune(div_assign)]The /= operation.
Protocol::BIT_AND_ASSIGN#[rune(bit_and_assign)]The &= operation.
Protocol::BIT_OR_ASSIGN#[rune(bit_or_assign)]The bitwise or operation.
Protocol::BIT_XOR_ASSIGN#[rune(bit_xor_assign)]The ^= operation.
Protocol::SHL_ASSIGN#[rune(shl_assign)]The <<= operation.
Protocol::SHR_ASSIGN#[rune(shr_assign)]The >>= operation.
Protocol::REM_ASSIGN#[rune(rem_assign)]The %= operation.

The manual way to register these functions is to use the new Module::field_function function. This clearly showcases that there's no relationship between the field used and the function registered:

use rune::{Any, Module};
use rune::runtime::Protocol;

#[derive(Any)]
struct External {
}

impl External {
    fn field_get(&self) -> String {
        String::from("Hello World")
    }
}

let mut module = Module::new();
module.field_function(&Protocol::GET, "field", External::field_get)?;

Would allow for this in Rune:

pub fn main(external) {
    println!("{}", external.field);
}

Custom field function

Using the Any derive, you can specify a custom field function by using an argument to the corresponding attribute pointing to the function to use instead.

The following uses an implementation of add_assign which performs checked addition:

use rune::runtime::{VmError, VmResult};
use rune::termcolor::{ColorChoice, StandardStream};
use rune::{Any, ContextError, Diagnostics, Module, Vm};

use std::sync::Arc;

#[derive(Any)]
struct External {
    #[rune(add_assign = External::value_add_assign)]
    value: i64,
}

#[allow(clippy::unnecessary_lazy_evaluations)]
impl External {
    fn value_add_assign(&mut self, other: i64) -> VmResult<()> {
        self.value = rune::vm_try!(self.value.checked_add(other).ok_or_else(VmError::overflow));
        VmResult::Ok(())
    }
}

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(e) {
                e.value += 1;
            }
        }
    };

    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 input = External { value: i64::MAX };
    let err = vm.call(["main"], (input,)).unwrap_err();
    println!("{:?}", err);
    Ok(())
}

fn module() -> Result<Module, ContextError> {
    let mut m = Module::new();
    m.ty::<External>()?;
    Ok(m)
}
$> cargo run --example checked_add_assign
Error: numerical overflow (at inst 2)