I develop a shared library in Rust and I have a lot of functions with arguments which look roughly like this:
#[unsafe(no_mangle)]
pub unsafe extern "C" fn process_buf(buf_ptr: *mut u8, buf_len: usize) {
let buf = unsafe { core::slice::from_raw_parts_mutt(p, len) };
// process `buf` with safe Rust code
}
It would've been really nice to be able to write such functions like this:
#[unsafe(no_mangle)]
pub extern "C" fn process_buf(buf: &mut [u8]) {
// process `buf` with safe Rust code
}
But unfortunately, slices are not considered FFI safe. What prevents Rust from using the "obvious" layout for slices?
#[repr(C)]
pub struct Slice<T> {
p: NonNull<T>,
len: usize,
}
// This works
#[unsafe(no_mangle)]
pub extern "C" fn process_buf(buf: Slice<u8>) {
// process `buf` with safe Rust code
}
From the looks of it, if we are to ignore the improper_ctypes_definitions warning, LLVM generates the expected ABI code for functions which accept or return slices (i.e. pointer + length), so it looks like a strictly Rust restriction.
There is this old issue, but I couldn't find anything newer after a cursory search.
(this is also safer as i can check for null pointers in the deref)
Which is basically exactly as ergonomic as an actual slice.
~The reason llvm generates the same assembly is completely dependent on the ABI not every platform handles "structure only containing pointers" the same as separate parameters~ ( i misunderstood the windows abi here please ignore )
No ABI that uses registers for arguments handles them the same. If you have enough arguments before the struct argument that there are no more registers to fit the entire struct, the entire struct argument will be passed on the stack. With separate parameters however the first parameter(s) would be passed in registers and only once all argument registers have been exhausted will the remaining parameters be passed on the stack.
Given that CStr exists, maybe there should be a CSlice too? (Of course there's no corresponding "standard" C type on the other side unlike with CStr, but anyway.)
Both of these calling conventions will never split arguments such that half of it gets passed in a register and half of it on the stack. However slices can considered to be two entirely separate arguments from the perspective of the calling convention and thus can get partially passed in a register and partially on the stack. Checking it again, this behavior currently only seems to happen with the Rust ABI though, not with the C ABI, though there are no guarantees about this. Compiler Explorer