Hi! The title is a bit gnarly, so I'll make things simpler here.
I have a struct Jones([num_complex::Complex64; 4])
and a function that returns Vec<Jones>
. I'd like to pass this to a Python caller via pyo3 as a numpy::PyArray2<numpy::c64>
, where the second dimension is always length 4 (in other words, each element of the [Complex64; 4]
becomes a column in a 2D array). numpy::c64
can be treated as num_complex::Complex64
(if they're already not the same), which in memory is essentially just two f64
s next to each other.
To my knowledge, the easiest and cheapest way to get there safely is via ndarray
with the following:
// `jones` has type `Vec<Jones>`
let jones: Array2<numpy::c64> = ndarray::Array1::from_iter(jones.into_iter().flat_map(|j| {
[
numpy::c64::from(j[0]),
numpy::c64::from(j[1]),
numpy::c64::from(j[2]),
numpy::c64::from(j[3]),
]
}))
.into_shape((az_rad.len(), 4))
.unwrap();
jones.into_pyarray(py) // `py` is one of the function args and isn't important.
Now, I'd like to check that this is a zero-cost abstraction. I've tried using cargo asm
, but... I can't read the assembly. It is likely, though, that some panic code is emitted for the unwrap
, and I would be OK with that if the rest of the code doesn't copy.
An unsafe way to do the same thing is:
let jones: Array2<numpy::c64> = unsafe {
let ptr = jones.as_mut_ptr() as *mut numpy::c64;
let len = jones.len();
let cap = jones.capacity();
std::mem::forget(jones);
let v = Vec::from_raw_parts(ptr, len * 4, cap * 4);
ndarray::Array2::from_shape_vec((v.len() / 4, 4), v).unwrap_unchecked()
};
Ok(jones.into_pyarray(py))
This is essentially what'd I'd do in C (if I was writing in C), but I'm not actually sure how sound this is. Can I assume that v
has a length and capacity 4x the original jones
vector? And, does this code avoid a useless copy that the above safe Rust code might be doing?
Thanks in advance.