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
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.