julia_module!() { /* proc-macro */ }
Expand description

Export functions, types and constants defined in Rust as a Julia module.

This macro generates an initialization function. This function is used in combination with the macros provided by the JlrsCore.Wrap module to automatically generate the content of that module.

The syntax is as follows:

julia_module! {
    // init_function_name is the name of the generated initialization function.
    //
    // The name of the generated function must be unique, it's recommended you prefix it with
    // the crate name. If your crate is named foo-jl, you should use a name like
    // `foo_jl_init`.
    become init_function_name;

    // Exports the function `foo` as `bar` with documentation.
    //
    // The `as <exposed_name>` part is optional, by default the function is exported with the
    // name it has in Rust, the exposed name can end in an exclamation mark.
    //
    // A docstring can be provided with the doc attribute; if multiple functions are exported
    // with the same name it shoud only be documented once. All exported items can be
    // documented, a multi-line docstring can be created by providing multiple doc attributes
    // for the same item.
    //
    // If the function doesn't need to call into Julia, you can annotate it with `#[gc_safe]`
    // to allow the GC to run without having to wait until the function has returned.
    #[doc = "    bar(arr::Array)"]
    #[doc = ""]
    #[doc = "Documentation for this function"]
    #[gc_safe]
    fn foo(arr: Array) -> usize as bar;

    // Exports the function `foo` as `bar!` in the `Base` module.
    //
    // This syntax can be used to extend existing functions.
    fn foo(arr: Array) -> usize as Base.bar!;

    // Exports the struct `MyType` as `MyForeignType`. `MyType` must implement `OpaqueType`
    // or `ForeignType`.
    struct MyType as MyForeignType;

    // Exports `MyType::new` as `MyForeignType`, turning it into a constructor for that type.
    in MyType fn new(arg0: TypedValue<u32>) -> TypedValueRet<MyType> as MyForeignType;

    // Exports `MyType::add` as the function `increment!`.
    //
    // If a method takes `self` in some way, it is tracked by default. You can opt out of this
    // behavior with the `#[untracked_self]` attribute.
    #[untracked_self]
    in MyType fn add(&mut self, incr: u32) -> RustResultRet<u32>  as increment!;
 
    // Exports the alias `MyTypeAlias` for `MyType`.
    //
    // This is exposes as `const MyTypeAlias = MyType`.
    type MyTypeAlias = MyType;

    // Exports the function `long_running_func`, the returned closure is executed on another
    // thread.
    //
    // After dispatching the closure to another thread, the generated Julia function waits for
    // the closure to return using an `AsyncCondition`. Because the closure is executed on
    // another thread you can't call Julia functions or allocate Julia data from it, but it is
    // possible to (mutably) access Julia data by tracking it.
    //
    // In order to be able to use tracked data from the closure,  `Unbound` managed types must
    // be used. Only `(Typed)ValueUnbound` and `(Typed)ArrayUnbound` exist,  they're aliases
    // for `(Typed)Value` and `(Typed)Array` with static lifetimes. The generated Julia
    // function guarantees all data passed as an argument lives at least until the closure has
    // finished, the tracked data must only be shared with that closure.
    //
    // In practice calling a function annotated with `#[gc_safe]` is much more performant.
    async fn long_running_func(
        array: ArrayUnbound
    ) -> JlrsResult<impl AsyncCallback<i32>>;

    // Exports `MY_CONST` as the constant `MY_CONST`, its type must implement `IntoJulia`.
    // `MY_CONST` can be defined in Rust as either static or constant data, i.e. both
    // `static MY_CONST: u8 = 1` and `const MY_CONST: u8 = 1` can be exposed this way.
    const MY_CONST: u8;

    // Exports `MY_CONST` as the global `MY_GLOBAL`, its type must implement `IntoJulia`.
    // `MY_CONST` can be defined in Rust as either static or constant data, i.e. both
    // `static MY_CONST: u8 = 1` and `const MY_CONST: u8 = 1` can be exposed this way.
    static MY_CONST: u8 as MY_GLOBAL;
}

And this is all you need to do in Julia:

module MyRustModule
using JlrsCore.Wrap

@wrapmodule("path/to/lib", :init_function_name)

function __init__()
    @initjlrs
end
end

It can be rather tricky to figure out how data is passed from Julia to Rust when ccalling a function written in Rust. Primitive and isbits types are passed by value, managed types provided directly by jlrs are guaranteed to be boxed, all other types might be passed by value or be boxed.

In order to avoid figuring out how such data is passed you can work with TypedValue, which ensures the data is boxed by using Any in the signature of the generated ccall invocation, but restricts the type of the data in the generated function to the type constructed from the TypedValue’s type parameter.