matlab-sys
provides low level bindings to Matlab's C API. This allows writing MEX functions, using the C Matrix API,
interacting with the C MAT API and authoring Matlab Engine Applications using Rust.
The bindings are mostly automatically generated by bindgen and named the same or similar as in the C headers.
Some manual changes are made to the bindings to ease usage or simplify the exposed API without sacrificing
functionality, such as replacing type definitions for the fixed size integer types with the native fixed size types
from Rust.
To make use of the functionality provided by this crate it has to be linked against the library files libmex.lib
,
libmx.lib
, libmat.lib
and libeng.lib
provided by your installation of Matlab. If the environment variable
MATLABPATH
is set to the directory of the Matlab installation to link against, this path will be used preferentially.
Otherwise the build script will attempt to seek the installation directory of the default matlab installation by running
the following command on the build machine: matlab -batch "disp(matlabroot)"
. This usually takes a few seconds for
Matlab to start up, so explicitly providing the path in the aforementioned environment variable helps compile times
and should be preferred.
If you have special requirements with regard to the linking process which can not be handled by the approach described
above, the necessary link directives can be specified in a .config
file with the rustc-link-lib
and rustc-link-search keys. Further information can be found here.
To build a mex function in Rust the crate type has to be a dynamic link library with a C ABI. In your cargo.toml
set crate-type = ["cdylib"]
. Every MEX function, regardless what name it will be called with from Matlab, needs an
entry point with the following signature:
#[no_mangle]
pub unsafe extern "C" fn mexFunction(
nlhs: c_int,
plhs: *mut *mut mxArray,
nrhs: c_int,
prhs: *const *const mxArray,
){/* your calculation */}
After building, change the file extension of the build artifact to *.mexw64
for windows or *.mexa64
for
linux. Copy the renamed file somewhere where Matlab can find it. You should now be able to call your mex function using
the filename without its extension.
A quick and simple translation of the arrayProduct example, which multiplies a scalar with a matrix (without most error handling for brevity) would look like this:
use matlab_sys::interleaved_complex as raw;
use std::ffi::{c_int, CString};
#[no_mangle]
pub unsafe extern "C" fn mexFunction(
nlhs: c_int,
plhs: *mut *mut raw::mxArray,
nrhs: c_int,
prhs: *const *const raw::mxArray,
) {
if nrhs != 2 {
// Letting the standard library do the work of making Rusts strings C-compatible
raw::mexErrMsgIdAndTxt(
CString::new("MyToolbox:arrayProduct:nrhs").unwrap().as_ptr(),
CString::new("Two inputs required.").unwrap().as_ptr()
);
}
if nlhs != 1 {
// Writing C-compatible strings manually
raw::mexErrMsgIdAndTxt(
b"MyToolbox:arrayProduct:nlhs\0".as_ptr().cast(),
b"One output required.\0".as_ptr().cast()
);
}
// SAFETY: nlhs and nrhs should get verified to be greater or equal to 0 and all pointers should get tested for validity
let plhs = std::slice::from_raw_parts_mut(plhs, nlhs as usize);
let prhs = std::slice::from_raw_parts(prhs, nrhs as usize);
/* get the value of the scalar input */
let multiplier = raw::mxGetScalar(prhs[0]);
/* create a pointer to the real data in the input matrix */
let inMatrix = raw::mxGetDoubles(prhs[1]);
/* get dimensions of the input matrix */
let ncols = raw::mxGetN(prhs[1]);
/* create the output matrix */
plhs[0] = raw::mxCreateDoubleMatrix(1, ncols, raw::mxComplexity::mxREAL);
/* get a pointer to the real data in the output matrix */
let outMatrix = raw::mxGetDoubles(plhs[0]);
/* call the computational routine */
array_product(multiplier, inMatrix, outMatrix, ncols);
}
unsafe fn array_product(x: f64, y: *mut f64, z: *mut f64, n: usize) {
unsafe {
for i in 0..n {
*z.add(i) = x * *y.add(i);
}
}
}
More examples can be found in the mex-examples directory. These examples are direct translations of the
original C examples as distributed with Matlab in matlabroot/extern/examples
. They are also used as tests for the
bindings, as such they should always compile and work.
While the ability of Rust to interact with C libraries is nice, its true power lies in its ability to build safe, ergonomic,
easy-to-use and hard-to-misuse abstractions. This crate in intended to work as a common building block for those abstractions
in the context of Matlab extensions.
If you happen to work on such an abstraction please let me know! I would love to know how my work
is being used and to promote your crate here.
matlab-sys
exposes both the older separate-complex C API which stands to be deprecated by Mathworks in
the future as well as the newer interleaved-complex C API, which is set to become the default in Matlab instead.
While most functions present in the separate-complex API are also present in the interleaved-complex API, there are some important
differences. The interleaved-complex API for instance provides new convenient and safe(er) typed data access functionality.
Most importantly though, some types have a different representation (e.g. mxArray of complex numbers) and some functions
behave differently even though the API stayed the same (e.g. mxGetData). To minimize the resulting risk of confusion
between both versions of the API, matlab-sys
exposes them in separate namespaces.
Do not use functions of one namespace with types of the other one. Since the namespace is part of the type, Rust's type system helps to prevent this issue. To further prevent this issue it is recommended to activate only the API you intend to use in your crate using the corresponding features. More information about the differences of the APIs can be found here.
In Matlab it is impossible to enable both versions of the API at the same time when using the mex
command,
which mitigates the danger of mixing APIs. While this mutually exclusive API could also be achieved in this crate
by using mutually exclusive features, this would lead to some significant drawbacks.
The dependency resolver of cargo unifies all features of a crate which are enabled by any dependent crate in
the dependency tree, effectively compiling the crate with the union of all enabled features in the project.
In the case of mutually exclusive features this would lead to the inability for multiple separate
intermediate dependencies to use the different APIs, even internally. By keeping them in separate namespaces, the features to
enable them satisfy cargo's requirement for features to be purely additive.
This feature enables the older API version and corresponds to the API available when compiling with Matlab's mex
command without additional arguments or with the additional argument -R2017b
. As the name implies arrays of complex numbers are represented internally by
separate arrays for the real and the imaginary part. This is the only available option for Matlab versions R2017b
and prior.
For all later releases Matlab uses the interleaved-complex representation internally. Using this API with Matlab releases
after R2017b
will incur potentially unnecessary copying when translation to the separate complex representation happens.
In addition many new features of the newer API, such as typed data access, are not available.
This feature enables the newer API version and corresponds to the API available when compiling with Matlab's mex
command
with the additional argument -R2018a
. This API is set to become the new default API for compilation with Matlab's mex
command
in the future. Arrays of complex numbers are represented internally by a single array with interleaved real and
imaginary parts. This is the representation Matlab uses internally since release R2018a
. As a result no copying has to
take place at the interface. This representation is also used by most popular numeric libraries, further reducing the
need for copying or transformations. Using this feature with a Matlab release older than R2018a
will not work!
For all newer releases this is the recommended API.