rustmex 0.6.4

Rustmex: providing convenient Rust bindings to Matlab MEX API's
Documentation
/*!
 * Allocation related functionality
 *
 * 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).
 */
use std::alloc::{GlobalAlloc, Layout};
use std::os::raw::c_void;
use rustmex_core::shim::{rustmex_malloc, rustmex_free, rustmex_make_memory_persistent};

/**
 * Simple allocator which forwards allocation requests the mex allocator.
 *
 * By default, matlab returns a pointer, which memory will be cleared after the mex
 * function returns. This is a problem when some data structure assumes the memory won't
 * be cleared, for example with threads. Since we can rely on Rust's ownership model, it
 * is okay (for the most part) to make every non-null pointer returned persistent.
 *
 * Note that Rust globals which hold references to the heap will never be deallocated.
 * For instance, Rayon's global thread pool is orchestrated via some global static value.
 * Since the allocations are static, this will live even after the mex function returns.
 * This may or may not be desired behaviour.
 */
// In line with the matlab types (see raw.rs), we make use bad style for the allocator
// struct too.
#[allow(non_camel_case_types)]
pub struct mxAlloc;

// Only compile this in if we're not running under the test harness
#[cfg(not(test))]
#[cfg(any(feature = "alloc", feature = "doc"))]
#[global_allocator]
static ALLOCATOR: mxAlloc = mxAlloc;

unsafe impl GlobalAlloc for mxAlloc {

	unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
		let ptr = rustmex_malloc(layout.size());
		// TODO: Put this behind a configuration flag?
		//
		// There might be some implementations which do not want or need this
		// behaviour, and actually do want their globals to be freed. This only
		// works for trivial Drop implementations however; Matlab does not run
		// the destructors for us.
		if !ptr.is_null() {
			rustmex_make_memory_persistent(ptr);
		}
		ptr as *mut u8
	}

	unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
		rustmex_free(ptr as *mut c_void);
	}
}

use std::ptr::NonNull;
use std::ops::{Deref, DerefMut};

/**
 * Smart pointer around a nonpersistent allocation of T.
 *
 * Rustmex marks all heap allocations as persistent — for most allocations, we want to be
 * the only owner of the data. We know how to free the memory of our types, and will do
 * so when we're done with it; we don't need Matlab's help.
 *
 * But there are some circumstances where we can't, and thus where a nonpersistent
 * allocation is required: for instance with divergent MEX API functions. If you need to
 * pass some data to such a function which is dynamic and must be behind a pointer, you
 * can use this type.
 *
 * It is specifically meant to work with those API functions; it is not a general usage
 * smart pointer. To make it slightly less of a footcannon, it implements some guardrails
 * (such as [`Drop`] in case the function is conditionally divergent, or if you forgot to
 * pass it into one you wont leak memory) — but it remains a footgun nonentheless.
 *
 * A footgun it is, as alluded to, because it fundamentally does not comply with the
 * basic principles of safe Rust code, i.e. there can be only one owner of the data. With
 * `NonPersistent`, you now share ownership with Matlab's garbage collector — which,
 * since it does not know about you, may pull the rug out from underneath you at any
 * point (especially if you persist the object after `MexFunction` returns). Maybe not
 * such a footgun, but more of a ticking time bomb. All constructor methods are therefore
 * marked as `unsafe`; you must understand that memory safety is now up to you to uphold.
 *
 * To make it somewhat ergonomic to work with, it implements a basic set of traits
 * (PartialEq, Display, AsRef, etc) by forwarding those to the inner types. It's
 * basically a meager Box.
 *
 * It is also Send and Sync if the inner types are. While this has can be used to hold on
 * to the `NonPersistent` longer than the lifetime of the `MexFunction`, it is not _per
 * se_ memory unsafe.
 *
 * In short: only use for good, and only when absolutely required.
 */
pub struct NonPersistent<T: ?Sized>(NonNull<T>);

impl<T> NonPersistent<T> {
	/**
	 * Create a new non-persistent memory allocation.
	 */
	pub unsafe fn new(t: T) -> Self {
		let nn = NonNull::new(rustmex_malloc(std::mem::size_of::<T>()) as *mut T)
			.expect("OOM");

		std::ptr::write(nn.as_ptr(), t);

		Self(nn)
	}
}

impl<T: Copy> NonPersistent<T> {
	/// Return the inner value, and free the allocation
	pub fn inner(np: NonPersistent<T>) -> T {
		*unsafe { np.0.as_ref() }
	}
}

// Make the link in the doc comment below work
#[cfg(doc)]
#[allow(unused_imports)]
use std::ffi::CString;

