/*! * Conversion to and from matlab with associated errors * * The rustmex family of crates define a set of wrapper types, to encapsulate an untyped * [`MatlabPtr`](crate::pointers::MatlabPtr) with some type information, after which it's * contents can be accessed safely. This module defines the error types and other * conversion tooling. */ use std::fmt::{self, Display}; /** * Error converting from matlab. Stores the unconverted data, allowing it to be reused, * especially for owned data. */ pub struct FromMatlabError

{ unconverted_data: P, the_reason: FromMatlabErrorReason, } impl FromMatlabError { pub fn new(unconverted_data: S, the_reason: FromMatlabErrorReason) -> Self { Self { unconverted_data, the_reason, } } pub fn new_badclass(unconverted_data: S) -> Self { Self::new(unconverted_data, FromMatlabErrorReason::BadClass) } pub fn new_badcomplexity(unconverted_data: S) -> Self { Self::new(unconverted_data, FromMatlabErrorReason::BadComplexity) } pub fn new_badsparsity(unconverted_data: S) -> Self { Self::new(unconverted_data, FromMatlabErrorReason::BadSparsity) } pub fn new_badsize(unconverted_data: S) -> Self { Self::new(unconverted_data, FromMatlabErrorReason::Size) } pub fn original(self) -> S { self.unconverted_data } pub fn reason_ref(&self) -> &FromMatlabErrorReason { &self.the_reason } pub fn reason(&self) -> FromMatlabErrorReason { self.the_reason } } /** * Possible errors which can occur when converting an mxArray into a Rust type. */ #[derive(Debug, Copy, Clone, PartialEq, Hash)] #[non_exhaustive] pub enum FromMatlabErrorReason { /// Returned when the type of the mxArray does not agree BadClass, /// Returned when the complexity of the mxArray does not agree with the type BadComplexity, /// Returned when the sparsity does not match the expected sparsity BadSparsity, /// Returned when the size of the mxArray does not match Size, } impl Display for FromMatlabError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // TODO: This should probably a bit more specific, but the enum atm does // not store the source mxArray nor the target type, so we can't (yet). write!(f, "Error converting mxArray to rust type") } } /** * Most Vectors are either a column-, or a row vector. This enum can be used to configure * that direction via [`FromVec`]. */ #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum VecType { Column, Row } impl VecType { /** * Construct the shape matrix. * * For a row vector, it will return `[1, length]`, since, for a row vector there * are `length` columns; vice versa for a row vector. */ pub fn shape_matrix(&self, length: usize) -> [usize; 2] { match self { VecType::Row => [1, length], VecType::Column => [length, 1] } } } /// The default vectype is a column. impl Default for VecType { fn default() -> Self { Self::Column } } /** * Trait for types describing how an array of vectors should be layed out when passed to * Matlab. Matlab arrays are, after all, multidimensional; the vector can lay among any * of them. */ pub trait VecLayout { /** * The list of numbers describing the vectors layout to Matlab. For flexibility, * any type which can be referenced to as a usize-slice is allowed. */ type Layout: AsRef<[usize]>; /** * Given the array length, compute the [`Layout`](Self::Layout) which needs to be * passed to matlab. */ fn layout(&self, length: usize) -> Self::Layout; } impl VecLayout for VecType { /// For a vectype, the layout will always be two usizes. type Layout = [usize; 2]; fn layout(&self, length: usize) -> Self::Layout { self.shape_matrix(length) } } /** * At compile time one might already know along what dimension to lay out a vector. That * dimension (also for higher dimensionality than rows/columns) can be selected via * `Cat1`. * * As the name alludes to, somewhat confusingly the const generic does not specify the * index of the dimension as 0-based, but as 1-based --- and therefore also the length of * the dimensions array. This is due to a limitation of the Rust type system, which, at * the moment, does not allow (useful) arithmetic with const generics. * * However, this implementation does avoid a heap allocation, since it is aware of the * length needed to describe the layout --- it can therefore be passed purely on the * stack. Contrast this with [`DynCat`], which has to dynamically allocate a [`Vec`]. * * For example, a row vector is created by setting the second element to the vector's * length (it has that many columns), so that would be `Cat1<2>`. Alternatively, for a * vector in the direction of the planes (the third dimension), `Cat1<3>` can be used. */ #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct Cat1; impl Cat1 { /// 0-based index, computed from the 1-based index, into the dimension array /// along which the vector will be layed out. const IDX: usize = IDX1 - 1; } impl VecLayout for Cat1 { type Layout = [usize; IDX1]; fn layout(&self, length: usize) -> Self::Layout { let mut l = [1; IDX1]; l[Cat1::::IDX] = length; l } } /** * In case one does not know ahead of time how a vector is to be layed out --- the * direction is dynamic --- `DynCat` can be used. Note that DynCat is 0-based. * * Since it is not known ahead of time how many dimensions are needed, a small vector * will be allocated. If you do know ahead of time what direction the vector is to be * layed out in, consider using [`Cat1`] or [`VecType`]; these operate fully on the stack * without allocating. */ #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct DynCat(usize); /// The default is a column vector impl Default for DynCat { fn default() -> Self { DynCat(0) } } impl VecLayout for DynCat { type Layout = Vec; fn layout(&self, length: usize) -> Self::Layout { let mut v = vec![1; self.0+1]; v[self.0] = length; v } } /** * Since a Matlab vector always has at least two dimensions, we need some extra * information to determine what shape the converted matrix in Matlab will have, * since it is not stored in a Rust vector. See also [`VecType`]. */ pub trait FromVec { /** * Convert an owned `Vec` to some Matlab class. */ fn from_vec(v: Vec, l: L) -> Self where Self: Sized { Self::from_boxed_slice(v.into_boxed_slice(), l) } fn from_slice>, L: VecLayout>(s: S, l: L) -> Self where Self: Sized { Self::from_boxed_slice(s.into(), l) } /** * Convert a boxed slice into some matlab type. */ fn from_boxed_slice(b: Box<[T]>, l: L) -> Self where Self: Sized; } /** * For a lot of types, the shape the data is not stored along with the data itself, so it * has to be provided separately. There can thus be a mismatch between the * number of elements in the data and the shape provided. In that case, the data to be * converted into a Matlab type is converted, along with the cause of the error. */ #[derive(Copy, Clone, Debug)] pub struct DataShapeMismatch { unconverted_data: S, the_reason: DataShapeMismatchReason, } impl DataShapeMismatch { pub fn new(unconverted_data: S, the_reason: DataShapeMismatchReason) -> Self { Self { unconverted_data, the_reason } } pub fn because_numel_shape(data: S) -> Self { Self::new(data, DataShapeMismatchReason::NumelShape) } pub fn because_im_re(data: S) -> Self { Self::new(data, DataShapeMismatchReason::ImRe) } /// Return the reason the conversion failed pub fn reason(&self) -> DataShapeMismatchReason { *self.reason_ref() } /// Return a reference to the reason the conversion failed. pub fn reason_ref(&self) -> &DataShapeMismatchReason { &self.the_reason } /// Retrieve the original, unconverted data pub fn original(self) -> S { self.unconverted_data } } /** * The reason a conversion failed */ #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] #[non_exhaustive] pub enum DataShapeMismatchReason { /** * The number of elements in the data does not match the number of elements * implied by the shape (_i.e._ the number of elements does not match the * product of the lengths of the dimensions in the shape argument. */ NumelShape, /** * In case of a separated complex layout, the number of elements in the imaginary * array does not match the number of elements in the real array. */ ImRe, } /** * Convenience [`Result`] type for converting types to Matlab types; returning the * unconverted original data if the conversion failed. See [`DataShapeMismatch`]. */ pub type ToMatlabResult = Result>; impl Display for DataShapeMismatch { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.the_reason { DataShapeMismatchReason::NumelShape => { write!(f, "The specified shape did not match the length of the data") }, DataShapeMismatchReason::ImRe => { write!(f, "The lengths of the data for the real and imaginary parts do not match") } } } }