ForeignType

Foreign types are very similar to opaque types, the main difference is that a foreign type can contain references to managed data. Instead of OpaqueType we'll need to implement ForeignType. When we implement this trait, we have to provide a mark function to let the GC find these references.

Like OpaqueType, implementations of ForeignType must be thread-safe. A foreign type may only be used in the library that defines it. Fields that reference managed data must use Ret-aliases because the 'scope lifetime has to be erased. One thing that's important to keep in mind is that we whenever we change what managed data is referenced by a field, we must insert a write barrier after this mutation. See this footnote for more information.

To implement the associated mark function1 we'll need to use mark_queue_obj and mark_queue_objarray to mark every reference to managed data. We need to sum the result of every call to mark_queue_obj and return this sum; mark_queue_objarray can be used to mark a slice of references to managed data, this operation doesn't affect the sum.

use jlrs::{ data::{ managed::value::{ typed::{TypedValue, TypedValueRet}, ValueRet, }, memory::PTls, types::foreign_type::ForeignType, }, memory::gc::{mark_queue_obj, write_barrier}, prelude::*, weak_handle, }; pub struct ForeignWrapper { a: ValueRet, b: ValueRet, } // Safety: Tracking `self` guarantees access to a `ForeignWrapper` is thread-safe. unsafe impl Send for ForeignWrapper {} unsafe impl Sync for ForeignWrapper {} unsafe impl ForeignType for ForeignWrapper { fn mark(ptls: PTls, data: &Self) -> usize { // Safety: We mark all referenced managed data. unsafe { let mut n_marked = 0; n_marked += mark_queue_obj(ptls, data.a) as usize; n_marked += mark_queue_obj(ptls, data.b) as usize; n_marked } } } impl ForeignWrapper { fn new(a: Value<'_, 'static>, b: Value<'_, 'static>) -> TypedValueRet<ForeignWrapper> { match weak_handle!() { Ok(handle) => { let data = ForeignWrapper { a: a.leak(), b: b.leak(), }; TypedValue::new(handle, data).leak() } Err(_) => panic!("not called from Julia"), } } fn set_a(&mut self, a: Value<'_, 'static>) { // Safety: we insert a write barrier after mutating the field unsafe { self.a = a.leak(); write_barrier(self, a); } } fn get_a(&self) -> ValueRet { self.a } } julia_module! { become julia_module_tutorial_init_fn; struct ForeignWrapper; in ForeignWrapper fn new(a: Value<'_, 'static>, b: Value<'_, 'static>) -> TypedValueRet<ForeignWrapper> as ForeignWrapper; in ForeignWrapper fn set_a(&mut self, a: Value<'_, 'static>); in ForeignWrapper fn get_a(&self) -> ValueRet; }
julia> module JuliaModuleTutorial ... end Main.JuliaModuleTutorial julia> x = JuliaModuleTutorial.ForeignWrapper(1, 2) Main.JuliaModuleTutorial.ForeignWrapper() julia> JuliaModuleTutorial.get_a(x) 1 julia> JuliaModuleTutorial.set_a(x, 4) julia> JuliaModuleTutorial.get_a(x) 4
1

Yes, the signature of mark is odd. It takes PTls as its first argument for consistency with mark_queue_* and other functions in the Julia C API which take PTls explicitly.