rustmex/
function.rs

1/*!
2 * Functions and function handles for calling back to Matlab
3 *
4 * This module contains functionality to call back into Matlab. Both calling via a
5 * `function_handle` or a named matlab function (such as "sqrt") are supported.
6 */
7use core::ops::{Deref, DerefMut};
8use rustmex_core::{
9	convert::{
10		FromMatlabError,
11	},
12	mxArray,
13	classid::ClassID,
14	pointers::{
15		MatlabPtr,
16		MutMatlabPtr,
17		MxArray,
18	},
19	shim::{
20		rustmex_call_matlab as mexCallMATLAB,
21	},
22
23	MatlabClass,
24	MutMatlabClass,
25	OwnedMatlabClass,
26};
27
28use std::{
29	ffi::{
30		CString,
31		CStr,
32	}
33};
34
35/**
36 * Call a named Matlab function, such as `sqrt`, or `fmincon`. If an error occurs,
37 * control is returned to the matlab prompt. Note that this might leak memory.
38 *
39 * Note that this function converts the function name into a CString. If you already have
40 * a null-terminated string available, consider using [`call_named_nul`].
41 */
42pub fn call_named<FnName>(f: FnName, nargout: usize, rhs: &[&mxArray])
43	-> Result<Box<[Option<MxArray>]>, FunctionCallError>
44where
45	FnName: Into<Vec<u8>>,
46{
47	let f = CString::new(f).map_err(|_| FunctionCallError::BadEncoding)?;
48	call_named_nul(&f, nargout, rhs).ok_or(FunctionCallError::CallFailed)
49}
50
51/**
52 * Call a named Matlab function, named with a nul terminated byte string.
53 */
54pub fn call_named_nul(f: &CStr, nargout: usize, rhs: &[&mxArray])
55	-> Option<Box<[Option<MxArray>]>>
56{
57	let lhs = vec![None; nargout].into_boxed_slice();
58
59	let ret = unsafe {
60		mexCallMATLAB(
61			lhs.len().try_into().expect("Don't expect 4 billion return values"),
62			lhs.as_ptr() as *mut *mut mxArray,
63			rhs.len().try_into().expect("Don't expect 4 billion arguments"),
64			rhs.as_ptr() as *mut *mut mxArray,
65			f.as_ptr() as *const i8
66		)
67	};
68
69	if ret == 0 {
70		Some(lhs)
71	} else {
72		None
73	}
74}
75
76
77/**
78 * Typed pointer representing a MATLAB `function_handle`
79 *
80 * Can be created from an mxArray with the correct class, and can then be used to call
81 * back into matlab.
82 */
83#[repr(transparent)]
84#[derive(Debug)]
85pub struct Function<F: MatlabPtr>(F);
86
87#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
88pub enum FunctionCallError {
89	BadEncoding,
90	CallFailed,
91}
92
93impl<P> Deref for Function<P> where P: MatlabPtr {
94	type Target = mxArray;
95
96	fn deref(&self) -> &Self::Target {
97		&self.0
98	}
99}
100
101impl<P> DerefMut for Function<P> where P: MutMatlabPtr {
102	fn deref_mut(&mut self) -> &mut Self::Target {
103		&mut self.0
104	}
105}
106
107
108// Bytestring to call feval with without having to construct the bytestring every time.
109const FEVAL: &[u8; 6] = b"feval\0";
110
111impl<F> Function<F> where F: MatlabPtr + AsRef<mxArray> {
112	/**
113	 * Call the function handle with the specified input arguments and number of
114	 * output arguments.
115	 */
116	// TODO: Add a check for too many arguments (limited by fifty in, fifty out).
117	pub fn call(&self, nargout: usize, rhs: &[&mxArray])
118		-> Option<Box<[Option<MxArray>]>>
119	{
120		// Since we're calling the function handle via feval, we need to
121		// "push_front" the function_handle onto the left-hand-side array
122		let rhs = {
123			let mut v = Vec::with_capacity(rhs.len() + 1);
124			v.push(self.0.as_ref());
125			v.extend(rhs.iter().map(|x| x.as_ref()));
126			v
127		};
128
129		// SAFETY: We already added the nul to FEVAL ourselves
130		call_named_nul(unsafe { CStr::from_bytes_with_nul_unchecked(FEVAL) },
131			nargout,
132			&rhs)
133	}
134}
135
136impl<P> MatlabClass<P> for Function<P> where P: MatlabPtr {
137	fn from_mx_array(mx: P) -> Result<Self, FromMatlabError<P>> {
138		if mx.numel() != 1 {
139			return Err(FromMatlabError::new_badsize(mx));
140		}
141
142		if mx.class_id() == Ok(ClassID::Function) {
143			Ok(Function(mx))
144		} else {
145			Err(FromMatlabError::new_badclass(mx))
146		}
147	}
148
149	fn into_inner(self) -> P {
150		self.0
151	}
152
153	fn inner(&self) -> &P {
154		&self.0
155	}
156
157	type Owned = Function<MxArray>;
158	fn duplicate(&self) -> Self::Owned {
159		Function(self.0.duplicate())
160	}
161}
162
163impl<P> MutMatlabClass<P> for Function<P> where P: MutMatlabPtr {
164	type AsBorrowed<'a> = Function<&'a mxArray> where Self: 'a;
165	fn as_borrowed<'a>(&'a self) -> Self::AsBorrowed<'a> {
166		Function(self.0.deref())
167	}
168
169	fn inner_mut(&mut self) -> &mut P {
170		&mut self.0
171	}
172}
173
174impl OwnedMatlabClass for Function<MxArray> {
175	type AsMutable<'a> = Function<&'a mut mxArray> where Self: 'a;
176	fn as_mutable<'a>(&'a mut self) -> Self::AsMutable<'a> {
177		Function(self.0.deref_mut())
178	}
179}