Auto-Generate API for Custom Type


This assumes that you have complete control of the type and can do whatever you want with it (such as putting attributes on fields).

In particular, the type must be defined within the current crate.

To register a type and its API for use with an Engine, the simplest method is via the CustomType trait.

A custom derive macro is provided to auto-implement CustomType on any struct type, which exposes all the type’s fields to an Engine all at once.

It is as simple as adding #[derive(CustomType)] to the type definition.

use rhai::{CustomType, TypeBuilder};    // <- necessary imports

#[derive(Clone, CustomType)]            // <- auto-implement 'CustomType'
pub struct Vec3 {                       //    for normal structs
    pub x: i64,
    pub y: i64,
    pub z: i64,

#[derive(Clone, CustomType)]            // <- auto-implement 'CustomType'
pub struct ABC(i64, bool, String);      //    for tuple structs

let mut engine = Engine::new();

// Register the custom types!

Custom Attribute Options

The rhai_type attribute, with options, can be added to the fields of the type to customize the auto-generated API.

OptionApplies toValueDescription
nametype, fieldstring expressionuse this name instead of the type/field name.
skipfieldnoneskip this field; cannot be used with any other attribute.
readonlyfieldnoneonly auto-generate getter, no setter; cannot be used with set.
getfieldfunction pathuse this getter function (with &self) instead of the auto-generated getter; if get_mut is also set, this is ignored.
get_mutfieldfunction pathuse this getter function (with &mut self) instead of the auto-generated getter.
setfieldfunction pathuse this setter function instead of the auto-generated setter; cannot be used with readonly.
extratypefunction pathcall this function after building the type to add additional API’s

Function signatures

The signature of the function for get is:

Fn(&T) -> V

The signature of the function for get_mut is:

Fn(&mut T) -> V

The signature of the function for set is:

Fn(&mut T, V)

The signature of the function for extra is:

Fn(&mut TypeBuilder<T>)


use rhai::{CustomType, TypeBuilder};    // <- necessary imports

#[derive(Debug, Clone)]
#[derive(CustomType)]                   // <- auto-implement 'CustomType'
pub struct ABC(
    #[rhai_type(skip)]                  // <- 'field0' not included

    #[rhai_type(readonly)]              // <- only auto getter, no setter for 'field1'
    #[rhai_type(name = "flag")]         // <- override property name for 'field2'
    String                              // <- auto getter/setter for 'field3'

#[derive(Default, Clone)]
#[derive(CustomType)]                   // <- auto-implement 'CustomType'
#[rhai_type(name = "MyFoo", extra = Self::build_extra)] // <- call this type 'MyFoo' and use 'build_extra' to add additional API's
pub struct Foo {
    #[rhai_type(skip)]                  // <- field not included
    dummy: i64,

    #[rhai_type(readonly)]              // <- only auto getter, no setter for 'bar'
    bar: i64,

    #[rhai_type(name = "flag")]         // <- override property name
    baz: bool,                          // <- auto getter/setter for 'baz'

    #[rhai_type(get = Self::qux)]       // <- call custom getter (with '&self') for 'qux'
    qux: char,                          // <- auto setter for 'qux'

    #[rhai_type(set = Self::set_hello)] // <- call custom setter for 'hello'
    hello: String                       // <- auto getter for 'hello'

impl Foo {
    /// Regular field getter function with `&self`
    pub fn qux(&self) -> char {

    /// Special setter implementation for `hello`
    pub fn set_hello(&mut self, value: String) {
        self.hello = if self.baz {
            let mut s = self.hello.clone();
            for _ in { s.push('!'); }
        } else {

    /// Additional API's
    fn build_extra(builder: &mut TypeBuilder<Self>) {
        // Register constructor function
        builder.with_fn("new_foo", || Self::default());

#[derive(Debug, Clone, Eq, PartialEq, CustomType)]
#[rhai_fn(extra = vec3_build_extra)]
pub struct Vec3 {
    #[rhai_type(get = Self::x, set = Self::set_x)]
    x: i64,
    #[rhai_type(get = Self::y, set = Self::set_y)]
    y: i64,
    #[rhai_type(get = Self::z, set = Self::set_z)]
    z: i64,

impl Vec3 {
    fn new(x: i64, y: i64, z: i64) -> Self { Self { x, y, z } }
    fn x(&self) -> i64 { self.x }
    fn set_x(&mut self, x: i64) { self.x = x }
    fn y(&self) -> i64 { self.y }
    fn set_y(&mut self, y: i64) { self.y = y }
    fn z(&self) -> i64 { self.z }
    fn set_z(&mut self, z: i64) { self.z = z }

fn vec3_build_extra(builder: &mut TypeBuilder<Self>) {
    // Register constructor function
    builder.with_fn("Vec3", Self::new);

TL;DR – The above is equivalent to this…

impl CustomType for ABC {
    fn build(mut builder: TypeBuilder<Self>)
        builder.with_get("field1", |obj: &mut Self| obj.1.clone());
            |obj: &mut Self| obj.2.clone(),
            |obj: &mut Self, val| obj.2 = val
            |obj: &mut Self| obj.3.clone(),
            |obj: &mut Self, val| obj.3 = val

impl CustomType for Foo {
    fn build(mut builder: TypeBuilder<Self>)
        builder.with_get("bar", |obj: &mut Self|;
            |obj: &mut Self| obj.baz.clone(),
            |obj: &mut Self, val| obj.baz = val
            |obj: &Self| Self::qux(&*obj)),
            |obj: &mut Self, val| obj.qux = val
            |obj: &mut Self| obj.hello.clone(),
        Self::build_extra(&mut builder);

impl CustomType for Vec3 {
    fn build(mut builder: TypeBuilder<Self>)
        builder.with_get_set("x", |obj: &mut Self| Self::x(&*obj), Self::set_x);
        builder.with_get_set("y", |obj: &mut Self| Self::y(&*obj), Self::set_y);
        builder.with_get_set("z", |obj: &mut Self| Self::z(&*obj), Self::set_z);
        vec3_build_extra(&mut builder);