jlrs/
lib.rs

1//! jlrs is a crate that provides access to the Julia C API. It can be used to embed Julia in Rust
2//! applications and to write interop libraries to Rust crates that can be used by Julia.
3//!
4//! Julia versions 1.10, 1.11 and 1.12 are currently supported. In general jlrs aims to support all
5//! versions starting at the current LTS version, but only the LTS and stable versions are
6//! actively tested. Using the current stable version of Julia is highly recommended. The minimum
7//! supported Rust version is currently 1.85.
8//!
9//! A tutorial is available [here](https://taaitaaiger.github.io/jlrs-tutorial/).
10//!
11//! # Overview
12//!
13//! An incomplete list of features that are currently supported by jlrs:
14//!
15//!  - Access arbitrary Julia modules and their content.
16//!  - Call Julia functions, including functions that take keyword arguments.
17//!  - Handle exceptions or convert them to an error message.
18//!  - Include and call your own Julia code.
19//!  - Use custom system images.
20//!  - Create values that Julia can use, and convert them back to Rust, from Rust.
21//!  - Access the type information and fields of such values. Inline and bits-union fields can be
22//!    accessed directly.
23//!  - Create and use n-dimensional arrays. The `jlrs-ndarray` feature can be enabled for
24//!    integration with ndarray.
25//!  - Map Julia structs to Rust structs, the Rust implementation can be generated with the
26//!    JlrsCore package.
27//!  - Structs that can be mapped to Rust include those with type parameters and bits unions.
28//!  - Use Julia from multiple threads either directly or via Julia-aware thread pools.
29//!  - Export Rust types, methods and functions to Julia with the `julia_module` macro.
30//!  - Libraries that use `julia_module` can be compiled with BinaryBuilder and distributed as
31//!    JLLs.
32//!
33//!
34//! # Prerequisites
35//!
36//! To use jlrs, supported versions of Rust and Julia must have been installed. Currently, Julia
37//! 1.10, 1.11 and 1.12 are supported, the minimum supported Rust version is 1.85. Some features may
38//! require a more recent version of Rust. jlrs uses the JlrsCore package for Julia, if this
39//! package has not been installed, the latest version will be installed automatically by default.
40//!
41//! ## With juliaup
42//!
43//! It is possible to use jlrs in combination with juliaup, but the default approach jlrs uses to
44//! detect the installed version of Julia, its header files, and the libjulia itself will not
45//! work. Instead, the jlrs-launcher application can be installed. This is an application that
46//! uses the juliaup crate itself to determine this information and launches an application with
47//! an updated environment.
48//!
49//! ## Without juliaup
50//!
51//! The recommended way to install Julia is to download the binaries from the official website,
52//! which is distributed as an archive containing a directory called `julia-x.y.z`. This directory
53//! contains several other directories, including a `bin` directory containing the `julia`
54//! executable.
55//!
56//! ### Linux
57//!
58//! During compilation, the paths to the header and library are normally detected automatically by
59//! executing the command `which julia`. The path to `julia.h` must be
60//! `$(which julia)/../include/julia/julia.h` and the path to the library
61//! `$(which julia)/../lib/libjulia.so`. If you want to override this default behavior or Julia
62//! is not available on the path, the `JLRS_JULIA_DIR` environment variable must be set to it to
63//! the appropriate `julia.x-y-z` directory, in this case `$JLRS_JULIA_DIR/include/julia/julia.h`
64//! and`$JLRS_JULIA_DIR/lib/libjulia.so` are used instead.
65//!
66//! In order to be able to load `libjulia.so` this file must be on the library search path. If
67//! this is not the case you must add `/path/to/julia-x.y.z/lib` to the `LD_LIBRARY_PATH`
68//! environment variable.
69//!
70//! ### macOS
71//!
72//! Follow the instructions for Linux, but replace `LD_LIBRARY_PATH` with `DYLD_LIBRARY_PATH`.
73//!
74//! ### Windows
75//!
76//! Julia can be installed with the installer or portable installation downloaded from the
77//! official website. In the first case, Julia has been likely installed in
78//! `%USERPROFILE%\.julia\juliaup\julia-x.y.z+0~x64`, using the installer or extracting allows you
79//! to pick the destination. After installation or extraction a folder called `Julia-x.y.z`
80//! exists, which contains several folders including a `bin` folder containing `julia.exe`. The
81//! path to the `bin` folder must be added to the `Path` environment variable.
82//!
83//! Julia is automatically detected by executing the command `where julia`. If this returns
84//! multiple locations the first one is used. The default can be overridden by setting the
85//! `JLRS_JULIA_DIR` environment variable.
86//!
87//!
88//! # Features
89//!
90//! Most functionality of jlrs is only available if the proper features are enabled. These
91//! features generally belong to one of two categories: runtimes and utilities.
92//!
93//! ## Runtimes
94//!
95//! A runtime lets initialize Julia from Rust application, the following features enable a
96//! runtime:
97//!
98//!  - `local-rt`
99//!
100//!    Enables the local runtime. The local runtime provides single-threaded, blocking access to
101//!    Julia.
102//!
103//!  - `async-rt`
104//!
105//!    Enables the async runtime. The async runtime runs on a separate thread and can be used from
106//!    multiple threads.
107//!
108//!  - `tokio-rt`
109//!
110//!    The async runtime requires an executor. This feature provides a tokio-based executor.
111//!
112//!  - `multi-rt`
113//!
114//!    Enables the multithreaded runtime. The multithreaded runtime lets you call Julia from
115//!    arbitrary threads. It can be combined with the `async-rt` feature to create Julia-aware
116//!    thread pools.
117//!
118//!
119//! <div class="warning"><strong>WARNING</strong>: Runtime features must only be enabled by applications that embed Julia.
120//! Libraries must never enable a runtime feature.</div>
121//!
122//! <div class="warning"><strong>WARNING</strong>: When building an application that embeds Julia, set
123//! <code>RUSTFLAGS="-Clink-arg=-rdynamic"</code> if you want fast code.</div>
124//!
125//! ## Utilities
126//!
127//! All other features are called utility features. The following are available:
128//!
129//! - `async`
130//!
131//!   Enable the features of the async runtime which don't depend on the executor. This
132//!   can be used in libraries which provide implementations of tasks that the async runtime can
133//!   handle.
134//!
135//! - `jlrs-derive`
136//!
137//!   This feature should be used in combination with the code generation provided by the
138//!   `Reflect` module in the JlrsCore package. This module lets you generate Rust implementations
139//!   of Julia structs, this generated code uses custom derive macros made available with this
140//!   feature to enable the safe conversion of data from Julia to Rust, and from Rust to Julia in
141//!   some cases.
142//!
143//! - `jlrs-ndarray`
144//!
145//!   Access the content of a Julia array as an `ArrayView` or `ArrayViewMut` from ndarray.
146//!
147//! - `f16`
148//!
149//!   Adds support for working with Julia's `Float16` type from Rust using half's `f16` type.
150//!
151//! - `complex`
152//!
153//!   Adds support for working with Julia's `Complex` type from Rust using num's `Complex` type.
154//!
155//! - `ccall`
156//!
157//!   Julia's `ccall` interface can be used to call functions written in Rust from Julia. The
158//!   `julia_module` macro is provided to easily export functions, types, and data in
159//!   combination with the macros from the Wrap module in the JlrsCore package.
160//!
161//! - `lto`
162//!
163//!   jlrs depends on a support library written in C, if this feature is enabled this support
164//!   library is built with support for cross-language LTO which can provide a significant
165//!   performance boost.
166//!
167//!   This feature has only been tested on Linux and requires building the support library using a
168//!   version of `clang` with the same major version as `rustc`'s LLVM version; e.g. rust 1.78.0
169//!   uses LLVM 18, so it requires `clang-18`. You can check what version you need by executing
170//!   `rustc -vV`.
171//!
172//!   You must set the `RUSTFLAGS` environment variable if this feature is enabled, and possibly the
173//!   `CC` environment variable. Setting `RUSTFLAGS` overrides the default flags that jlrs sets, so
174//!   you must set at least the following flags:
175//!   `RUSTFLAGS="-Clinker-plugin-lto -Clinker=clang-XX -Clink-arg=-fuse-ld=lld -Clink-arg=-rdynamic"`.
176//!
177//! - `i686`
178//!
179//!   Link with a 32-bit build of Julia on Linux, only used for cross-compilation.
180//!
181//! - `windows`
182//!
183//!   Flag that must be enabled when cross-compiling for Windows from Linux.
184//!
185//! - `debug`
186//!
187//!   Link with a debug build of Julia on Linux.
188//!
189//! You can enable all features except `debug`, `i686`, `windows`, and `lto` by enabling the
190//! `full` feature. If you don't want to enable any runtimes either, you can use `full-no-rt`.
191//!
192//!
193//! ## Environment variables
194//!
195//! It's possible to override certain defaults of jlrs and Julia by setting environment variables.
196//! Many of the environment variables mentioned
197//! [in the Julia documentation] should apply to applications that use jlrs as well, but this is
198//! mostly untested.
199//!
200//! Several additional environment variables can be set which only affect applications that use
201//! jlrs.
202//!
203//! - `JLRS_CORE_VERSION=major.minor.patch`
204//! Installs the set version of JlrsCore before loading it.
205//!
206//! - `JLRS_CORE_REVISION=rev`
207//! Installs the set revision of JlrsCore before loading it.
208//!
209//! - `JLRS_CORE_REPO=repo-url`
210//! Can be used with `JLRS_CORE_REVISION` to set the repository JlrsCore will be downloaded from.
211//!
212//! - `JLRS_CORE_NO_INSTALL=...`
213//! Don't install JlrsCore, its value is ignored.
214//!
215//! `JLRS_CORE_NO_INSTALL` takes priority over `JLRS_CORE_REVISION`, which takes priority over
216//! `JLRS_CORE_VERSION`.
217//!
218//!
219//! # Using jlrs
220//!
221//! How you should use this crate depends on whether you're embedding Julia in a Rust application,
222//! or writing a library you want to call from Julia. We're going to focus on embedding first.
223//! Some topics covered in the section about the local runtime section are relevant for users of
224//! the other runtimes, and library authors who want to call into Rust from Julia and into Julia
225//! again from Rust.
226//!
227//!
228//! ## Calling Julia from Rust
229//!
230//! If you want to embed Julia in a Rust application, you must enable a runtime and a version
231//! feature:
232//!
233//! `jlrs = {version = "0.22", features = ["local-rt"]}`
234//!
235//! `jlrs = {version = "0.22", features = ["tokio-rt"]}`
236//!
237//! `jlrs = {version = "0.22", features = ["multi-rt"]}`
238//!
239//! When Julia is embedded in an application, it must be initialized before it can be used. A
240//! [`Builder`] is available to configure the runtime before starting it. This lets you set
241//! options like the number of threads Julia can start or instruct Julia to use a custom system
242//! image.
243//!
244//! There are three runtimes: the local, async and multithreaded runtime. Let's take a look at them
245//! in that same order.
246//!
247//!
248//! ### Local runtime
249//!
250//! The local runtime initializes Julia on the current thread and lets you call into Julia from
251//! that one thread.
252//!
253//! Starting this runtime is quite straightforward, you only need to create a `Builder` and call
254//! [`Builder::start_local`]. This initializes Julia on the current thread and returns a
255//! [`LocalHandle`] that lets you call into Julia. The runtime shuts down when this handle is
256//! dropped.
257//!
258//! The handle by itself doesn't let you do much directly. In order to create Julia data and call
259//! Julia functions, a scope must be created first. These scopes ensure Julia data can only be
260//! used while it's guaranteed to be safe from being freed by Julia's garbage collector. jlrs has
261//! dynamically-sized scopes and statically-sized local scopes. The easiest way to familiarize
262//! ourselves with these scopes is with a simple example where we allocate some Julia data.
263//!
264//! Dynamically-sized scope:
265//!
266//! ```
267//! use jlrs::prelude::*;
268//!
269//! # fn main() {
270//! let mut julia = Builder::new().start_local().unwrap();
271//!
272//! // To create to dynamically-sized scope we need to create a stack first.
273//! //
274//! // NB: This is a relatively expensive operation, if you need to create a stack you should do
275//! // so early and reuse it as much as possible.
276//! julia.with_stack(|mut stack| {
277//!     stack.scope(|mut frame| {
278//!         // We use `frame` every time we create Julia data. This roots the data in the
279//!         // frame, which means the garbage collector is guaranteed to leave this data alone
280//!         // at least until we leave this scope. Even if the frame is dropped, the data is
281//!         // guaranteed to be protected until the scope ends.
282//!         //
283//!         // This value inherits `frame`'s lifetime, which prevents it from being returned
284//!         // from this closure.
285//!         let _v = Value::new(&mut frame, 1usize);
286//!     })
287//! })
288//! # }
289//! ```
290//!
291//! Statically-sized local scope:
292//!
293//! ```
294//! use jlrs::prelude::*;
295//!
296//! # fn main() {
297//! let mut julia = Builder::new().start_local().unwrap();
298//!
299//! // Local scopes can be created without creating a stack, but you need to provide the exact
300//! // number of slots you need.
301//! julia.local_scope::<_, 1>(|mut frame| {
302//!     // We root one value in this frame, so the required capacity of this local scope is 1.
303//!     let _v = Value::new(&mut frame, 1usize);
304//!
305//!     // Because there is only one slot available, uncommenting the next line would cause a
306//!     // panic unless we changed `local_scope::<_, 1>` to `local_scope::<_, 2>`.
307//!     // let _v2 = Value::new(&mut frame, 2usize);
308//! })
309//! # }
310//! ```
311//!
312//! In general you should prefer using local scopes over dynamic scopes. For more information
313//! about scopes, frames, and other important topics involving memory management, see the
314//! [`memory`] module.
315//!
316//! In the previous two examples we saw the function [`Value::new`], which converts Rust to Julia
317//! data. In particular, calling `Value::new(&mut frame, 1usize)` returned a Julia `UInt` with the
318//! value 1. Any type that implements [`IntoJulia`] can be converted to Julia data with this
319//! method. Similarly, any type that implements [`Unbox`] can be converted from Julia to Rust.
320//!
321//! ```
322//! use jlrs::prelude::*;
323//!
324//! # fn main() {
325//! let mut julia = Builder::new().start_local().unwrap();
326//!
327//! julia.local_scope::<_, 1>(|mut frame| {
328//!     // We root one value in this frame, so the required capacity of this local scope is 1.
329//!     let v = Value::new(&mut frame, 1.0f32);
330//!
331//!     // `Value::unbox` checks if the conversion is valid before unboxing the value.
332//!     let unboxed = v.unbox::<f32>().expect("not a Float32");
333//!     assert_eq!(unboxed, 1.0f32);
334//! })
335//! # }
336//! ```
337//!
338//! We don't just want to unbox the exact same data we've just allocated, obviously. We want to
339//! call functions written in Julia with that data. This boils down to accessing the function in
340//! the right module and calling it.
341//!
342//! ```
343//! use jlrs::prelude::*;
344//!
345//! # fn main() {
346//! let mut julia = Builder::new().start_local().unwrap();
347//!
348//! // This scope contains a fallible operation. Whenever the return type is a `Result` and the
349//! // `?` operator is used, the closure typically has to be annotated with its return type.
350//! julia
351//!     .local_scope::<_, 4>(|mut frame| -> JlrsResult<()> {
352//!         let v1 = Value::new(&mut frame, 1.0f32); // 1
353//!         let v2 = Value::new(&mut frame, 2.0f32); // 2
354//!
355//!         // The Base module is globally rooted, so we can access it with `&frame` instead of
356//!         // `&mut frame`. Only uses of mutable references count towards the necessary capacity
357//!         // of the local scope.
358//!         let base = Module::base(&frame);
359//!
360//!         // The Base module contains the `+` function.
361//!         let func = base.global(&mut frame, "+")?; // 3
362//!
363//!         // `Value` implements the `Call` trait which lets us call it as a function. Any
364//!         // callable object can be called this way. Functions can throw exceptions, if it does
365//!         // it's caught and returned as the `Err` branch of a `Result`. Converting the result
366//!         // to a `JlrsResult` converts it to its error message and lets it be returned with the
367//!         // `?` operator.
368//!         //
369//!         // Calling Julia functions is unsafe. Some functions are inherently unsafe to call,
370//!         // their names typically start with `unsafe`. Other functions might involve
371//!         // multithreading and affect how you must access certain global variables. Adding two
372//!         // numbers is not an issue.
373//!         let v3 = unsafe {
374//!             func.call(&mut frame, [v1, v2])? // 4
375//!         };
376//!
377//!         let unboxed = v3.unbox::<f32>().expect("not a Float32");
378//!         assert_eq!(unboxed, 3.0f32);
379//!
380//!         Ok(())
381//!     })
382//!     .unwrap()
383//! # }
384//! ```
385//!
386//! Julia functions are highly generic, calling functions with the `Call` trait calls the most
387//! appropriate function given the arguments. The `+` function for example accepts any number of
388//! arguments and returns their sum, so we can easily adjust the previous example to add more
389//! numbers together.
390//!
391//! ```
392//! use jlrs::prelude::*;
393//!
394//! # fn main() {
395//! let mut julia = Builder::new().start_local().unwrap();
396//!
397//! julia
398//!     .local_scope::<_, 5>(|mut frame| -> JlrsResult<()> {
399//!         let v1 = Value::new(&mut frame, 1.0f32); // 1
400//!         let v2 = Value::new(&mut frame, 2.0f32); // 2
401//!         let v3 = Value::new(&mut frame, 3.0f32); // 3
402//!
403//!         let v3 = unsafe {
404//!             Module::base(&frame)
405//!                 .global(&mut frame, "+")? // 4
406//!                 .call(&mut frame, [v1, v2, v3])? // 5
407//!         };
408//!
409//!         let unboxed = v3.unbox::<f32>()?;
410//!         assert_eq!(unboxed, 6.0f32);
411//!
412//!         Ok(())
413//!     })
414//!     .unwrap()
415//! # }
416//! ```
417//!
418//! By default you can only access the `Main`, `Base` and `Core` module. If you want to use
419//! functions defined in standard libraries or installed packages, you must load them first.
420//!
421//! ```
422//! use jlrs::prelude::*;
423//!
424//! # fn main() {
425//! let mut julia = Builder::new().start_local().unwrap();
426//!
427//! unsafe {
428//!     julia
429//!         .using("LinearAlgebra")
430//!         .expect("LinearAlgebra package does not exist");
431//! }
432//!
433//! julia.local_scope::<_, 1>(|mut frame| {
434//!     let lin_alg = Module::package_root_module(&frame, "LinearAlgebra");
435//!     assert!(lin_alg.is_some());
436//!
437//!     let mul_mut_func = lin_alg.unwrap().global(&mut frame, "mul!");
438//!     assert!(mul_mut_func.is_ok());
439//! })
440//! # }
441//! ```
442//!
443//!
444//! ### Multithreaded runtime
445//!
446//! The multithreaded runtime initializes Julia on some background thread, and allows calling into
447//! Julia from arbitrary threads.
448//!
449//! To start this runtime you need to create a `Builder` and call [`Builder::start_mt`]. It has
450//! its own handle type, [`MtHandle`], which can be used to spawn new threads that can call into
451//! Julia. Unlike the local runtime's `LocalHandle`, it can't be used directly, you must call
452//! [`MtHandle::with`] first to ensure the thread is in a state where it can call into Julia.
453//!
454//! Let's call into Julia from two separate threads to see it in action:
455//!
456//! ```
457//! use std::thread;
458//!
459//! use jlrs::prelude::*;
460//!
461//! # fn main() {
462//! # #[cfg(feature = "multi-rt")]
463//! # {
464//! // When the multithreaded runtime is started the current thread initializes Julia.
465//! Builder::new().start_mt(|mt_handle| {
466//!     let t1 = mt_handle.spawn(move |mut mt_handle| {
467//!         // By calling `MtHandle::with` we enable the thread to call into Julia. The handle you can
468//!         // use in that closure provides the same functionality as the local runtime's
469//!         // `LocalHandle`.
470//!         mt_handle.with(|handle| {
471//!             handle.local_scope::<_, 1>(|mut frame| unsafe {
472//!                 let _v = Value::new(&mut frame, 1);
473//!             })
474//!         })
475//!     });
476//!
477//!     let t2 = mt_handle.spawn(move |mut mt_handle| {
478//!         mt_handle.with(|handle| {
479//!             handle.local_scope::<_, 1>(|mut frame| unsafe {
480//!                 let _v = Value::new(&mut frame, 2);
481//!             })
482//!         })
483//!     });
484//!
485//!     t1.join().expect("thread 1 panicked");
486//!     t2.join().expect("thread 2 panicked");
487//! }).unwrap();
488//! # }
489//! # }
490//! ```
491//!
492//! It's important that you avoid blocking operations unrelated to Julia in a call to
493//! `MtHandle::with`. The reason is that this can prevent the garbage collector from running.
494//! Roughly speaking, whenever Julia data is allocated the garbage collector can signal it has to
495//! run. This blocks the thread that tried to allocate data, and every other thread will similarly
496//! block when they try to allocate data, until every thread is blocked. When all threads are
497//! blocked, the garbage collector collects garbage and unblocks the threads when it's done.
498//!
499//! The implication is that long-running operations which don't allocate Julia data can block the
500//! garbage collector, which can grind Julia to a halt. Outside calls to `MtHandle::with`, the
501//! thread is guaranteed to be in a state where it won't block the garbage collector from running.
502//!
503//!
504//! ### Async runtime
505//!
506//! While the sync and multithreaded runtimes let you call into Julia directly from one or more
507//! threads, the async runtime runs on a background thread and uses an executor to allow
508//! running multiple tasks on that thread concurrently. Its handle type, `AsyncHandle`, can be
509//! shared across threads like the `MtHandle`, and lets you send tasks to the runtime thread.
510//!
511//! The async runtime supports three kinds of tasks: blocking, async, and persistent tasks.
512//! Blocking tasks run as a single unit and prevent other tasks from running until they've
513//! completed. Async tasks run as a separate task on the executor, they can use async operations
514//! and long-running Julia functions can be dispatched to a background thread. Persistent tasks
515//! are similar to async tasks, they run as separate tasks but additionally have internal state
516//! and can be called multiple times.
517//!
518//! Blocking task:
519//!
520//! ```
521//! use jlrs::prelude::*;
522//!
523//! # fn main() {
524//! let (julia, thread_handle) = Builder::new()
525//!     .async_runtime(Tokio::<3>::new(false))
526//!     .spawn()
527//!     .unwrap();
528//!
529//! // When a task cannot be dispatched to the runtime because the
530//! // channel is full, the dispatcher is returned in the `Err` branch.
531//! // `blocking_task` is the receiving end of a tokio oneshot channel.
532//! let blocking_task = julia
533//!     .blocking_task(|mut frame| -> JlrsResult<f32> {
534//!         Value::new(&mut frame, 1.0f32).unbox::<f32>()
535//!     })
536//!     .try_dispatch()
537//!     .expect("unable to dispatch task");
538//!
539//! let res = blocking_task
540//!     .blocking_recv()
541//!     .expect("unable to receive result")
542//!     .expect("blocking task failed.");
543//!
544//! assert_eq!(res, 1.0);
545//!
546//! // The runtime thread exits when the last instance of `julia` is dropped.
547//! std::mem::drop(julia);
548//! thread_handle.join().unwrap();
549//! # }
550//! ```
551//!
552//! Async task:
553//!
554//! ```
555//! use jlrs::prelude::*;
556//!
557//! struct AdditionTask {
558//!     a: u64,
559//!     b: u32,
560//! }
561//!
562//! // Async tasks must implement the `AsyncTask` trait. Only the runtime thread can call the
563//! // Julia C API, so the `run` method must not return a future that implements `Send` or `Sync`.
564//! impl AsyncTask for AdditionTask {
565//!     // The type of the result of this task.
566//!     type Output = JlrsResult<u64>;
567//!
568//!     // This async method replaces the closure from the previous examples,
569//!     // an `AsyncGcFrame` can be used the same way as other frame types.
570//!     async fn run<'frame>(self, mut frame: AsyncGcFrame<'frame>) -> Self::Output {
571//!         let a = Value::new(&mut frame, self.a);
572//!         let b = Value::new(&mut frame, self.b);
573//!
574//!         let func = Module::base(&frame).global(&mut frame, "+")?;
575//!
576//!         // CallAsync::call_async schedules the function call on another thread.
577//!         // The runtime can switch to other tasks while awaiting the result.
578//!         // Safety: adding two numbers is safe.
579//!         unsafe { func.call_async(&mut frame, [a, b]) }
580//!             .await?
581//!             .unbox::<u64>()
582//!     }
583//! }
584//!
585//! # fn main() {
586//! let (julia, thread_handle) = Builder::new()
587//!     .async_runtime(Tokio::<3>::new(false))
588//!     .spawn()
589//!     .unwrap();
590//!
591//! // When a task cannot be dispatched to the runtime because the
592//! // channel is full, the dispatcher is returned in the `Err` branch.
593//! // `async_task` is the receiving end of a tokio oneshot channel.
594//! let async_task = julia
595//!     .task(AdditionTask { a: 1, b: 2 })
596//!     .try_dispatch()
597//!     .expect("unable to dispatch task");
598//!
599//! let res = async_task
600//!     .blocking_recv()
601//!     .expect("unable to receive result")
602//!     .expect("AdditionTask failed");
603//!
604//! assert_eq!(res, 3);
605//!
606//! // The runtime thread exits when the last instance of `julia` is dropped.
607//! std::mem::drop(julia);
608//! thread_handle.join().unwrap();
609//! # }
610//! ```
611//!
612//! Async closures implement `AsyncTask`:
613//!
614//! ```
615//! use jlrs::prelude::*;
616//!
617//! # fn main() {
618//! let (julia, thread_handle) = Builder::new()
619//!     .async_runtime(Tokio::<3>::new(false))
620//!     .spawn()
621//!     .unwrap();
622//!
623//! let a = 1u64;
624//! let b = 2u64;
625//!
626//! // It's necessary to provide frame's type
627//! let async_task = julia
628//!     .task(async move |mut frame: AsyncGcFrame| -> JlrsResult<u64> {
629//!         let a = Value::new(&mut frame, a);
630//!         let b = Value::new(&mut frame, b);
631//!
632//!         let func: Value = Module::base(&frame).global(&mut frame, "+")?;
633//!         unsafe { func.call_async(&mut frame, [a, b]) }
634//!             .await?
635//!             .unbox::<u64>()
636//!     })
637//!     .try_dispatch()
638//!     .expect("unable to dispatch task");
639//!
640//! let res = async_task
641//!     .blocking_recv()
642//!     .expect("unable to receive result")
643//!     .expect("AdditionTask failed");
644//!
645//! assert_eq!(res, 3);
646//!
647//! std::mem::drop(julia);
648//! thread_handle.join().unwrap();
649//! # }
650//! ```
651//!
652//! Persistent task:
653//!
654//! ```
655//! use jlrs::prelude::*;
656//!
657//! struct AccumulatorTask {
658//!     n_values: usize,
659//! }
660//!
661//! // The internal state of a persistent task can contain Julia data.
662//! struct AccumulatorTaskState<'state> {
663//!     array: TypedArray<'state, 'static, usize>,
664//!     offset: usize,
665//! }
666//!
667//! // The same is true for implementations of `PersistentTask`.
668//! impl PersistentTask for AccumulatorTask {
669//!     type Output = JlrsResult<usize>;
670//!
671//!     // The type of the task's internal state.
672//!     type State<'state> = AccumulatorTaskState<'state>;
673//!
674//!     // The type of the additional data that the task must be called with.
675//!     type Input = usize;
676//!
677//!     // This method is called before the task can be called.
678//!     async fn init<'frame>(
679//!         &mut self,
680//!         mut frame: AsyncGcFrame<'frame>,
681//!     ) -> JlrsResult<Self::State<'frame>> {
682//!         // A `Vec` can be moved from Rust to Julia if the element type
683//!         // implements `IntoJulia`.
684//!         let data = vec![0usize; self.n_values];
685//!         let array = TypedArray::from_vec(&mut frame, data, self.n_values)??;
686//!
687//!         Ok(AccumulatorTaskState { array, offset: 0 })
688//!     }
689//!
690//!     // Whenever the task is called, it's called with its state and the provided input.
691//!     async fn run<'frame, 'state: 'frame>(
692//!         &mut self,
693//!         mut frame: AsyncGcFrame<'frame>,
694//!         state: &mut Self::State<'state>,
695//!         input: Self::Input,
696//!     ) -> Self::Output {
697//!         unsafe {
698//!             let mut data = state.array.bits_data_mut();
699//!             data[state.offset] = input;
700//!         };
701//!
702//!         state.offset += 1;
703//!         if (state.offset == self.n_values) {
704//!             state.offset = 0;
705//!         }
706//!
707//!         unsafe {
708//!             Module::base(&frame)
709//!                 .global(&mut frame, "sum")?
710//!                 .call(&mut frame, [state.array.as_value()])?
711//!                 .unbox::<usize>()
712//!         }
713//!     }
714//! }
715//!
716//! # fn main() {
717//! let (julia, thread_handle) = Builder::new()
718//!     .async_runtime(Tokio::<3>::new(false))
719//!     .spawn()
720//!     .unwrap();
721//!
722//! let persistent_task = julia
723//!     .persistent(AccumulatorTask { n_values: 2 })
724//!     .try_dispatch()
725//!     .expect("unable to dispatch task")
726//!     .blocking_recv()
727//!     .expect("unable to receive handle")
728//!     .expect("init failed");
729//!
730//! // A persistent task can be called with its input, the same dispatch mechanism
731//! // is used as above.
732//! let res = persistent_task
733//!     .call(1)
734//!     .try_dispatch()
735//!     .expect("unable to dispatch call")
736//!     .blocking_recv()
737//!     .expect("unable to receive handle")
738//!     .expect("call failed");
739//!
740//! assert_eq!(res, 1);
741//!
742//! let res = persistent_task
743//!     .call(2)
744//!     .try_dispatch()
745//!     .expect("unable to dispatch call")
746//!     .blocking_recv()
747//!     .expect("unable to receive handle")
748//!     .expect("call failed");
749//!
750//! assert_eq!(res, 3);
751//!
752//! // If the `AsyncHandle` is dropped before the task is, the runtime continues
753//! // running until the task has been dropped.
754//! std::mem::drop(julia);
755//! std::mem::drop(persistent_task);
756//! thread_handle.join().unwrap();
757//! # }
758//! ```
759//!
760//!
761//! ### Async, multithreaded runtime
762//!
763//! There are two non-exclusive ways the async runtime can be combined with the multithreaded
764//! runtime. You can start the runtime thread with an async executor, which grants you both an
765//! `AsyncHandle` to that thread and a `MtHandle`. This can be useful if you have code that must
766//! run on the main thread.
767//!
768//! The second option is thread pools. When both runtimes are enabled, `MtHandle` lets you
769//! construct pools of async worker threads that share a single task queue. Each pool can have an
770//! arbitrary number of workers, which are automatically restarted if they die. Like the async
771//! runtime, you interact with a pool through its `AsyncHandle`. The pool shuts down when the last
772//! handle is dropped.
773//!
774//! ```
775//! use jlrs::prelude::*;
776//!
777//! # fn main() {
778//! # #[cfg(feature = "multi-rt")]
779//! # {
780//! Builder::new()
781//!     .async_runtime(Tokio::<3>::new(false))
782//!     .start_mt(|mt_handle, _async_handle| {
783//!         let pool_handle = mt_handle
784//!             .pool_builder(Tokio::<1>::new(false))
785//!             .n_workers(2.try_into().unwrap())
786//!             .spawn();
787//!     })
788//!     .unwrap();
789//! # }
790//! # }
791//! ```
792//!
793//!
794//! ## Calling Rust from Julia
795//!
796//! Julia can call functions written in Rust thanks to its `ccall` interface, which lets you call
797//! arbitrary functions which use the C ABI. These functions can be defined in dynamic libraries or
798//! provided directly to Julia by converting a function pointer to a `Value`.
799//!
800//! ```
801//! use jlrs::prelude::*;
802//!
803//! // This function will be provided to Julia as a pointer, so its name can be mangled.
804//! unsafe extern "C" fn call_me(arg: Bool) -> isize {
805//!     if arg.as_bool() { 1 } else { -1 }
806//! }
807//!
808//! # fn main() {
809//! # let mut julia = Builder::new().start_local().unwrap();
810//!
811//! julia
812//!     .local_scope::<_, 3>(|mut frame| -> JlrsResult<_> {
813//!         unsafe {
814//!             // Cast the function to a void pointer
815//!             let call_me_val = Value::new(&mut frame, call_me as *mut std::ffi::c_void);
816//!
817//!             // Value::eval_string can be used to create new functions.
818//!             let func = Value::eval_string(
819//!                 &mut frame,
820//!                 "myfunc(callme::Ptr{Cvoid})::Int = ccall(callme, Int, (Bool,), true)",
821//!             )?;
822//!
823//!             // Call the function and unbox the result.
824//!             let result = func.call(&mut frame, [call_me_val])?.unbox::<isize>()?;
825//!
826//!             assert_eq!(result, 1);
827//!             Ok(())
828//!         }
829//!     })
830//!     .unwrap();
831//! # }
832//! ```
833//!
834//! To create a library that Julia can use, you must compile your crate as a `cdylib`. To achieve
835//! this you need to add
836//!
837//! ```toml
838//! [lib]
839//! crate-type = ["cdylib"]
840//! ```
841//!
842//! to your crate's `Cargo.toml`. You must also abort on panic:
843//!
844//! ```toml
845//! [profile.release]
846//! panic = "abort"
847//! ```
848//!
849//! You must not enable any of jlrs's runtimes.
850//!
851//! The most versatile way to export Rust functions like `call_me` from the previous example is by
852//! using the [`julia_module`] macro. This macro lets you export custom types and functions in a
853//! way that is friendly to precompilation.
854//!
855//! In Rust, this macro is used as follows:
856//!
857//! ```ignore
858//! use jlrs::prelude::*;
859//!
860//! fn call_me(arg: Bool) -> isize {
861//!     if arg.as_bool() {
862//!         1
863//!     } else {
864//!         -1
865//!     }
866//! }
867//!
868//! julia_module! {
869//!     become callme_init_fn;
870//!     fn call_me(arg: Bool) -> isize;
871//! }
872//! ```
873//!
874//! While on the Julia side things look like this:
875//!
876//! ```julia
877//! module CallMe
878//! using JlrsCore.Wrap
879//!
880//! @wrapmodule("./path/to/libcallme.so", :callme_init_fn)
881//!
882//! function __init__()
883//!     @initjlrs
884//! end
885//! end
886//! ```
887//!
888//! All Julia functions are automatically generated and have the same name as the exported
889//! function:
890//!
891//! ```julia
892//! @assert CallMe.call_me(true) == 1
893//! @assert CallMe.call_me(false) == -1
894//! ```
895//!
896//! This macro has many more capabilities than just exporting functions written in Rust. For more
897//! information see the [documentation]. A practical example that uses this macro is the
898//! [rustfft-jl] crate, which uses this macro to expose RustFFT to Julia. The recipe for
899//! BinaryBuilder can be found [here].
900//!
901//! While `call_me` doesn't call back into Julia, it is possible to call arbitrary functions from
902//! jlrs from a `ccall`ed function. This will often require a `Target`, to create a target you
903//! must create an instance of `CCall` first.
904//!
905//!
906//! # Testing
907//!
908//! The restriction that Julia can be initialized once must be taken into account when running
909//! tests that use jlrs. Because tests defined in a single crate are not guaranteed to be run
910//! from the same thread you must guarantee that each crate has only one test that initializes
911//! Julia. It's recommended you only use jlrs in integration tests because each top-level
912//! integration test file is treated as a separate crate.
913//!
914//! ```
915//! use jlrs::{prelude::*, runtime::handle::local_handle::LocalHandle};
916//!
917//! fn test_1(julia: &mut LocalHandle) {
918//!     // use handle
919//! }
920//! fn test_2(julia: &mut LocalHandle) {
921//!     // use handle
922//! }
923//!
924//! #[test]
925//! fn call_tests() {
926//!     let mut julia = unsafe { Builder::new().start_local().unwrap() };
927//!     test_1(&mut julia);
928//!     test_2(&mut julia);
929//! }
930//! ```
931//!
932//!
933//! # Custom types
934//!
935//! In order to map a struct in Rust to one in Julia you can derive several traits. You normally
936//! shouldn't need to implement these structs or traits manually. The `reflect` function defined
937//! in the `JlrsCore.Reflect` module can generate Rust structs whose layouts match their counterparts
938//! in Julia and automatically derive the supported traits.
939//!
940//! The main restriction is that structs with atomic fields, and tuple or union fields with type
941//! parameters are not supported. The reason for this restriction is that the layout of such
942//! fields can be very different depending on the parameters in a way that can't be easily
943//! represented in Rust.
944//!
945//! These custom types can also be used when you call Rust from Julia with `ccall`.
946//!
947//! [`LocalHandle`]: crate::runtime::handle::local_handle::LocalHandle
948//! [`MtHandle`]: crate::runtime::handle::mt_handle::MtHandle
949//! [`MtHandle::with`]: crate::runtime::handle::mt_handle::MtHandle::with
950//! [`Builder::start_local`]: crate::runtime::builder::Builder::start_local
951//! [`Unrooted`]: crate::memory::target::unrooted::Unrooted
952//! [`GcFrame`]: crate::memory::target::frame::GcFrame
953//! [`Module`]: crate::data::managed::module::Module
954//! [`Value`]: crate::data::managed::value::Value
955//! [`Call`]: crate::call::Call
956//! [`Value::eval_string`]: crate::data::managed::value::Value::eval_string
957//! [`Value::new`]: crate::data::managed::value::Value::new
958//! [`Array`]: crate::data::managed::array::Array
959//! [`JuliaString`]: crate::data::managed::string::JuliaString
960//! [`Module::main`]: crate::data::managed::module::Module::main
961//! [`Module::base`]: crate::data::managed::module::Module::base
962//! [`Module::core`]: crate::data::managed::module::Module::core
963//! [`Module::global`]: crate::data::managed::module::Module::global
964//! [`Module::submodule`]: crate::data::managed::module::Module::submodule
965//! [`IntoJulia`]: crate::convert::into_julia::IntoJulia
966//! [`Typecheck`]: crate::data::types::typecheck::Typecheck
967//! [`ValidLayout`]: crate::data::layout::valid_layout::ValidLayout
968//! [`ValidField`]: crate::data::layout::valid_layout::ValidField
969//! [`Unbox`]: crate::convert::unbox::Unbox
970//! [`AsyncGcFrame`]: crate::memory::target::frame::AsyncGcFrame
971//! [`AsyncTask`]: crate::async_util::task::AsyncTask
972//! [`PersistentTask`]: crate::async_util::task::PersistentTask
973//! [`CallAsync`]: crate::call::CallAsync
974//! [`DataType`]: crate::data::managed::datatype::DataType
975//! [`TypedArray`]: crate::data::managed::array::TypedArray
976//! [`Builder`]: crate::runtime::builder::Builder
977//! [`Builder::start_mt`]: crate::runtime::builder::Builder::start_mt
978//! [`jlrs::prelude`]: crate::prelude
979//! [`julia_module`]: jlrs_macros::julia_module
980//! [documentation]: jlrs_macros::julia_module
981//! [rustfft_jl]: https://github.com/Taaitaaiger/rustfft-jl
982//! [here]: https://github.com/JuliaPackaging/Yggdrasil/tree/master/R/rustfft
983//! [in the Julia documentation]: https://docs.julialang.org/en/v1/manual/environment-variables/
984
985#![forbid(rustdoc::broken_intra_doc_links)]
986
987use std::{
988    env,
989    ffi::c_int,
990    sync::atomic::{AtomicBool, Ordering},
991};
992
993use data::{
994    managed::module::mark_global_cache, static_data::mark_static_data_cache,
995    types::construct_type::mark_constructed_type_cache,
996};
997use jl_sys::jl_gc_set_cb_root_scanner;
998use jlrs_sys::jlrs_init_missing_functions;
999use memory::get_tls;
1000use prelude::Managed;
1001use semver::Version;
1002
1003use crate::{
1004    data::managed::{module::JlrsCore, value::Value},
1005    memory::{
1006        context::{ledger::init_ledger, stack::Stack},
1007        target::unrooted::Unrooted,
1008    },
1009};
1010
1011pub mod args;
1012#[cfg(feature = "async")]
1013pub mod async_util;
1014pub mod call;
1015pub mod catch;
1016pub mod convert;
1017pub mod data;
1018pub mod error;
1019pub mod gc_safe;
1020pub mod info;
1021pub mod memory;
1022pub mod prelude;
1023pub(crate) mod private;
1024pub mod runtime;
1025pub mod safety;
1026pub mod util;
1027
1028/// The version of the jlrs API this version of jlrs is compatible with.
1029///
1030/// If this version number doesn't match `JLRS_API_VERSION` in JlrsCore.jl, initialization fails.
1031pub const JLRS_API_VERSION: isize = 4;
1032
1033/// Installation method for the JlrsCore package. If JlrsCore is already installed the installed version
1034/// is used.
1035#[derive(Clone)]
1036pub enum InstallJlrsCore {
1037    /// Install the most recent version of JlrsCore
1038    Default,
1039    /// Don't install JlrsCore
1040    No,
1041    /// Install the given version
1042    Version {
1043        /// Major version
1044        major: usize,
1045        /// Minor version
1046        minor: usize,
1047        /// Patch version
1048        patch: usize,
1049    },
1050    /// Install a revision of some git repository
1051    Git {
1052        /// URL of the repository
1053        repo: String,
1054        /// Revision to be installed
1055        revision: String,
1056    },
1057}
1058
1059impl InstallJlrsCore {
1060    #[cfg_attr(
1061        not(any(
1062            feature = "local-rt",
1063            feature = "async-rt",
1064            feature = "multi-rt",
1065            feature = "ccall"
1066        )),
1067        allow(unused)
1068    )]
1069    unsafe fn use_or_install(&self) {
1070        unsafe {
1071            let unrooted = Unrooted::new();
1072            let cmd = match self {
1073            InstallJlrsCore::Default => {
1074                "try; using JlrsCore; catch; import Pkg; Pkg.add(\"JlrsCore\"); using JlrsCore; end"
1075                    .to_string()
1076            }
1077            InstallJlrsCore::Git { repo, revision } => {
1078                format!("import Pkg; Pkg.add(url=\"{repo}\", rev=\"{revision}\"); using JlrsCore")
1079            }
1080            InstallJlrsCore::Version {
1081                major,
1082                minor,
1083                patch,
1084            } => {
1085                format!(
1086                    "import Pkg; Pkg.add(name=\"JlrsCore\", version=\"{major}.{minor}.{patch}\"); using JlrsCore"
1087                )
1088            }
1089            InstallJlrsCore::No => "using JlrsCore".to_string(),
1090        };
1091
1092            let cmd = format!(
1093                "if !haskey(Base.loaded_modules, Base.PkgId(Base.UUID(\"29be08bc-e5fd-4da2-bbc1-72011c6ea2c9\"), \"JlrsCore\")); {cmd}; end"
1094            );
1095
1096            if let Err(err) = Value::eval_string(unrooted, cmd) {
1097                eprintln!("Failed to load or install JlrsCore package");
1098                // JlrsCore failed to load, print the error message to stderr without using
1099                // `Managed::error_string_or`.
1100                err.as_value().print_error();
1101                panic!();
1102            }
1103        }
1104    }
1105}
1106
1107fn preferred_jlrs_core_version() -> Option<InstallJlrsCore> {
1108    if let Some(_) = env::var("JLRS_CORE_NO_INSTALL").ok() {
1109        return Some(InstallJlrsCore::No);
1110    }
1111
1112    if let Some(version) = env::var("JLRS_CORE_VERSION").ok() {
1113        if let Ok(version) = Version::parse(version.as_str()) {
1114            return Some(InstallJlrsCore::Version {
1115                major: version.major as _,
1116                minor: version.minor as _,
1117                patch: version.patch as _,
1118            });
1119        }
1120    }
1121
1122    if let Some(revision) = env::var("JLRS_CORE_REVISION").ok() {
1123        let repo = env::var("JLRS_CORE_REPO")
1124            .unwrap_or("https://github.com/Taaitaaiger/JlrsCore.jl".into());
1125        return Some(InstallJlrsCore::Git { repo, revision });
1126    }
1127
1128    None
1129}
1130
1131#[cfg_attr(
1132    not(any(
1133        feature = "local-rt",
1134        feature = "async-rt",
1135        feature = "multi-rt",
1136        feature = "ccall"
1137    )),
1138    allow(unused)
1139)]
1140pub(crate) unsafe fn init_jlrs(install_jlrs_core: &InstallJlrsCore, allow_override: bool) {
1141    unsafe {
1142        static IS_INIT: AtomicBool = AtomicBool::new(false);
1143
1144        if IS_INIT.swap(true, Ordering::Relaxed) {
1145            return;
1146        }
1147
1148        jlrs_init_missing_functions();
1149
1150        jl_gc_set_cb_root_scanner(root_scanner, 1);
1151
1152        if let Some(preferred_version) = preferred_jlrs_core_version() {
1153            if allow_override {
1154                preferred_version.use_or_install();
1155            } else {
1156                install_jlrs_core.use_or_install();
1157            }
1158        } else {
1159            install_jlrs_core.use_or_install();
1160        }
1161
1162        let unrooted = Unrooted::new();
1163        let api_version = JlrsCore::api_version(&unrooted);
1164        if api_version != JLRS_API_VERSION {
1165            panic!(
1166                "Incompatible version of JlrsCore detected. Expected API version {JLRS_API_VERSION}, found {api_version}"
1167            );
1168        }
1169
1170        init_ledger();
1171        Stack::init(&unrooted);
1172    }
1173}
1174
1175#[cfg_attr(
1176    not(any(
1177        feature = "local-rt",
1178        feature = "async-rt",
1179        feature = "multi-rt",
1180        feature = "ccall"
1181    )),
1182    allow(unused)
1183)]
1184unsafe extern "C" fn root_scanner(full: c_int) {
1185    unsafe {
1186        let ptls = get_tls();
1187        debug_assert!(!ptls.is_null());
1188
1189        let full = full != 0;
1190        mark_constructed_type_cache(ptls, full);
1191        mark_global_cache(ptls, full);
1192        mark_static_data_cache(ptls, full);
1193    }
1194}