Local targets

The frame we've use so far is a LocalGcFrame. It's called local because all roots are stored locally on the stack, which is why we need to know its size at compile time.

Every time we root data by using a mutable reference to a LocalGcFrame we consume one of its slots. It's also possible to reserve a slot as a LocalOutput or LocalReusableSlot, they can be created by calling LocalGcFrame::local_output and LocalGcFrame::local_reusable_slot. These methods consume a slot. The main difference between the two is that LocalReusableSlot is a bit more permissive with the lifetime of the result at the cost of returning an unrooted reference. They're useful if we need to return multiple instances of managed data from a scope, or want to reuse a slot inside one.

use jlrs::prelude::*;

fn add<'target, Tgt>(target: Tgt, a: u8, b: u8) -> ValueResult<'target, 'static, Tgt>
where
    Tgt: Target<'target>,
{
    target.with_local_scope::<_, _, 3>(|target, mut frame| {
        let a = Value::new(&mut frame, a);
        let b = Value::new(&mut frame, b);
        let func = Module::base(&frame)
            .global(&mut frame, "+")
            .expect("+ not found in Base");

        // Safety: calling + is safe
        unsafe { func.call2(target, a, b) }
    })
}

fn main() {
    let handle = Builder::new().start_local().expect("cannot init Julia");

    handle.local_scope::<_, 2>(|mut frame| {
        let mut output = frame.local_output();
        let mut reusable_slot = frame.local_reusable_slot();

        {
            // This result can be used until the next time `(&mut) output` is used
            let result = add(&mut output, 1, 2).expect("could not add numbers");
            let unboxed = result.unbox::<u8>().expect("cannot unbox as u8");
            assert_eq!(unboxed, 3);
        }

        {
            // This result can be used until the scope ends
            let result = add(output, 1, 2).expect("could not add numbers");
            let unboxed = result.unbox::<u8>().expect("cannot unbox as u8");
            assert_eq!(unboxed, 3);
        }

        {
            // This result can be used until the scope ends, but must not be used after
            // `reusable_slot` has been used again. Because the result can live longer
            // than it might be rooted, it's returned as a `ValueRef`.
            let result = add(&mut reusable_slot, 1, 2).expect("could not add numbers");

            // Safety: result is rooted until we use `reusable_slot` again
            let unboxed = unsafe { result.as_value() }.unbox::<u8>().expect("cannot unbox as u8");
            assert_eq!(unboxed, 3);
        }

        {
            // This result can be used until the scope ends
            let result = add(reusable_slot, 1, 2).expect("could not add numbers");
            let unboxed = result.unbox::<u8>().expect("cannot unbox as u8");
            assert_eq!(unboxed, 3);
        }
    });
}

An UnsizedLocalGcFrame is similar to a LocalGcFrame, the major difference is that its size isn't required to be known at runtime. If the size of the frame is statically known, use LocalGcFrame.

use jlrs::prelude::*;

fn add<'target, Tgt>(target: Tgt, a: u8, b: u8) -> ValueResult<'target, 'static, Tgt>
where
    Tgt: Target<'target>,
{
    target.with_local_scope::<_, _, 3>(|target, mut frame| {
        let a = Value::new(&mut frame, a);
        let b = Value::new(&mut frame, b);
        let func = Module::base(&frame)
            .global(&mut frame, "+")
            .expect("+ not found in Base");

        // Safety: calling + is safe
        unsafe { func.call2(target, a, b) }
    })
}

fn main() {
    let handle = Builder::new().start_local().expect("cannot init Julia");

    handle.unsized_local_scope(2, |mut frame| {
        let mut output = frame.local_output();
        let mut reusable_slot = frame.local_reusable_slot();

        {
            let result = add(&mut output, 1, 2).expect("could not add numbers");
            let unboxed = result.unbox::<u8>().expect("cannot unbox as u8");
            assert_eq!(unboxed, 3);
        }

        {
            let result = add(output, 1, 2).expect("could not add numbers");
            let unboxed = result.unbox::<u8>().expect("cannot unbox as u8");
            assert_eq!(unboxed, 3);
        }

        {
            let result = add(&mut reusable_slot, 1, 2).expect("could not add numbers");

            // Safety: result is rooted until we use `reusable_slot` again
            let unboxed = unsafe { result.as_value() }.unbox::<u8>().expect("cannot unbox as u8");
            assert_eq!(unboxed, 3);
        }

        {
            let result = add(reusable_slot, 1, 2).expect("could not add numbers");
            let unboxed = result.unbox::<u8>().expect("cannot unbox as u8");
            assert_eq!(unboxed, 3);
        }
    })
}