Nils Hasenbanck is the founder of Tsukisoft GmbH and a senior developer. His passion is building technically elegant, easy to maintain …
Please don't lie about safety
Is this safe?
Recently, I stumbled upon a function from an upstream library I was using, that looked something like this:
/// Data must be aligned to 32 byte.
fn load_data(data: &[u8]) {
// Some call to a FFI function (C).
}
I had a look into the FFI function and the data was indeed expected to be aligned to 32 byte, which raises the question: It this actually a safe function? The function did take a simple slice of bytes and didn’t asserted that the data was actually aligned to 32 bytes. Since the documentation of the FFI function did mention that the data had to be aligned to 32 bytes, I must assumed that “nasal demons” could pop up if that wouldn’t be the case.
I would argue that such a function is not safe to use and should not be marked as such.
So, how can we fix this?
Solution 1: This is not our problem.
The easiest fix would be to mark this function as “unsafe” and document how to safely use this function. We expect the user to handle the safety constraints:
/// # Safety
/// Data must be aligned to 32 bytes.
unsafe fn load_data(data: &[u8]) {
//...
}
Solution 2: Fail if the safety condition is not met.
Another fix would be to fail when the data is not properly aligned. We of course make sure to properly document this behavior:
/// # Panics
/// Panics if the data is not aligned to 32 bytes.
fn load_data(data: &[u8]) {
let ptr = data.as_ptr();
assert_eq!(ptr.align_offset(32), 0);
//...
}
Solution 3: Design an API that guides the user of the function.
I think the best approach here would be to design the API in a way, which guides the user of the function to the correct usage. It could be a good idea to introduce a custom type, which is aligned to 32 bytes and require the usage of it:
#[repr(C, align(32))]
pub struct Aligned32([u8; 32]);
fn load_data(data: &[Aligned32]) {
//...
}
This solution has the nice property, that the function can’t fail. Of course we could also provide
helper functionality to easily create the aligned data from an arbitrary AsRef<[u8]>
or a reader.