Description
On Windows, the standard library's function for getting a stdio handle looks like this:
pub fn get_handle(handle_id: c::DWORD) -> io::Result<c::HANDLE> {
let handle = unsafe { c::GetStdHandle(handle_id) };
if handle == c::INVALID_HANDLE_VALUE {
Err(io::Error::last_os_error())
} else if handle.is_null() {
Err(io::Error::from_raw_os_error(c::ERROR_INVALID_HANDLE as i32))
} else {
Ok(handle)
}
}
Note that it only returns a handle if one is set, otherwise it returns an error.
In contrast, the public AsRawHandle
stdio implementation ignores errors returned by GetStdHandle
and just uses the returned value, whatever that may be.
impl AsRawHandle for io::Stdin {
fn as_raw_handle(&self) -> RawHandle {
unsafe { c::GetStdHandle(c::STD_INPUT_HANDLE) as RawHandle }
}
}
// ...and similar for `Stdout` and `Stdin`.
The Safe I/O RFC introduced new types for managing handles. The AsHandle
trait is intended to be a drop in replacement for the old AsRawHandle
trait but returns a BorrowedHandle
instead of a RawHandle
.
I personally don't think AsHandle
should be implemented for stdio types. Instead a function with a signature similar to this should be implemented:
fn try_as_handle(&self) -> io::Result<BorrowedHandle<'_>>;
It would work similarly to the internal get_handle
function in that it will return an error if there is no handle to return.
Reasons for a try_as_handle()
function instead of implementing AsHandle
on stdio types:
- The error is surfaced at its origin.
- It gives users the correct mental model when thinking about Windows' stdio (especially since it differs from Unix).
- A
BorrowedHandle
should be what the name implies: a borrow of a handle. It should not be an error sentinel value (which may overlap with an actual handle value).
Reasons not to do this:
- Using a null or
INVALID_HANDLE_VALUE
will probably lead to an error in any case so it's unclear how much of an issue this is in practice. - It makes it harder to update code if
AsRawHandle
andAsHandle
aren't implemented for all the same types. - Given the above, is changing this really worth the trade-off?
See also: I/O Safety Tracking Issue (#87074) and a previous discussion on internals.