A library providing convenient Rust bindings to Matlab's MEX C api.
Rustmex makes writing MEX functions in Rust a bit easier. It convert Matlab types, and
the arguments it provides to mexFunction
into more Rusty types, which can then be used
to interface with other Rust code easily.
In this readme: 1. Installation 2. Usage and Examples 3. Design and Internals 4. Future plans and TODO 5. Licence
The installation of this crate is slightly more involved than you might be used to: you
need to select a target API. This target API depends on what version of Matlab or
GNU/Octave you are targeting with your library. See the API section for that.
Once you've selected your target API, put the following in your Cargo.toml
:
toml
rustmex = { version = "0.3", features = ["matlab_interleaved"] }
where "matlab_interleaved"
is then one of the options for target APIs.
Furthermore, if want to use rustmex to write a MEX file, you need to add the following to
your Cargo.toml
file too:
toml
[lib]
crate-type = ["cdylib"]
Compiling your crate then results in a C dynamic library, instead of a Rust library.
Each MEX function file has an entrypoint called mexFunction
. This is a C FFI function,
which, preferably, you do not want to write yourself.
Instead, rustmex provides the entrypoint macro; a macro to mark your Rust entrypoint with. For example: ```rust use rustmex::prelude::*;
fn hello_world(lhs: Lhs, rhs: Rhs) -> rustmex::Result<()> { println!("Hello Matlab!"); Ok(()) } ```
Note that this example mirrors the definition of mexFunction
itself: Matlab has already
allocated a return value array, you just need to place your results into it.
The FromMatlabError
is for when you want to convert an mxArray into a more Rusty data
type. These conversions are not infallible: Matlab is dynamically typed, and the provided
mxArray
must have the same type and size as the type we want to convert it into.
As a more convoluted example: ```rust use rustmex::prelude::*;
fn euclideanlength(lhs: Lhs, rhs: Rhs) -> rustmex::Result<()> { let v: &[f64] = rhs .get(0) .errorifmissing("euclideanlength:missinginput", "Missing input vector to compute the length of")? .dataslice()?;
// I recommend to only execute your planned computation once you're sure it
// actually needs to be computed — it's a bit of a waste to compute an expensive
// result without somewhere to return it to.
if let Some(ret) = lhs.get_mut(0) {
ret.replace(v.iter().map(|x|x*x).sqrt().to_matlab());
}
Ok(())
} ```
This example computes the Euclidean length of an input vector. Note that type annotations
are (almost always) needed for the return type of data_slice()
and from_matlab()
.
Regarding [FromMatlab
][convert::FromMatlab
], this library also supports building
NDarrays from mxArrays. It will ensure that NDarray understands the data the same way
matlab does. For example, the following prints out a debug representation of the array:
```rust
use rustmex::prelude::*;
fn displaymatrix(lhs: Lhs, rhs: Rhs) -> rustmex::Result<()> {
if let Some(mx) = rhs.get(0) {
let mat = ArrayViewD
Calling back into Matlab is also supported. For example, to compute the square
root of the sum of squares (i.e. nd-pythagoras):
matlab
% Call Rust MEX file
x = rusty_fn_call(@(x) sqrt(sum(x.^2)), 1:10)
```rust
use rustmex::prelude::*
use rustmex::function::Function;
fn rustyfncall(lhslice: rustmex::Lhs, rhslice: rustmex::Rhs) -> rustmex::Result<()> {
let f = rhslice
.get(0)
.error_if_missing("rusthello:no_fn", "Didn't get a function")?
.to_rust::<Function<_>>()?;
if let Some(r) = lhslice.get_mut(0) {
// Just forward the remaining arguments
let mut results: Box<[Option<MxArray>; 1]> = f.call(&rhslice[1..]).unwrap();
let v = results[0].take().unwrap();
r.replace(v);
};
return Ok(());
}
prints:
x = 19.621
```
Writing MEX extensions is not as straightforward as you might be useful. In particular, Matlab (by default) breaks some fundamental assumptions Rust makes on memory management. do not skip over the memory management section.
The design of the library consists of three levels: 1. The Raw FFI level: the C FFI definitions 2. The wrapped mex level: functionality wrapping the raw level for a more ergonomic and safe Rust interface 3. Top level: Wrapping the mex level, providing easy conversions to and from mxArrays.
It is recommended to write against the level which supports your features. If Rustmex does not implement functionality you need, the lower levels are still available for that.
Rustmex wraps the bindings exposed by Matlab (pre and post 2017b) and GNU/Octave. The
features these bindings provide are all slightly different, so select one via a cargo
feature. These are:
- matlab_interleaved
: Matlab, release 2018a and later, with the interleaved complex
API;
- matlab_separated
: Matlab, release 2017b and earlier, with the separate real and
complex parts of mxArrays;
- octave
: for GNU/Octave. (uses the same representation as matlab_separated)
If you've compiled your crate as a cdylib
, you can then take the resultant dynamic
library (on linux a .so
), copy it to the target location in your Matlab path, with the
appropriate file extension. (.mexa64
for MEX on 64 bit Linux, .mexw64
for MEX on
Windows, and .mex
for GNU/Octave.)
Some of these targets also use symbol versioning, so e.g. mxCreateNumericArray
is
actually mxCreateNumericArray_800
for the matlab_interleaved API. For ease of use,
rustmex renames (pub use $x_$v as $x
) these symbols to all have the same name in
[mex::raw].
When Rustmex is compiled for an API which represents complex values with two arrays, slightly different implementations for data conversions are used. Since most Rust code assumes that complex values are interleaved (i.e., the real and imaginary parts of the value are stored next to eachother in memory) this might cause a lot of copying on the interface.
See also https://nl.mathworks.com/help/matlab/matlab_external/matlab-support-for-interleaved-complex.html.
The Mex API has a few quirks when it comes to memory management. In particular,
allocations made via mxMalloc
are, by default, deallocated when the MEX function
returns. This causes problems when some part of Rust code assumes that they are
responsible for deallocating the memory and assuming it is still after. Rustmex therefore
marks all allocations as persistent via mexMakeMemoryPersistent
. This may cause your
MEX function to leak instead of crash (the latter takes Matlab with it). See also
[mex::alloc::mxAlloc
].
Array<HashMap<Field, Value>>
mexAtExit
(also important for dropping static values.mexLock
/mexUnlock
mexGetVariablePtr
/mexGetVariable
/mexPutVariable
This is licensed to you under the Mozilla Public License, version 2. You can the licence in the LICENCE file in this project's source tree root folder.