Skip to content

Native Functions

Native functions expose Rust functionality to scripts without exposing Rust state pointers. They are registered with stable names, signatures, effects, and reflection access metadata.

Pure functions receive copied script values and return copied script values.

fn bonus_manual(amount: i64, multiplier: i64) -> i64 {
amount * multiplier
}
let engine = Engine::builder()
.register_typed_native_fn::<(i64, i64), _>(
NativeFunctionDesc::new("game::bonus_manual", NativeFunctionId::new(10_001))
.param("amount", TypeHint::i64())
.param("multiplier", TypeHint::i64())
.returns(TypeHint::i64())
.effects(EffectSet::pure())
.access(FunctionAccess::public().reflect_callable(true)),
bonus_manual,
)
.build()?;
fn main() {
return game::bonus_manual(3, 4);
}

Functions that need host services use NativeCallContext. This is the allowed entry point for capabilities, budgets, and HostAccess.

#[script_context_function(name = "game::grant_level", effect = "write_host")]
fn grant_level(
ctx: &mut NativeCallContext<'_, '_>,
player: HostRef,
amount: i64,
) -> vela_vm::error::VmResult<i64> {
let path = Player::vela_field_path_level(player);
ctx.add_path(
path.clone(),
HostValue::Scalar(vela_common::ScalarValue::I64(amount)),
None,
)?;
match ctx.read_path(&path, None)? {
HostValue::Scalar(vela_common::ScalarValue::I64(level)) => Ok(level),
_ => Ok(0),
}
}

Native functions are not overloaded. Use one public name per callable. If a native needs to mutate host state, it must go through NativeCallContext, HostAccess, a registered adapter method, or return a value that script code later writes through a normal host path.