I am trying to design an API for implementation in Rust such that the API is convenient to call both from C and Rust. I know what I want the C API to look like, but I don’t have enough Rust background to design the Rust mapping properly.
I want an API function that reads from and writes to caller-allocated buffers. The API can read only the beginning of a supplied input buffer or write only to the beginning of a supplied output buffer. Thus, the API function needs to be able to signal how much was read or written. Additionally, after such a partial read or write it should be convenient to the caller to pass the unread and unwritten tails of the buffers to the API again.
On the C side, I’d represent the input as two arguments:
const uint8_t** src, const uint8_t* src_end.
Upon calling into the API function
src would point to a pointer that points to first byte in the input buffer and upon return it would point to a pointer that points to the first byte that was not read.
src_end would point to the first byte that is not part of the input buffer.
Likewise, I’d use
uint8_t** dst, uint8_t* dst_end for output.
void foo(const uint8_t** src, const uint8_t* src_end, uint8_t** dst, uint8_t* dst_end)
That is, logically the API call partitions the input buffer into a read head and an unread tail and the output buffer to a written head and an unwritten tail. It is easy to call the API again with the tails as the new buffers.
std::io::Read, it seems that the concept of caller-supplied output buffer is a concept that isn’t foreign to Rust. However, returning the number of bytes read leaves it to the caller to split the slice if the caller wants to read some more data into the part of the buffer that wasn’t already filled.
Is the answer for idiomatic Rust that the function foo in Rust should return the number of bytes read and written and leave it to the caller to readjust the buffers? That is:
fn foo(src: &[u8], dst: &mut [u8]) -> (usize, usize);
Or is it appropriate to have function foo split the slices? Maybe something like:
fn foo<'a, 'b>(src: &'a [u8], dst: &'b mut [u8]) -> (&'a [u8], &'a [u8], &'b mut [u8], &'b mut [u8]);
The problem I see with this is that eventually after some number of calls to foo such that the slices passed in are views into the same underlying buffer eventually one wants to identify a slice that is the original full buffer with the last tail slice returned by foo removed. As far as I can tell, you can’t perform an operation like that with slices, since sizes don’t know that they are views into the same large buffer. Is that correct?
I’d appreciate any advice regarding how to map the C function foo above into Rust in an idiomatic way.