The documentation of OwnedFd and BorrowedFd state that they cannot have the value -1 (with nothing said about other negative values). In addition to an explicit assert! check for -1 there is also a debug assertion in OwnedFd::Drop (debug_assert_fd_is_open) that should fail for any negative value, so it seems OwnedFd is intended to only be used for positive file descriptors.
However, this has a couple of unfortunate downsides:
libc:AT_FDCWD is a valid FD-like object with value -100 (this is used to indicate the current directory for *at(2)-style syscalls).
Programs sometimes want to explicitly disallow relative paths when using *at(2)-style syscalls, and so you often see -libc::EBADF (-9) as a "bad file descriptor" that will ensure you get an error if the path is not absolute.
When returning file descriptors with FFI, it would be really nice to be able to return a Unix-like error code (positive number for a file descriptor, negative number for an error -- -1 is traditional). This can be done by using RawFd instead but it would be nice to have OwnedFd in the FFI signature.
When taking a file descriptor through FFI, it is possible for a user to pass a negative file descriptor. For BorrowedFd this is mostly okay, but a user could provide -1 just as easily as -EBADF. While this is all unsafe and so it's "not Rust's problem", the intention of the io_safety FFI support was so that you could provide safe FD-based interfaces easily -- but normal C FD-based interfaces are expected to be able to take negative values for file descriptors because that's what Unix does.
For the first two examples, you can construct a BorrowedFd<'static> to avoid hitting the Drop impl for OwnedFd. And ultimately, I guess BorrowedFd<'static> makes more sense semantically anyway. For the third example it seems RawFd is the only option, and for the fourth example you'd need to take a RawFd and then construct your own custom enum which is incredibly unergonomic.
My question is: Is there a reason for this restriction, and would it be possible to drop it? AFAICS it was not mentioned in the io_safety RFC and negative file descriptors are trivially detected as being invalid so there seems to be little benefit to this (and while there is #[rustc_nonnull_optimization_guaranteed] in the definitions of OwnedFd and BorrowedFd, I don't see what NonNull optimisation there is here AFAICS -- is the idea that Option<OwnedFd> will use -1 for None?). In particular, I don't understand the need to disallow -1 specifically -- if the concern is the use of invalid file descriptors then all negative file descriptors should be banned (which I don't want, to be clear -- AT_FDCWD and EBADF are kind of important things to be able to handle).
If the issue is that Option<OwnedFd> turns -1 to None, can Option<OwnedFd> be made safe for use with FFI?
Semantically, the purpose of OwnedFd is to call close() on the contained file descriptor when it's dropped. You do not ever want to call close(libc::AT_FDCWD) or close(-libc::EBADF), so you do not want these values to be in an OwnedFd.
The value of -1 is made available as a "niche" here, which means that Option<OwnedFd> can use the value -1 to represent None, and other values to represent Some(value).
Constructing a BorrowedFd<'static> is fine for libc::AT_FDCWD and -libc::EBADF, because that represents a file descriptor that's available for as long as the program needs it to be available, and that's never closed - which is exactly what you want.
Unix-like error codes are outside the normal Rust philosophy; the purpose of OwnedFd and BorrowedFd is to make file descriptors behave like "normal" Rust objects, and you'd normally be expected to supply Result<OwnedFd, Err> for error handling; using RawFd if you're passing out to C is perfectly reasonable, since you're passing ownership out of Rust anyway, and don't necessarily want Rust semantics around drop to come into play for the FD.
And when you're getting a file descriptor from a C caller, you're expected to do validity checks before passing it into Rust - which allows you to turn it into Option<OwnedFd>, or Option<BorrowedFd<'_>>, or Result<OwnedFd, Error> as appropriate.
Right, but BorrowedFd is #[repr(transparent)] and is documented as being safe for use in pub extern "C" fn function signatures -- it seems to me that passing -1 is something a reasonable C caller would do regardless of any documentation to the contrary and requiring validation would mean that you couldn't use BorrowedFd in a function signature in practice (defeating the purpose of #[repr(transparent)] and making BorrowedFd FFI-safe). Same goes for OwnedFd (yeah, the implication is you would drop it but if you return it to the caller like pub extern "C" fn foo(f: OwnedFd) -> OwnedFd there isn't a drop problem).
That being said, it seems that Option<BorrowedFd<'_>> is actually FFI-safe (presumably -1 maps to None), so in practice I guess most users should use Option<BorrowedFd<'_>>? Maybe this should be documented somewhere?
Per the docs "it can be used in FFI in places where a file descriptor is passed as an argument, it is not captured or consumed, and it never has the value -1." The idea is that you use it to call C functions from Rust, rather than using it as the parameter type for Rust functoins that are called from C.
When you're writing a Rust function that accepts an FD as a parameter from C, you need a RawFd or even an integral type (to match C) that you can then convert for free to an appropriate type (BorrowedFd if you want to borrow it from C, OwnedFd if the C code should expect you to close it).