Without generics

As mentioned before, OpaqueType can be derived if a type doesn't contain any references to either Rust or Julia data, and implements Send and Sync. A type that implements this trait must be exported with struct {{Name}}. Methods and associated functions can be exported with in {{Type}} {{Signature}}.

When an exported method is called from Julia, an instance of the opaque type must be used as the first argument. This data is tracked before it is converted to a reference, which guarantees that mutable aliasing is prevented. It's possible to opt out of tracking by annotating the export with #[untracked_self], which is safe to use if no methods which take &mut self are exported. #[gc_safe] is also supported.

To create a constructor, we can export an (associated) function and rename it to the name of the type. The constructor must return either a CCallRefRet, a TypedValueRet, or a ValueRet; opaque types implement IntoJulia, so they can be converted with (Typed)Value::new. A finalizer that drops the data is automatically registered.

use jlrs::{
    data::managed::{ccall_ref::CCallRefRet, value::typed::TypedValue},
    prelude::*,
    weak_handle,
};

#[derive(Debug, OpaqueType)]
struct OpaqueInt {
    _a: i32,
}

impl OpaqueInt {
    fn new(a: i32) -> CCallRefRet<OpaqueInt> {
        match weak_handle!() {
            Ok(handle) => CCallRefRet::new(TypedValue::new(handle, OpaqueInt { _a: a }).leak()),
            Err(_) => panic!("not called from Julia"),
        }
    }

    fn print(&self) {
        println!("{:?}", self)
    }
}

julia_module! {
    become julia_module_tutorial_init_fn;

    struct OpaqueInt;

    in OpaqueInt fn new(a: i32) -> CCallRefRet<OpaqueInt> as OpaqueInt;

    #[untracked_self]
    in OpaqueInt fn print(&self);
}
julia> module JuliaModuleTutorial ... end
Main.JuliaModuleTutorial

julia> v =  JuliaModuleTutorial.OpaqueInt(Int32(3))
Main.JuliaModuleTutorial.OpaqueInt()

julia> JuliaModuleTutorial.print(v)
OpaqueInt { _a: 3 }