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.