Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
jlrs
The main goal behind jlrs is to provide a simple and safe interface to the Julia C API. Using
this crate you can call arbitrary Julia code from Rust, including your own, and share data
between the two languages. Currently this crate has only been tested on Linux, if you try to use
it on another OS it will likely fail to generate the bindings to Julia.
Generating the bindings
This crate depends on jl-sys which contains the raw bindings to the Julia C API, these are
generated by bindgen. The recommended way to install Julia is to download the binaries from
the official website, which is distributed in an archive containing a directory called
julia-x.y.z. This directory contains several other directories, including a bin directory
containing the julia executable.
In order to ensure the julia.h header file can be found, you have to set the JL_PATH
environment variable to /path/to/julia-x.y.z. Similarly, in order to load libjulia.so you
must add /path/to/julia-x.y.z/lib to the LD_LIBRARY_PATH environment variable.
Using this crate
The first thing you should do is use the prelude-module with an asterisk, this will
bring all the structs and traits you're likely to need in scope. Before you can use Julia it
must first be initialized. You do this by creating a Runtime with Runtime::new, this
method forces you to pick a stack size. You will learn how to choose this value in the
section about memory management. Note that this method can only be called once, if you
drop the Runtime you won't be able to create a new one and have to restart the entire
program.
With the Runtime you can do two things: you can call Runtime::include to include your
own Julia code, and Runtime::session to interact with Julia. If you want to create arrays
with more than three dimensions or borrow arrays with more than one, you should include
jlrs.jl first, which you can find in the root of this crate's github repository. This is
necessary because this functionality currently depends on some Julia code defined in that file.
The latter method takes a closure with a single argument, a mutable reference to a
Session. Using this Session you can do useful things inside the closure.
In order to actually call a function, you need three things:
- A place to store the function output that's protected from garbage collection
- Arguments to call the function with, also protected from garbage collection
- A handle to the function
The Session lets you take care of all the preliminary work; with
Session::new_unassigned you create a safe place for the output to go, while other methods
like Session::new_primitive, Session::new_string and Session::new_owned_array let
you transfer primitive datatypes like u8 and f32, strings, and n-dimensional arrays to
Julia. For a full overview of the possibilities, you should take a look at the documentation
for Session.
In the case of named functions, ie those defined inside a module, you must first acquire a
handle to that module. You can can a handle to the Main, Base and Core modules with the
methods Session::main_module, Session::base_module and Session::core_module
respectively. You can traverse the path to a deeper module with Module::submodule. Finally,
you get a handle to a function with Module::function. Because these are global handles
they don't need to be protected from garbage collection.
There's something a bit special about functions though: there's no real way to differentiate
between functions and other globals in a module. For example, there's nothing that prevents
you from calling Base.pi as a function. It's not possible to check if you call functions
with the correct arguments, either. It's up to you to ensure you call things correctly.
Failing to do so will only result in an error being returned, though, rather than crash your
program.
With all these things in hand, it is time to call Session::execute. This method works just
like Runtime::session does: it takes closure with a single argument, a mutable reference
to an ExecutionContext. Besides letting you copy data from Julia to Rust with
ExecutionContext::try_unbox, you will need this reference when calling functions using the
Call trait.
Both Runtime::session and Session::execute have generic return types, which lets you
easily return the results of your computations. As a simple example, this is how you can add
two numbers:
use *;
Memory management
So far you've seen that you can use a Session to allocate data and an ExecutionContext
to use that data. The data allocated using a Session is valid until the session ends and
nothing prevents you from allocating more data and calling Session::execute again. The
actual allocations happen when Session::execute is called. If nothing was allocated,
calling this function will take one slot on the stack, otherwise it will take as many slots as
allocations plus three.
It's also possible to allocate temporary data with Session::with_temporaries, which works
mostly the same way as Runtime::session and Session::execute do, except its argument
is an AllocationContext rather than a mutable reference to one. The AllocationContext
offers you the same interface as Session does, with two major differences:
AllocationContext::executetakes the context by value rather than by reference, you have to stop using theAllocationContextafter callingAllocationContext::execute.- Data allocated by an
AllocationContextis only valid within that context, rather than the entire session.
So, to summarize, in order to estimate how large your stack size should be, you need to check
where you call Session::execute, Session::with_temporaries and
AllocationContext::execute and figure out how many items you're allocating to get a rough
estimate for how many slots you need. In case your computations fail due to exceeding the
stack size, you can use Runtime::set_stack_size to create a larger one.
Limitations
Calling Julia is entirely single-threaded. You won't be able to use the Runtime from
another thread and while Julia is doing stuff you won't be able to interact with it.
Support for multithreading in Julia is currently in an experimental phase, there might still
be options to use this functionality in order to build experimental support for some kind of
multithreaded task-like system, but that has not been investigated yet.