impl NonPersistent<[u8]> {
	/**
	 * Construct a [`NonPersistent`] byte buffer from stringish types, optionally
	 * with a nul byte terminator.
	 *
	 * Some MEX API functions expect a C string, while most of rust works with string
	 * slices. Instead of copying to a [`CString`] first (to add the nul terminator),
	 * directly copy into a nonpersistent buffer, eliding one copy.
	 *
	 * Note that this function provides fewer guarantees than [`CString`] does.
	 */
	pub unsafe fn from_stringish<B: AsRef<[u8]>>(b: B, append_null: bool) -> Self {
		let b = b.as_ref();

		let l = b.len() + if append_null { 1 } else { 0 };
		let ptr = rustmex_malloc(l) as *mut u8;
		if ptr.is_null() {
			panic!("OOM");
		}
		let b2 = std::slice::from_raw_parts_mut(ptr, l);

		b2[0..b.len()].copy_from_slice(b);

		if append_null {
			b2[b.len()] = b'\0';
		}

		NonPersistent(NonNull::from(b2))
	}
}

impl<T: ?Sized> NonPersistent<T> {
	/**
	 * Get the inner pointer for communication with the MEX api. Marked as unsafe
	 * since the pointer may be used to create an alias to the data, so the
	 * invariants assumed by Send and Sync no longer hold.
	 */
	pub unsafe fn ptr(np: &NonPersistent<T>) -> *const T {
		np.0.as_ptr()
	}

	/**
	 * Get the inner pointer as mutable for communcation with the MEX api. Marked as
	 * unsafe since the pointer may be used to create an alias to the data, so the
	 * invariants assumed by Send and Sync no longer hold.
	 */
	pub unsafe fn mut_ptr(np: &mut NonPersistent<T>) -> *mut T {
		np.0.as_ptr()
	}

	/**
	 * Persist the current allocation by turning it into a "normal" box.
	 *
	 * This does not copy the allocation, since there is a function to mark an
	 * allocation as persistent (but not the other way around).
	 */
	pub fn persist(mut np: NonPersistent<T>) -> Box<T> {
		let ptr = unsafe { Self::mut_ptr(&mut np) };
		unsafe { rustmex_make_memory_persistent(ptr as *mut c_void); }
		std::mem::forget(np);
		unsafe { Box::from_raw(ptr) }
	}
}

impl<T: ?Sized> Deref for NonPersistent<T> {
	type Target = T;

	fn deref(&self) -> &Self::Target {
		unsafe { self.0.as_ref() }
	}
}

impl<T: ?Sized> DerefMut for NonPersistent<T> {
	fn deref_mut(&mut self) -> &mut Self::Target {
		unsafe { self.0.as_mut() }
	}
}

impl<T: ?Sized> AsRef<T> for NonPersistent<T> {
	fn as_ref(&self) -> &T {
		self.deref()
	}
}

impl<T: ?Sized> AsMut<T> for NonPersistent<T> {
	fn as_mut(&mut self) -> &mut T {
		self.deref_mut()
	}
}

impl<T: ?Sized> std::ops::Drop for NonPersistent<T> {
	fn drop(&mut self) {
		unsafe {
			let ptr = self.0.as_mut();
			std::ptr::drop_in_place(ptr);
			rustmex_free(ptr as *mut T as *mut c_void);
		}
	}
}

// SAFETY: NonNull says about itself that it isn't sync because the data it points to may
// be aliased. However, with the exception of unsafe methods, we know that we manage a
// pointer we allocated ourselves — it isn't aliased; we hold the only one — so we can
// safely implement Send and Sync when the inner types are.
//
// While technically, Send and Sync can be used to hold on to a NonPersistent for longer
// than the lifetime of the MexFunction, they are not unsafe by themselves. You can hold
// on to a NonPersistent by stuffing it into a static variable, for instance. So if you
// want to shoot yourself in the foot, that is up to you.
unsafe impl<T> std::marker::Sync for NonPersistent<T> where T: Sync + ?Sized {}
unsafe impl<T> std::marker::Send for NonPersistent<T> where T: Send + ?Sized {}

use std::fmt;

impl<T: ?Sized> fmt::Debug for NonPersistent<T> where T: fmt::Debug {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		write!(f, "NonPersistent(")?;
		self.deref().fmt(f)?;
		write!(f, ")")
	}
}

impl<T: ?Sized> fmt::Display for NonPersistent<T> where T: fmt::Display {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		self.deref().fmt(f)
	}
}

use std::cmp::{PartialEq, Eq};

impl<T: ?Sized, Other> PartialEq<Other> for NonPersistent<T> where T: PartialEq<Other> {
	fn eq(&self, other: &Other) -> bool {
		self.deref().eq(other)
	}
}

impl<T: ?Sized> Eq for NonPersistent<T> where T: Eq + PartialEq<NonPersistent<T>> {}