Pre-RFC: I/O Safety

Sure, and data structures can easily also provide an as_ptr analog (in fact they already do, it's called as_raw_fd, they just need to document more precisely what happens). But the default should follow the usual Rust patterns and properly use lifetimes.

Traits like IoSafe describe the minimal guarantees that implementations have to provide. Particular types can always decide to provide stronger guarantees. But your proposal makes the minimal guarantees needlessly strong; it guarantees way more than clients like SocketRef need. And it does so without precedent -- the entire point of this proposal, in my eyes, is to handle FD ownership more like memory ownership; currently, it fails to do so. We have no trait on the memory side that would be equivalent to your proposed guarantee for AsRawFd+IoSafe.

What is the problem on Windows? We just do what we did for AsRawFd and also have corresponding BorrowedSomething types (and corresponding traits) for Windows handles.

People can do FFI on memory with raw pointers without a special trait. The same would work here. So yes, they still need RawFd, but nobody needs the AsRawFd trait. I am not arguing against RawFd (that would be like arguing against raw pointers; clearly we need raw pointers). I am arguing against the AsRawFd trait -- it should not be hotfixed with an unsafe IoSafe marker trait, it should be deprecated. (Note that AsBorrowedFd is a safe trait, so this proposal entirely avoids having unsafe traits, which IMO is a big advantage.)

The problem with AsRawFd is that it is like a trait with the signature

trait AsPtr {
  fn as_ptr(&self) -> *const u8
}

This trait is useless since for all we know, implementations will just always return NULL! So for a trait we need something that actually expresses, in its docs or ideally its type, the general flow of ownership. That's why we don't have a AsPtr trait. Instead we have traits like AsRef and Borrow, that do document the ownership flow.

However, having as_ptr on a particular type still makes perfect sense; that type can then document what one may do with the resulting raw pointer. Similarly, as_raw_fd on a particular type makes perfect sense.

The FFI case is easily handled by equipping BorrowedFd<'a> with an as_raw_fd method. That method can then precisely document for how long the resulting FD may be used (namely, for the lifetime 'a), and now it is up to the code to ensure that this guarantee is upheld. No IoSafe trait is needed for this, just like we don't need a MemSafe trait to be allowed to convert an &T to *const T and use it for the lifetime of the reference. This works fine when unsafe code passes raw pointers over FFI; I claim it will work just as fine when unsafe code passes raw FDs over FFI.

So yes we do need the concept of I/O safety (I never said we didn't, so I am not sure why you bring that up). What we do not need is the IoSafe trait. BorrowedFd::as_raw_fd is a specific concrete method that can be documented to provide I/Os safety guarantees without the need for an IoSafe trait.

5 Likes