Returning managed data
We can return data by value as long as the return type is an isbits
type, anything more complex must be returned as managed data. However, we run into an issue here: we'll need to create a scope before we can create managed data, and we need to return that data from the scope despite the lifetime constraints.
To create a scope we'll need a handle, introducing: WeakHandle
. A WeakHandle
provides the same functionalty as the LocalHandle
we've used in most embedding examples, but it doesn't shut down Julia when it's dropped. We can created them freely with the weak_handle!
and weak_handle_unchecked!
macros. The first confirms that the thread is in a state where it can call into Julia and is safe to use, the latter assumes this is the case and is therefore unsafe to use.
While this at least gives us a way to create scopes, we still need to solve the other problem: how do we return managed data from a scope?
Every managed type in jlrs has a 'scope
lifetime, to return managed data from the scope we'll need to erase this lifetime. jlrs takes the rootedness guarantee of managed types seriously, so we can't simply adjust the lifetime of such data directly. Ref
-types don't guarantee that the data is rooted for its 'scope
lifetime, so we're free to relax it to 'static
, which solves our issue. We call this leaking managed data.
In short, to return managed data we'll need to convert it to a Ref
with static lifetimes first. All managed types have a Ret
alias, which is the Ref
alias with static lifetimes. These Ret
-aliases implement CCallReturn
. Converting managed data to a Ret
type is a matter of calling Managed::leak
.
use jlrs::{
data::managed::value::typed::{TypedValue, TypedValueRet},
prelude::*,
weak_handle,
};
fn add(a: f64, b: f64) -> TypedValueRet<f64> {
match weak_handle!() {
Ok(handle) => TypedValue::new(handle, a + b).leak(),
_ => panic!("not called from Julia"),
}
}
julia_module! {
become julia_module_tutorial_init_fn;
fn add(a: f64, b: f64) -> TypedValueRet<f64>;
}
julia> module JuliaModuleTutorial ... end
Main.JuliaModuleTutorial
julia> JuliaModuleTutorial.add(1.0, 2.0)
3.0
We didn't have to create a scope because a WeakHandle
is a non-rooting target itself. We can skip rooting the data because we call no other functions that could hit a safepoint before returning from add
. The weak_handle!
macro must be used in combination with match
or if let
, we can't unwrap
or expect
it.
We can return arrays the same way, all ArrayBase
aliases have a Ret
-alias.
use jlrs::{data::managed::array::TypedMatrixRet, prelude::*, weak_handle};
fn new_matrix(rows: usize, cols: usize) -> TypedMatrixRet<f64> {
match weak_handle!() {
Ok(handle) => {
let data = vec![0.0f64; rows * cols];
TypedMatrix::from_vec(handle, data, [rows, cols])
.expect("size invalid")
.expect("caught exception")
.leak()
}
_ => panic!("not called from Julia"),
}
}
julia_module! {
become julia_module_tutorial_init_fn;
fn new_matrix(rows: usize, cols: usize) -> TypedMatrixRet<f64>;
}
julia> module JuliaModuleTutorial ... end
Main.JuliaModuleTutorial
julia> JuliaModuleTutorial.new_matrix(UInt(4), UInt(2))
4×2 Matrix{Float64}:
0.0 0.0
0.0 0.0
0.0 0.0
0.0 0.0