Structs
By exporting a Rust struct
type, you register this type in the script engine,
allowing other exported items (e.g., Rust functions) to refer to it.
#[export]
#[derive(Debug, Clone, Copy, PartialEq)]
struct Vector {
// Only the public fields will be exposed in scripts by default.
pub x: f64,
// Enforce private field exposure by annotating the field with `#[export]`.
#[export]
y: f64,
// Read-only fields will be exposed as read-only in scripts.
#[export(readonly)]
pub z: f64,
}
// Referring to Vector as an exported function parameter.
#[export]
fn foo(v: &Vector) {}
The exported Rust structure must be of a type that is Send + Sync + 'static
.
Therefore, you cannot export a structure with non-static lifetime references.
Fields
By default, the macro exposes only the public fields to the scripting
environment. However, you can enforce exposure by annotating the field with the
#[export]
or #[export(include)]
attribute (both are synonyms when applied to
struct fields).
The field type must be one of the following:
- Any Rust numeric type:
f32
,usize
, etc. - The
bool
type. - The unit
()
type. - A range (
Range
) type. - Any exported struct type.
Note that, in contrast to functions, you cannot expose a structure field with a
type like Option<usize>
.
To bypass this limitation, you can prevent public field exposure using the
#[export(exclude)]
annotation and expose the struct field value using
corresponding getters and setters.
#[export]
struct Foo {
#[export(exclude)]
pub bar: Option<usize>,
}
#[export]
impl Foo {
pub fn get_bar(&self) -> &Option<usize> {
self.bar
}
pub fn set_bar(&mut self, bar: Option<usize>) {
self.bar = bar;
}
}
Methods
To export associated implementation members of the exported structure, you
should export the corresponding impl
block of the structure.
#[export]
impl Vector {
#[export(name "vec")]
pub fn new(x: f64, y: f64, z: f64) -> Self {
Self { x, y, z }
}
pub fn radius(&self) -> f64 {
(self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
}
pub fn normalize(&mut self) -> &mut Self {
let r = self.radius();
self.x /= r;
self.y /= r;
self.z /= r;
self
}
}
let v = vec(1.0, 3.7, 9.0);
v.normalize();
v.radius() == 1.0;
Similarly to struct fields, the exporting system exposes only public methods by
default. Therefore, if an implementation has a non-public method that you want
to expose, or a public method that you don't want to export, you should
annotate them with the #[export(include)]
and #[export(exclude)]
attributes,
respectively.
There are two types of associated functions:
- Object methods: functions that have
self
,&self
, or&mut self
as a receiver. - Non-methods, such as the
Vector::new
constructor from the example above.
Non-methods will be exported on behalf of the script package, just like normal crate-global functions. Their names must be unique across the exported crate functions namespace.
Usually, you would assign more type-specific names to constructors, such as
renaming the Vector::new
function using the #[export(name = "vec")]
attribute.
In contrast, type methods with a receiver belong to the namespace of the
exported type. Their names must be unique only within the type's namespace.
For example, Vector::radius
does not need renaming even if you export another
type with a method of the same name.
Finally, exported methods may return references with the same lifetime as the
receiver's lifetime. The Vector::normalize
is an example of such a method.