Skip to content

Should getting a BorrowedHandle from Stdin, Stdout or Stderr return a Result on Windows? #90964

Description

@ChrisDenton

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 and AsHandle 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-error-handlingArea: Error handlingA-ioArea: `std::io`, `std::fs`, `std::net` and `std::path`C-discussionCategory: Discussion or questions that doesn't represent real issues.O-windowsOperating system: WindowsT-libsRelevant to the library team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions