I've written a wrapper for a camera library in Rust that commands and operates a camera, and also saves an image to file using bindgen. Once I command an exposure to start (basically telling the camera to take an image), I can grab the image using a function of the form:
pub fn GetQHYCCDSingleFrame(
handle: *mut qhyccd_handle,
w: *mut u32,
...,
imgdata: &mut [u8],) -> u32 //(u32 is a retval)
In C++, this function was:
uint32_t STDCALL GetQHYCCDSingleFrame(qhyccd_handle: *handle, ..., uint8_t *imgdata)
In C++, I could pass in a buffer of the form imgdata = new unsigned char[length_buffer]
and the function would fill the buffer with image data from the camera.
In Rust, similarly, I can pass in a buffer in the form of a Vec: let mut buffer: Vec<u8> = Vec::with_capacity(length_buffer)
.
Currently, the way I have structured the code is that there is a main struct, with settings such as the width and height of image, the camera handle, and others, including the image buffer. The struct has been initialized as a mut
as:
let mut main_settings = MainSettings {
width: 9600,
...,
buffer: Vec::with_capacity(length_buffer),
}
There is a separate function I wrote that takes the main struct as a parameter and calls the GetQHYCCDSingleFrame function:
fn grab_image(main_settings: &mut MainSettings) {
let retval = unsafe { GetQHYCCDSingleFrame(main_settings.cam_handle, ..., &mut main_settings.image_buffer) };
}
Immediately after calling this function, if I check the length and capacity of main_settings.image_buffer:
println!("Elements in buffer are {}, capacity of buffer is {}.", main_settings.image_buffer.len(), main_settings.image_buffer.capacity());
I get 0 for length, and the buffer_length as the capacity. Similarly, printing any index such as main_settings.image_buffer[0]
or 1 leads to a panic exit saying len is 0
.
This would make me think that the GetQHYCCDSingleFrame
code is not working properly, however, when I save the image_buffer to file using fitsio
and hdu.write_region
(fitsio docs linked here), I use:
let ranges = [&(x_start..(x_start + roi_width)), &(y_start..(y_start+roi_height))];
hdu.write_region(&mut fits_file, &ranges, &main_settings.image_buffer).expect("Could not write to fits file");
This saves an actual image to file with the right size and is a perfectly fine image (exactly what it would look if I took using the C++ program). However, when I try to print the buffer, for some reason is empty, yet the hdu.write_region
code is able to access data somehow.
Currently, my (not good) workaround is to create another vector that reads data from the saved file and saves to a buffer, which then has the right number of elements:
main_settings.new_buffer = hdu.read_region(&mut fits_file, &ranges).expect("Couldn't read fits file");
Why can I not access the original buffer at all, and why does it report length 0, when the hdu.write_region
function can access data from somewhere? And where exactly is it accessing the data from, and how can correctly I access it as well? I am bit new to borrowing and referencing, so I believe I might be doing something wrong in borrowing/referencing the buffer, or is it something else?
Sorry for the long story, but the details would probably be important for everything here. Thanks!
Well, first of all, you need to know that Vec<u8>
and &mut [u8]
are not quite the same as C or C++'s uint8_t *
. The main difference is that Vec<u8>
and &mut [u8]
have the size of the array or slice saved within themselves, while uint8_t *
doesn't. The Rust equivalent to C/C++ pointers are raw pointers, like *mut [u8]
. Raw pointers are safe to build, but requires unsafe
to be used. However, even tho they are different types, a smart pointer as &mut [u8]
can be casted to a raw pointer without issue AFAIK.
Secondly, the capacity of a Vec
is different of its size. Indeed, to have good performances, a Vec
allocates more memory than you use, to avoid reallocating on each new element added into vector. The length however is the size of the used part. In your case, you ask the Vec
to allocate a heap space of length length_buffer
, but you don't tell them to consider any of the allocated space to be used, so the initial length is 0. Since C++ doesn't know about Vec
and only use a raw pointer, it can't change the length written inside the Vec
, that stays at 0. Thus the panicking.
To resolve it, I see multiple solutions:
Changing the Vec::with_capacity(length_buffer)
into vec![0; length_buffer]
, explicilty asking to have a length of length_buffer
from the start
Using unsafe
code to explicitly set the length of the Vec
without touching what is inside (using Vec::from_raw_parts
). This might be faster than the first solution, but I'm not sure.
Using a Box<[u8; length_buffer]>
, which is like a Vec
but without reallocation and with the length that is the capacity
If your length_buffer
is constant at compile time, using a [u8; length_buffer]
would be much more efficient as no allocation is needed, but it comes with downsides, as you probably know