4 ms·
> The first one is mostly a problem with expressive power in the foreign function interface. Can you express what "int read(int fd, char buf[], size_t len)" mea
by dbaupp 11y ago
> The first one is mostly a problem with expressive power in the foreign function interface. Can you express what "int read(int fd, char buf[], size_t len)" means in the foreign function definition syntax? Rust's foreign function syntax isn't expressive enough to do that.[1] You can't tell Rust that "len" is the length of "buf". Being able to do that would help reduce the need for unsafe code. Most of the POSIX/Linux API can be described with relatively simple syntax that allows you to associate size info with C arrays. (I once proposed this as an extension to C. It's technically possible but politically too difficult.)[2]
There's still a need for `unsafe`, since it's possible for the relationship to be described incorrectly. It's fundamentally not something the compiler can check, and hence requires `unsafe` conceptually (if not in practice).
One can regard wrapping FFI functions in safe interfaces as specifying the relationships between parameters.
> If you cast 4 bytes to a 32-bit unsigned integer, the result is always a valid 32-bit unsigned integer. Conversions like that should be explicit, but are not memory-unsafe.
Only a very small subset of types have the property that any bit-pattern is safe, essentially only primitives. So this seems like a rather limited way to reduce unsafety (instead of just writing a short library function once).
- Animats 11y ago"Only a very small subset of types have the property that any bit-pattern is safe, essentially only primitives." Structs which contain only primitives have that property. Consider a TCP header.
- dbaupp 11y agoNo, only structs with no invariants. As soon as you have invariants, there are illegal bit patterns. Of course, these illegal bit pattern may not necessarily result in memory unsafety, but there's no way for the compiler to know this automatically^. This functionality could be implemented something like fn from_bytes<T: JustBits>(bytes: &[u8]) -> Option<&T> { if bytes.len() >= std::mem::size_of::<T>() { unsafe { Some(&*(bytes.as_ptr() as *const T)) } } else { None } } /// Values for which any bit pattern is valid. pub unsafe trait JustBits {} unsafe impl JustBits for u8 {} unsafe impl JustBits for i8 {} unsafe impl JustBits for u16 {} unsafe impl JustBits for i16 {} // ... Some custom struct that can be any bit pattern can then do: struct CustomStruct { ... } unsafe impl JustBits for CustomStruct {} Of course, there's `unsafe` there, but there has to be: it's asserting that "yes, I'm sure that anything works". ^Notably, there's been proposals for `unsafe` fields which will make expressing "invariants exist" more focused, and adjust the trade-offs here. (I'll note that a TCP header has 3 reserved bits (100, 101, 102) which, I believe, should be set to zero, making some bit patterns theoretically illegal.)