extensions: Use Rust IO-safety types for fd/handle arguments/returns
Use Rust's IO-safety types like OwnedFd/BorrowedFd (and their Windows *Handle equivalent) to communicate ownership transfer (or lack thereof) to the user and provide automatic cleanup when an owned handle is transferred to the caller.
For Windows however the returned handle is only owned when it is a "handle type defined as NT handle" 1, being unclear what classifies as an NT handle (anything built/used after 1993??).
We already contemplated this before, and the change puts cfg(windows) and cfg(unix) guards in place for the corresponding OS-specific extension wrappers, as these safety types are likewise only available on the aforementioned platforms.
Unfortunately the import APIs pass file descriptors and handles via structs which have non-trivial ownership transfer into Vulkan functions: we could switch them to move semantics but there's no guarantee that these structs are only used with these functions. And it's even more complicated for external memory import: there the handle / file descriptor is passed in an Info struct chained in pNext for calls to vkAllocateMemory(), making it even more complicated (if at all possible) for us to intercept and "consume" the owned handle in a wrapper layer to prevent Rust from closing it.
Furthermore note that all import APIs only transfer ownership on Unix; on Windows the caller remains responsible for calling CloseHandle() after (not sooner) the import is no longer used in Vulkan.
This bumps MSRV to 1.63, and we could switch from std::os::unix::io to std::os::fd in MSRV 1.66.
the change puts cfg(windows) and cfg(unix) guards in place for the corresponding OS-specific extension wrappers
I like these types in principle, but I'm not sure about the cfg gates. Other ecosystem components (e.g. raw-window-handle) have moved away from cfg gates towards code that will compile on all targets, allowing downstream code to rely purely on runtime branching rather than needing both the runtime branch and cfg gates. This allows for more concise code and reduced risk of changes breaking other platforms.
Yup, that - together with being inconsistent with the import calls - is my only reason to not push this through. Coming up with a polyfill isn't desired either, and the caller will anyway have to cfg their invocations to be able to interface with these types in Rust.
Alternatively we have a (default enabled?) io-safety feature that enables the guards and changes the raw types to these safe types?
I don't think that's a reasonable use of features, since code built with it disabled will break when it is enabled.
In another project, I worked around this problem by rolling my own OwnedFd that's uninhabited but has the same public API on non-Unix. Not sure it's worth the obscurity for ash, though. Vulkan is rife with logically owning handles that are simple Copy types anyway.
Fair enough, features will have to be additive so we can only expose more API, not remove any. I.e. we would have to keep the original RawFd/i32 functions and enable a Borrowed/Owned* interface on windows/unix and when the user opts in to this. Also because these types are not available in no_std scenarios.
Indeed unlikely that rolling our own polyfill warrants the obscurity in Ash. As already said above the caller would have to cfg this code anyway unless your own OwnedFd is somehow able to provide all functionality that is available in the standard library and other crates that the user might interact with?
I assume in most cases the user reads an OwnedFd from a socket or sends it into a socket to share fences, semaphore and memory across processes.
Vulkan handles share the same problems as the struct-based APIs discussed in the PR description, requiring and enforcing move semantics would make this almost impossible afaik.
the caller would have to cfg this code anyway unless your own OwnedFd is somehow able to provide all functionality
That's the trick I illustrated in sd-listen-fds: because the code will never dynamically run outside of Unixy platforms, you can just stub everything. Still, a lot of ceremony for ash.