wolfram_library_link/lib.rs
1//! A safe and ergonomic wrapper around Wolfram [LibraryLink][library-link-guide].
2//!
3//! Wolfram LibraryLink is an interface for dynamic libraries that can be
4//! [dynamically loaded][LibraryFunctionLoad] by the [Wolfram Language][WL]. This crate
5//! provides idiomatic Rust bindings around the lower-level LibraryLink C interface.
6//!
7//! **Features:**
8//!
9//! * Call Rust functions from Wolfram code using [`#[export]`][crate::export].
10//! * Pass data efficiently between Rust and Wolfram code using native data types
11//! like [`NumericArray`] and [`Image`].
12//! * Pass arbitrary expressions between Rust and Wolfram code using
13//! [`Expr`][struct@crate::Expr] and the [`#[export(wstp)]`][crate::export#exportwstp]
14//! macro. Build `Expr` values concisely with the [`expr!`][crate::expr::expr] macro.
15//! * Pass **typed Rust structs** directly using WXF serialization via
16//! `#[export(wxf)]` — no manual serialization code needed (see
17//! [`wolfram_export`](https://docs.rs/wolfram-export) with `features = ["wxf"]`).
18//! * Generate asynchronous events handled by the Wolfram Language, using an [`AsyncTaskObject`]
19//! background thread.
20//!
21//!
22//!
23//!
24//! # What is Wolfram LibraryLink?
25//!
26//! > Wolfram LibraryLink provides a powerful way to connect external code to the Wolfram
27//! > Language, enabling high-speed and memory-efficient execution. It does this by
28//! > allowing dynamic libraries to be directly loaded into the Wolfram Language kernel so
29//! > that functions in the libraries can be immediately called from the Wolfram Language.
30//! >
31//! > — [Wolfram LibraryLink User Guide](https://reference.wolfram.com/language/LibraryLink/tutorial/Overview.html)
32//!
33//! ## Library function types
34//!
35//! Four calling conventions are supported, each with a different tradeoff:
36//!
37//! | Mode | Attribute | Best for |
38//! |------|-----------|----------|
39//! | **Native** | `#[export]` | Scalars, `NumericArray`, images — maximum efficiency |
40//! | **Raw native** | `#[export(margs)]` | Same ABI as Native, full manual control over marshaling |
41//! | **WSTP** | `#[export(wstp)]` | Arbitrary `Expr` trees, streaming, dynamic argument counts |
42//! | **WXF** | `#[export(wxf)]` | Typed Rust structs/enums auto-serialized via `#[derive(ToWXF, FromWXF)]` |
43//!
44//! **Native** functions pass values as C-ABI `MArgument` slots — machine integers,
45//! doubles, C strings, or [`NumericArray`]s. Very fast, but limited to a fixed set of
46//! primitive types.
47//!
48//! **Raw native** functions receive the same `MArgument` slots as Native, but
49//! as a raw `&[MArgument]`/`MArgument` instead of having `FromArg`/`IntoArg`
50//! applied automatically — use this when you need full manual control over
51//! argument/return conversion. Since the macro can't see real types from a
52//! `fn(&[MArgument], MArgument)` signature, declare them by hand with
53//! `args = (..)`/`ret = ..`:
54//!
55//! ```
56//! # mod scope {
57//! use wolfram_library_link::{self as wll, sys::MArgument, FromArg};
58//!
59//! #[wll::export(margs, args = (::Real, ::Real), ret = ::Real)]
60//! fn raw_add(args: &[MArgument], ret: MArgument) {
61//! let a = unsafe { f64::from_arg(&args[0]) };
62//! let b = unsafe { f64::from_arg(&args[1]) };
63//! unsafe { *ret.real = a + b; }
64//! }
65//! # }
66//! ```
67//!
68//! Omitting `args`/`ret` still compiles, but defaults the generated
69//! `LibraryFunctionLoad` type spec to the same fixed `LinkObject`/`LinkObject`
70//! placeholder WSTP mode uses (which a raw `MArgument` function doesn't
71//! actually accept) and emits a compile-time warning telling you to annotate it.
72//!
73//! Raw native mode is also the escape hatch for types that have no
74//! [`FromArg`]/[`IntoArg`] impl — like `SparseArray`. Read the raw
75//! `MArgument.sparse` pointer (an [`MSparseArray`][crate::sys::MSparseArray])
76//! and drive the `MSparseArray_*`/`MTensor_*` functions in [`rtl`] directly;
77//! see `margs_sparse_array_merge` in
78//! [wolfram-examples-internal](https://github.com/WolframResearch/wolfram-library-link-rs/blob/master/wolfram-examples-internal/src/margs.rs)
79//! for a worked example.
80//!
81//! **WSTP** functions pass values over a WSTP [`Link`] object that can carry any Wolfram
82//! Language expression, including associations, nested lists, and symbols. Use the
83//! [`expr!`][crate::expr::expr] macro to build `Expr` values with WL-like syntax.
84//!
85//! **WXF** functions (available via the `wolfram-export` crate with `features = ["wxf"]`)
86//! pass a single WXF-encoded `ByteArray` in and out. Argument structs just need
87//! `#[derive(FromWXF)]`; return types need `#[derive(ToWXF)]`. The kernel side calls
88//! `BinaryDeserialize` / `BinarySerialize` automatically.
89//!
90//!
91//!
92//!
93//! # Examples
94//!
95//! **Example programs:** In addition to the basic example programs show below, the
96//! wolfram-library-link repository contains
97//! [example programs](https://github.com/WolframResearch/wolfram-library-link-rs#example-programs)
98//! demonstrating the major features of this crate.
99//!
100//! **Quick Start**: The [LibraryLink for Rust Quick Start][QuickStart] is a complete tutorial
101//! covering how to create a new Rust library, compile it, and call into it from the
102//! Wolfram Language.
103//!
104//! ## Native functions
105//!
106//! Rust functions can be exported for use from the Wolfram Language using the
107//! [`#[export]`][crate::export] macro:
108//!
109//! ```
110//! # mod scope {
111//! use wolfram_library_link::export;
112//!
113//! #[export]
114//! fn square(x: i64) -> i64 {
115//! x * x
116//! }
117//! # }
118//! ```
119//!
120//! Then, after building the Rust code into a dynamic library, the function can be loaded
121//! into the Wolfram Language using [`LibraryFunctionLoad`][LibraryFunctionLoad]:
122//!
123//! ```wolfram
124//! square = LibraryFunctionLoad["<library name>", "square", {Integer}, Integer];
125//! ```
126//!
127//! After being loaded, a library function can be called like any other Wolfram Language
128//! function:
129//!
130//! ```wolfram
131//! square[5] (* Returns 25 *)
132//! ```
133//!
134//! [QuickStart]: https://github.com/WolframResearch/wolfram-library-link-rs/blob/master/docs/QuickStart.md
135//!
136//! ## WSTP Functions
137//!
138//! Rust WSTP functions can be exported for use from the Wolfram Language using the
139//! [`#[export(wstp)]`][crate::export#exportwstp] macro:
140//!
141//! ```
142//! # mod scope {
143//! use wolfram_library_link::{export, wstp::Link};
144//!
145//! #[export(wstp)]
146//! fn square(link: &mut Link) {
147//! // WSTP function arguments are passed as a List expression: {...}
148//! let arg_count = link.test_head("System`List").unwrap();
149//!
150//! // Get that the argument list contains a single element.
151//! if arg_count != 1 {
152//! panic!("square: expected one argument")
153//! }
154//!
155//! // Check that the argument is an integer, and get it's value.
156//! let x: i64 = link.get_i64().expect("expected Integer argument");
157//!
158//! // Write the return value.
159//! link.put_i64(x * x).unwrap();
160//! }
161//! # }
162//! ```
163//!
164//!
165//!
166//!
167//! # Documentation
168//!
169//! * [How To: Convert Between Rust and Wolfram Types][crate::docs::converting_between_rust_and_wolfram_types]
170//! * [How To: Evaluate Wolfram code from Rust][docs::evaluate_wolfram_code_from_rust]
171//! * [How To: Export typed functions using WXF][docs::using_wxf_mode]
172//! * [`expr!`][crate::expr::expr] — build `Expr` values with WL-like syntax
173//!
174//! *See the [`docs`] module for a complete list of available long-form documentation.*
175//!
176//! <br />
177//!
178//!
179//!
180//! # Additional Features
181//!
182//! ### Non-primitive native types
183//!
184//! Native functions support passing primitive types like [`bool`], [`i64`], [`f64`],
185//! and strings. Additionally, they also support a small number of non-primitive types
186//! that can be used to efficiently pass more complicated data structures between the
187//! Wolfram Langauge and compiled code, without requiring the full generality of using a
188//! WSTP function.
189//!
190//! The set of currently supported non-primitive native types includes:
191//!
192//! * [`NumericArray`]
193//! * [`Image`]
194//! * [`DataStore`]
195//!
196//! ### Cooperative computation abort handling
197//!
198//! The Wolfram Language supports the ability for the user to abort an in-progress
199//! computation, without ending the process and losing their current state. This is
200//! accomplished by code that checks if an abort has been triggered — and if so, performs an early return
201//! — which is placed at important points in the Wolfram Language kernel
202//! evaluation process.
203//!
204//! User libraries can cooperatively include abort checking logic in their library using
205//! the [`aborted()`] function. This enables LibraryLink libraries to provide the same
206//! user experience as built in Wolfram Language functions. LibraryLink libraries that may
207//! perform long computations are especially encouraged to do abort checking within loops
208//! that may run for a long time.
209//!
210//! ```no_run
211//! use wolfram_library_link as wll;
212//!
213//! if wll::aborted() {
214//! // The user aborted this computation, so it doesn't matter what we return.
215//! panic!("Wolfram abort");
216//! }
217//! ```
218//!
219//! *Note: The Wolfram Language 'abort' command is not at all related to the
220//! C/Rust [`abort()`][std::process::abort] function. The `abort()` function is typically used
221//! when an unrecoverable error occurs, at a point determined by the programmer. The
222//! Wolfram 'abort' command can be issued by the user at any point, and is commonly used
223//! to end long-running computations the user no longer wishes to wait for.*
224//!
225//! ### Show backtrace when a panic occurs
226//!
227//! [WSTP functions](#wstp-functions) will automatically catch any
228//! Rust panics that occur in the wrapped code, and return a [`Failure`][failure] object
229//! with the panic message and source file/line number. The failure can optionally include
230//! a backtrace showing the location of the panic.
231//!
232//! This is configured by the `"LIBRARY_LINK_RUST_BACKTRACE"` environment variable. Enable
233//! it by evaluating:
234//!
235//! ```wolfram
236//! SetEnvironment["LIBRARY_LINK_RUST_BACKTRACE" -> "True"]
237//! ```
238//!
239//! Now the error shown when a panic occurs will include a backtrace.
240//!
241//!
242//!
243//!
244//! # Related Links
245//!
246//! * [LibraryLink for Rust Quick Start][QuickStart]
247//! * [Wolfram LibraryLink User Guide](https://reference.wolfram.com/language/LibraryLink/tutorial/Overview.html)
248//!
249//!
250//!
251//!
252//! [WL]: https://wolfram.com/language
253//! [library-link-guide]: https://reference.wolfram.com/language/guide/LibraryLink.html
254//! [LibraryFunctionLoad]: https://reference.wolfram.com/language/ref/LibraryFunctionLoad.html
255//! [failure]: https://reference.wolfram.com/language/ref/Failure.html
256// #![doc = include_str!("../docs/included/Overview.md")]
257#![warn(missing_docs)]
258
259mod args;
260mod async_tasks;
261pub(crate) mod catch_panic;
262mod data_store;
263mod errors;
264mod image;
265mod library_data;
266mod numeric_array;
267
268/// This module is *semver exempt*. This is not intended to be part of the public API of
269/// wolfram-library-link.
270///
271/// Utilities used by code generated by the public macros.
272#[doc(hidden)]
273pub mod macro_utils;
274pub mod managed;
275pub mod rtl;
276
277pub mod docs;
278
279// Note: This is exported as doc(inline) so that it shows up in the 'Modules' section of
280// the crate docs instead of in the 'Re-exports' section. This is to make way for
281// the chance that in the future, wolfram-library-link will have it's own expression
282// type that uses types like NumericArray and Image as variants, which can't be
283// used in the more general wolfram-expr crate (since NumericArray and Image depend
284// on the Wolfram RTL, which isn't available in arbitrary Rust code).
285#[doc(inline)]
286pub use wolfram_expr as expr;
287pub use wolfram_library_link_sys as sys;
288#[cfg(feature = "wstp")]
289pub use wstp;
290
291// Used by the #[export]/#[export(wstp)] macro implementations.
292#[cfg(feature = "automate-function-loading-boilerplate")]
293#[doc(hidden)]
294pub use inventory;
295
296#[cfg(feature = "automate-function-loading-boilerplate")]
297pub use self::macro_utils::exported_library_functions_association;
298
299pub use self::{
300 args::{FromArg, IntoArg, NativeFunction},
301 async_tasks::AsyncTaskObject,
302 catch_panic::call_and_catch_panic,
303 data_store::{DataStore, DataStoreNode, DataStoreNodeValue, Nodes},
304 errors::{LibraryError, FAILED_TO_INIT, FAILED_WITH_PANIC},
305 image::{ColorSpace, Image, ImageData, ImageType, Pixel, UninitImage},
306 library_data::{get_library_data, initialize, WolframLibraryData},
307 numeric_array::{
308 NumericArray, NumericArrayConvertMethod, NumericArrayDataType, NumericArrayKind,
309 NumericArrayType, UninitNumericArray,
310 },
311};
312
313#[cfg(feature = "wstp")]
314pub use self::args::WstpFunction;
315
316use wolfram_library_link_sys::mint;
317#[cfg(feature = "wstp")]
318use {once_cell::sync::Lazy, std::sync::Mutex, wstp::Link};
319
320#[cfg(feature = "wstp")]
321pub(crate) use self::library_data::assert_main_thread;
322#[cfg(feature = "wstp")]
323use crate::expr::{expr, Expr, ExprKind, Symbol};
324
325//--------------------------------------
326// Re-exported items
327//--------------------------------------
328
329/// Designate an initialization function to run when this library is loaded via Wolfram
330/// LibraryLink.
331///
332/// `#[init]` can be applied to at most one function in a library.
333///
334/// The function annotated with `#[init]` will automatically call [`initialize()`].
335///
336/// LibraryLink libraries are not required to define an initialization function.
337///
338/// # Panics
339///
340/// Any panics thrown during the executation of `#[init]` will automatically be caught,
341/// and an error code will be returned to the Wolfram Kernel.
342///
343// TODO: Mention which error code specifically:
344// `macro_utils::error_code::FAILED_WITH_PANIC`.
345///
346/// If the initialization function panics, the Wolfram Kernel will prevent other LibraryLink
347/// functions exported from that library from being loaded.
348///
349/// # Example
350///
351/// Define an initialization function:
352///
353/// ```rust
354/// use wolfram_library_link as wll;
355///
356/// #[wll::init]
357/// fn init_my_library() {
358/// println!("library is now initialized");
359/// }
360/// ```
361///
362/// # Behavior
363///
364/// If a library exports a function [called `WolframLibrary_initialize()`][lib-init], that
365/// function will automatically be called by the Wolfram Kernel when the library is
366/// loaded.
367///
368/// `#[init]` works by generating a definition for `WolframLibrary_initialize()`.
369///
370/// [lib-init]: https://reference.wolfram.com/language/LibraryLink/tutorial/LibraryStructure.html#280210622
371// Back-compat re-export: `#[wolfram_library_link::init]` now resolves to the
372// version in `wolfram-export-macros` (the new home for all #[export*]-related
373// proc-macros). Same call-site syntax as before.
374pub use wolfram_export_macros::init;
375
376/// Export the specified functions as native *LibraryLink* functions.
377///
378/// To be exported by this macro, the specified function(s) must implement
379/// [`NativeFunction`].
380///
381/// Functions exported using this macro will automatically:
382///
383/// * Call [`initialize()`] to initialize this library.
384/// * Catch any panics that occur.
385/// - If a panic does occur, the function will return
386/// [`LIBRARY_FUNCTION_ERROR`][crate::sys::LIBRARY_FUNCTION_ERROR].
387///
388// * Extract the function arguments from the raw [`MArgument`] array.
389// * Store the function return value in the raw [`MArgument`] return value field.
390///
391/// # Syntax
392///
393/// Export a native function.
394///
395/// ```
396/// # mod scope {
397/// # use wolfram_library_link::export;
398/// #[export]
399/// # fn square(x: i64) -> i64 { x }
400/// # }
401/// ```
402///
403/// Export a function using the specified low-level shared library symbol name.
404///
405/// ```
406/// # mod scope {
407/// # use wolfram_library_link::export;
408/// #[export(name = "WL_square")]
409/// # fn square(x: i64) -> i64 { x }
410/// # }
411/// ```
412///
413/// ### Advanced
414///
415/// Don't include an exported function in the automatic
416/// [`generate_loader!`] output.
417///
418/// ```
419/// # mod scope {
420/// # use wolfram_library_link::export;
421/// #[export(hidden)]
422/// # fn square(x: i64) -> i64 { x }
423/// # }
424/// ```
425///
426/// # Examples
427///
428/// ### Primitive data types
429///
430/// Export a native function with a single integer argument:
431///
432/// ```
433/// # mod scope {
434/// # use wolfram_library_link::export;
435/// #[export]
436/// fn square(x: i64) -> i64 {
437/// x * x
438/// }
439/// # }
440/// ```
441///
442/// ```wolfram
443/// LibraryFunctionLoad["...", "square", {Integer}, Integer]
444/// ```
445///
446/// Export a native function with a single string argument:
447///
448/// ```
449/// # mod scope {
450/// # use wolfram_library_link::export;
451/// #[export]
452/// fn reverse_string(string: String) -> String {
453/// string.chars().rev().collect()
454/// }
455/// # }
456/// ```
457///
458/// ```wolfram
459/// LibraryFunctionLoad["...", "reverse_string", {String}, String]
460/// ```
461///
462/// Export a native function with multiple arguments:
463///
464/// ```
465/// # mod scope {
466/// # use wolfram_library_link::export;
467/// #[export]
468/// fn times(a: f64, b: f64) -> f64 {
469/// a * b
470/// }
471/// # }
472/// ```
473///
474/// ```wolfram
475/// LibraryFunctionLoad["...", "times", {Real, Real}, Real]
476/// ```
477///
478/// ### Numeric arrays
479///
480/// Export a native function with a [`NumericArray`] argument:
481///
482/// ```
483/// # mod scope {
484/// # use wolfram_library_link::{export, NumericArray};
485/// #[export]
486/// fn total_i64(list: &NumericArray<i64>) -> i64 {
487/// list.as_slice().into_iter().sum()
488/// }
489/// # }
490/// ```
491///
492/// ```wolfram
493/// LibraryFunctionLoad[
494/// "...", "total_i64",
495/// {LibraryExpressionEnum[NumericArray, "Integer64"]}
496/// Integer
497/// ]
498/// ```
499///
500/// ### Customize exported function name
501///
502/// By default, the exported name of a function exported to the Wolfram Langauge
503/// using `#[export]` will be the same as the Rust function name. The exported name
504/// can be customed by specifying the `name = "..."` argument to the `#[export]` macro:
505///
506/// ```
507/// # mod scope {
508/// use wolfram_library_link::export;
509///
510/// #[export(name = "native_square")]
511/// fn square(x: i64) -> i64 {
512/// x * x
513/// }
514/// # }
515/// ```
516///
517/// ```wolfram
518/// LibraryFunctionLoad["<library name>", "native_square", {Integer}, Integer]
519/// ```
520///
521///
522// TODO: Add a "Memory Management" section to this comment and discuss "Constant".
523//
524// ```wolfram
525// LibraryFunctionLoad[
526// "...", "total_i64",
527// {
528// {LibraryExpressionEnum[NumericArray, "Integer64"], "Constant"}
529// },
530// Integer
531// ]
532// ```
533///
534/// # Parameter types
535///
536/// When manually writing the Wolfram
537/// [`LibraryFunctionLoad`][ref/LibraryFunctionLoad]<sub>WL</sub> call necessary to load
538/// a Rust *LibraryLink* function, you must declare the type signature of the function
539/// using the appropriate types.
540///
541/// The following table describes the relationship between Rust types that can be used as
542/// parameter types in a native LibraryLink function (namely: those that implement
543/// [`FromArg`]) and the compatible Wolfram *LibraryLink* function parameter type(s).
544///
545/// [`FromArg::parameter_type()`] can be used to determine the Wolfram library function
546/// parameter type programatically.
547///
548/// If you would prefer to have the Wolfram Language code for loading your library be
549/// generated automatically, use the [`generate_loader!`] macro.
550///
551/// <h4 style="border-bottom: none; margin-bottom: 4px"> ⚠️ Warning! ⚠️ </h4>
552///
553/// Calling a *LibraryLink* function from the Wolfram Language that was loaded using the
554/// wrong parameter type may lead to undefined behavior! Ensure that the function
555/// parameter type declared in your Wolfram Language code matches the Rust function
556/// parameter type.
557///
558/// Rust parameter type | Wolfram library function parameter type
559/// -----------------------------------|---------------------------------------
560/// [`bool`] | `"Boolean"`
561/// [`mint`] | `Integer`
562/// [`mreal`][crate::sys::mreal] | `Real`
563/// [`mcomplex`][crate::sys::mcomplex] | `Complex`
564/// [`String`] | `String`
565/// [`CString`][std::ffi::CString] | `String`
566/// [`&NumericArray`][NumericArray] | a. `LibraryExpressionEnum[NumericArray]` <br/> b. `{LibraryExpressionEnum[NumericArray], "Constant"}`[^1]
567/// [`NumericArray`] | a. `{LibraryExpressionEnum[NumericArray], "Manual"}`[^1] <br/> b. `{LibraryExpressionEnum[NumericArray], "Shared"}`[^1]
568/// [`&NumericArray<T>`][NumericArray] | a. `LibraryExpressionEnum[NumericArray, `[`"..."`][ref/NumericArray]`]`[^1] <br/> b. `{LibraryExpressionEnum[NumericArray, "..."], "Constant"}`[^1]
569/// [`NumericArray<T>`] | a. `{LibraryExpressionEnum[NumericArray, "..."], "Manual"}`[^1] <br/> b. `{LibraryExpressionEnum[NumericArray, "..."], "Shared"}`[^1]
570/// [`DataStore`] | `"DataStore"`
571///
572/// # Return types
573///
574/// The following table describes the relationship between Rust types that implement
575/// [`IntoArg`] and the compatible Wolfram *LibraryLink* function return type.
576///
577/// [`IntoArg::return_type()`] can be used to determine the Wolfram library function
578/// parameter type programatically.
579///
580/// Rust return type | Wolfram library function return type
581/// -----------------------------------|---------------------------------------
582/// [`()`][`unit`] | `"Void"`
583/// [`bool`] | `"Boolean"`
584/// [`mint`] | `Integer`
585/// [`mreal`][crate::sys::mreal] | `Real`
586/// [`i8`], [`i16`], [`i32`] | `Integer`
587/// [`u8`], [`u16`], [`u32`] | `Integer`
588/// [`f32`] | `Real`
589/// [`mcomplex`][crate::sys::mcomplex] | `Complex`
590/// [`String`] | `String`
591/// [`NumericArray`] | `LibraryExpressionEnum[NumericArray]`
592/// [`NumericArray<T>`] | `LibraryExpressionEnum[NumericArray, `[`"..."`][ref/NumericArray][^1]`]`
593/// [`DataStore`] | `"DataStore"`
594///
595/// Some LibraryLink types — notably `SparseArray` — have no [`FromArg`]/[`IntoArg`]
596/// impl and so can't be used here. Use `#[export(margs)]` instead and read/write
597/// the raw `MArgument.sparse` ([`MSparseArray`][crate::sys::MSparseArray])
598/// pointer by hand; see the "Raw native" section above.
599///
600/// [^1]: The Details and Options section of the Wolfram Language
601/// [`NumericArray` reference page][ref/NumericArray] lists the available element
602/// types.
603///
604/// [ref/NumericArray]: https://reference.wolfram.com/language/ref/NumericArray.html
605/// [ref/LibraryFunctionLoad]: https://reference.wolfram.com/language/ref/LibraryFunctionLoad.html
606///
607///
608///
609/// <br/><br/><br/>
610///
611/// # `#[export(wstp)]`
612///
613/// Export the specified functions as native *LibraryLink* WSTP functions.
614///
615/// To be exported by this macro, the specified function(s) must implement
616/// [`WstpFunction`].
617///
618/// Functions exported using this macro will automatically:
619///
620/// * Call [`initialize()`][crate::initialize] to initialize this library.
621/// * Catch any panics that occur.
622/// - If a panic does occur, it will be returned as a [`Failure[...]`][ref/Failure]
623/// expression.
624///
625/// [ref/Failure]: https://reference.wolfram.com/language/ref/Failure.html
626///
627/// # Syntax
628///
629/// Export a LibraryLink WSTP function.
630///
631/// ```
632/// # mod scope {
633/// # use wolfram_library_link::{export, wstp::Link, expr::Expr};
634/// #[export(wstp)]
635/// # fn square(args: Vec<Expr>) -> Expr { todo!() }
636/// # }
637/// ```
638///
639/// Export a LibraryLink WSTP function using the specified low-level shared library symbol
640/// name.
641///
642/// ```
643/// # mod scope {
644/// # use wolfram_library_link::{export, wstp::Link, expr::Expr};
645/// #[export(wstp, name = "WL_square")]
646/// # fn square(args: Vec<Expr>) -> Expr { todo!() }
647/// # }
648/// ```
649///
650/// # Examples
651///
652/// ##### WSTP function that squares a single integer argument:
653///
654/// ```
655/// # mod scope {
656/// use wolfram_library_link::{export, wstp::Link};
657///
658/// #[export(wstp)]
659/// fn square_wstp(link: &mut Link) {
660/// // Get the number of elements in the arguments list.
661/// let arg_count = link.test_head("List").unwrap();
662///
663/// if arg_count != 1 {
664/// panic!("square_wstp: expected to get a single argument");
665/// }
666///
667/// // Get the argument value.
668/// let x = link.get_i64().expect("expected Integer argument");
669///
670/// // Write the return value.
671/// link.put_i64(x * x).unwrap();
672/// }
673/// # }
674/// ```
675///
676/// ```wolfram
677/// LibraryFunctionLoad["...", "square_wstp", LinkObject, LinkObject]
678/// ```
679///
680/// ##### WSTP function that computes the sum of a variable number of arguments:
681///
682/// ```
683/// # mod scope {
684/// use wolfram_library_link::{export, wstp::Link};
685///
686/// #[export(wstp)]
687/// fn total_args_i64(link: &mut Link) {
688/// // Check that we recieved a functions arguments list, and get the number of arguments.
689/// let arg_count: usize = link.test_head("List").unwrap();
690///
691/// let mut total: i64 = 0;
692///
693/// // Get each argument, assuming that they are all integers, and add it to the total.
694/// for _ in 0..arg_count {
695/// let term = link.get_i64().expect("expected Integer argument");
696/// total += term;
697/// }
698///
699/// // Write the return value to the link.
700/// link.put_i64(total).unwrap();
701/// }
702/// # }
703/// ```
704///
705/// ```wolfram
706/// LibraryFunctionLoad["...", "total_args_i64", LinkObject, LinkObject]
707/// ```
708// Back-compat re-export: `#[wolfram_library_link::export]` and
709// `#[wolfram_library_link::export(wstp)]` both route through the
710// legacy-compat shim in `wolfram-export-macros::export`, which dispatches by
711// `wstp` keyword and forwards to the appropriate codegen. Same call-site
712// syntax as today.
713pub use wolfram_export_macros::export;
714
715// Const-eval feature guards emitted by the #[export] proc-macro.
716// Evaluated in a const context; panic bodies become compile-time errors with
717// actionable messages when the wrong mode is requested for this crate.
718#[doc(hidden)]
719pub const fn __assert_native_enabled() {}
720
721// Same C ABI/feature-gating as native — margs is "native, but you marshal
722// `&[MArgument]`/`MArgument` by hand" — so it's unconditionally available
723// wherever native is.
724#[doc(hidden)]
725pub const fn __assert_margs_enabled() {}
726
727#[cfg(feature = "wstp")]
728#[doc(hidden)]
729pub const fn __assert_wstp_enabled() {}
730#[cfg(not(feature = "wstp"))]
731#[doc(hidden)]
732pub const fn __assert_wstp_enabled() {
733 panic!(
734 "`#[export(wstp)]` requires enabling the `wstp` feature of `wolfram-library-link`"
735 );
736}
737
738// WXF mode is only available via `wolfram-export` with `features = ["wxf"]`.
739#[doc(hidden)]
740pub const fn __assert_wxf_enabled() {
741 panic!(
742 "`#[export(wxf)]` is not available from `wolfram-library-link`; \
743 use `wolfram-export` with `features = [\"wxf\"]` instead"
744 );
745}
746
747//======================================
748// Callbacks to the Wolfram Kernel
749//======================================
750
751/// Evaluate `expr` by calling back into the Wolfram Kernel.
752///
753/// TODO: Specify and document what happens if the evaluation of `expr` triggers a
754/// kernel abort (such as a `Throw[]` in the code).
755#[cfg(feature = "wstp")]
756pub fn evaluate(expr: &Expr) -> Expr {
757 match try_evaluate(expr) {
758 Ok(returned) => returned,
759 Err(msg) => panic!(
760 "evaluate(): evaluation of expression failed: {}: \n\texpression: {}",
761 msg, expr
762 ),
763 }
764}
765
766/// Attempt to evaluate `expr`, returning an error if a WSTP transport error occurred
767/// or evaluation failed.
768#[cfg(feature = "wstp")]
769pub fn try_evaluate(expr: &Expr) -> Result<Expr, String> {
770 with_link(|link: &mut Link| {
771 // Send an EvaluatePacket[expr].
772 let _: () = link
773 .put_expr(&expr!(System::EvaluatePacket[(expr.clone())]))
774 .map_err(|e| e.to_string())?;
775
776 let _: () = process_wstp_link(link)?;
777
778 let return_packet: Expr = link.get_expr().map_err(|e| e.to_string())?;
779
780 let returned_expr = match return_packet.kind() {
781 ExprKind::Normal(normal) => {
782 debug_assert!(normal.has_head(&Symbol::new("System`ReturnPacket")));
783 debug_assert!(normal.elements().len() == 1);
784 normal.elements()[0].clone()
785 },
786 _ => {
787 return Err(format!(
788 "try_evaluate(): returned expression was not ReturnPacket: {}",
789 return_packet
790 ))
791 },
792 };
793
794 Ok(returned_expr)
795 })
796}
797
798/// Returns `true` if the user has requested that the current evaluation be aborted.
799///
800/// Programs should finish what they are doing and return control of this thread to
801/// to the kernel as quickly as possible. They should not exit the process or
802/// otherwise terminate execution, simply return up the call stack.
803///
804/// Within Rust functions exported using [`#[export]`][crate::export] or
805/// [`#[export(wstp)]`][crate::export#exportwstp] (which generate a wrapper function that
806/// catches panics), `panic!()` can be used to quickly unwind the call stack to the
807/// appropriate place.
808/// Note that this will not work if the current library is built with
809/// `panic = "abort"`. See the [`panic`][panic-option] profile configuration option
810/// for more information.
811///
812/// [panic-option]: https://doc.rust-lang.org/cargo/reference/profiles.html#panic
813pub fn aborted() -> bool {
814 // TODO: Is this function thread safe? Can it be called from a thread other than the
815 // one the LibraryLink wrapper was originally invoked from?
816 let val: mint = unsafe { rtl::AbortQ() };
817 // TODO: What values can `val` be?
818 val == 1
819}
820
821/// Environment variable that opts in to backtrace resolution in caught-panic
822/// `Failure["RustPanic", …]` expressions (the `panic-failure-backtraces` feature
823/// must also be enabled). Resolving symbols is slow, so it's off unless set.
824#[cfg(feature = "panic-failure-backtraces")]
825const BACKTRACE_ENV_VAR: &str = "LIBRARY_LINK_RUST_BACKTRACE";
826
827// TODO: Instead of making these public, add new evaluate(..) alternative that
828// takes a WstpExpr type.
829#[cfg(feature = "wstp")]
830fn process_wstp_link(link: &mut Link) -> Result<(), String> {
831 assert_main_thread();
832
833 let raw_link = unsafe { link.raw_link() };
834
835 // Process the packet on the link.
836 let code: i32 = unsafe { rtl::processWSLINK(raw_link as *mut _) };
837
838 if code == 0 {
839 let error_message = link
840 .error_message()
841 .unwrap_or_else(|| "unknown error occurred on WSTP Link".into());
842
843 return Err(error_message);
844 }
845
846 Ok(())
847}
848
849/// Enforce exclusive access to the link returned by `getWSLINK()`.
850#[cfg(feature = "wstp")]
851fn with_link<F: FnOnce(&mut Link) -> R, R>(f: F) -> R {
852 assert_main_thread();
853
854 static LOCK: Lazy<Mutex<()>> = Lazy::new(|| Default::default());
855
856 let _guard = LOCK.lock().expect("failed to acquire LINK lock");
857
858 let lib = get_library_data().raw_library_data;
859
860 let unsafe_link: sys::WSLINK = unsafe { rtl::getWSLINK(lib) };
861 let mut unsafe_link: wstp::sys::WSLINK = unsafe_link as wstp::sys::WSLINK;
862
863 // Safety:
864 // By using LOCK to ensure exclusive access to the `getWSLINK()` value within
865 // safe code, we can be confident that this `&mut Link` will not alias with
866 // other references to the underling link object.
867 let link = unsafe { Link::unchecked_ref_cast_mut(&mut unsafe_link) };
868
869 f(link)
870}
871
872#[inline]
873fn bool_from_mbool(boole: sys::mbool) -> bool {
874 boole != 0
875}
876
877// TODO: Allow any type which implements FromExpr in wrapper parameter lists?
878
879/// Generate and export a "loader" function, which returns an Association containing the
880/// names and loaded forms of all functions exported by this library.
881///
882/// All functions exported by the [`#[export(..)]`][crate::export] macro will
883/// automatically be included in the Association returned by this function.
884///
885/// See also: [`exported_library_functions_association()`]
886///
887/// # Syntax
888///
889/// Generate and export an automatic loader function.
890///
891/// ```
892/// # use wolfram_library_link::generate_loader;
893/// generate_loader![load_my_library];
894/// ```
895///
896/// # Example
897///
898/// The following Rust program exports three primary functions via LibraryLink:
899///
900/// * `add2`
901/// * `flat_total_i64`
902/// * `time_since_epoch`
903///
904/// These functions are exported from the library using the
905/// [`#[export(..)]`][crate::export] macro. This makes them loadable using
906/// [`LibraryFunctionLoad`][ref/LibraryFunctionLoad]<sub>WL</sub>.
907///
908///
909/// ```
910/// # mod scope {
911/// use wolfram_library_link::{self as wll, NumericArray, expr::{expr, Expr}};
912///
913/// wll::generate_loader![load_my_library_functions];
914///
915/// #[wll::export]
916/// fn add2(x: i64, y: i64) -> i64 {
917/// x + y
918/// }
919///
920/// #[wll::export]
921/// fn flat_total_i64(list: &NumericArray<i64>) -> i64 {
922/// list.as_slice().into_iter().sum()
923/// }
924///
925/// #[wll::export(wstp)]
926/// fn time_since_epoch(args: Vec<Expr>) -> Expr {
927/// use std::time::{SystemTime, UNIX_EPOCH};
928///
929/// assert!(args.len() == 0, "expected no arguments, got {}", args.len());
930///
931/// let duration = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
932///
933/// expr!(System::Quantity[(duration.as_secs_f64()), "Seconds"])
934/// }
935/// # }
936/// ```
937///
938/// WXF-mode functions (from `wolfram-export` with `features = ["wxf"]`) are also
939/// included automatically. They appear in the loader output as:
940///
941/// ```wolfram
942/// "myFn" -> LibraryFunction[lib, "myFn", {LibraryDataType[ByteArray]}, LibraryDataType[ByteArray]]
943/// ```
944///
945/// and are called from Wolfram using `BinarySerialize` / `BinaryDeserialize` automatically
946/// when loaded via the paclet loader generated by `generate_loader!`.
947///
948/// However, writing out the correct invocations of
949/// [`LibraryFunctionLoad`][ref/LibraryFunctionLoad] can be tedious and error prone.
950/// `generate_loader!` provides an easier alternative way to load the functions
951/// exported by this library.
952///
953/// In addition to the three previously mentioned functions, this library also exports a
954/// fourth function, called `load_my_library_functions`.
955///
956/// Instead of writing three separate `LibraryFunctionLoad` calls, one for each exported
957/// function, you can instead load the single `load_my_library_functions` function, which,
958/// when called, will automatically load the other three functions exported by this
959/// library:
960///
961/// ```wolfram
962/// library = "example_library";
963///
964/// loadFunctions = LibraryFunctionLoad[library, "load_my_library_functions", LinkObject, LinkObject];
965///
966/// functions = loadFunctions[library];
967/// ```
968///
969/// The `functions` variable will be an association, with roughly the following content:
970///
971/// ```wolfram
972/// <|
973/// "add2" -> LibraryFunction["example_library", "add2", {Integer, Integer}, Integer],
974/// "flat_total_i64" -> LibraryFunction[
975/// "example_library",
976/// "flat_total_i64",
977/// {{LibraryExpressionEnum[NumericArray, "Integer64"], "Constant"}},
978/// Integer
979/// ],
980/// "time_since_epoch" -> LibraryFunction["example_library", "time_since_epoch", LinkObject]
981/// |>
982/// ```
983///
984/// As shown above, the `load_my_library_functions` function generated by
985/// `generate_loader!` has automatically mapped the Rust paramater and return types onto
986/// the appropriate Wolfram LibraryLink types.
987///
988/// Functions from the library can be called by applying arguments to the appropriate
989/// value from the `functions` association:
990///
991/// ```wolfram
992/// (* Returns 12 *)
993/// functions["add2"][4, 8]
994///
995/// (* Returns 6 *)
996/// functions["flat_total_i64"][NumericArray[{1, 2, 3}, "Integer64"]]
997///
998/// (* Returns Quantity[seconds_, "Seconds"], containing the current number of seconds
999/// since the Unix epoch time. *)
1000/// functions["time_since_epoch"][]
1001/// ```
1002///
1003/// [ref/LibraryFunctionLoad]: https://reference.wolfram.com/language/ref/LibraryFunctionLoad.html
1004#[cfg(all(feature = "automate-function-loading-boilerplate", feature = "wstp"))]
1005#[macro_export]
1006macro_rules! generate_loader {
1007 ($name:ident) => {
1008 const _: () = {
1009 #[no_mangle]
1010 pub unsafe extern "C" fn $name(
1011 lib: $crate::sys::WolframLibraryData,
1012 raw_link: $crate::wstp::sys::WSLINK,
1013 ) -> std::os::raw::c_int {
1014 $crate::macro_utils::load_library_functions_impl(lib, raw_link)
1015 }
1016 };
1017 };
1018}