Skip to main content

conpty_oxide/
status.rs

1// SPDX-FileCopyrightText: 2026 conpty-oxide contributors <https://github.com/P4suta/conpty-oxide/graphs/contributors>
2//
3// SPDX-License-Identifier: MIT OR Apache-2.0
4
5//! Child process exit status.
6//!
7//! Windows exit codes are plain `DWORD`s: there are no signals and no
8//! "terminated by" case, so [`ExitStatus`] is a thin, always-valid wrapper
9//! around a `u32` rather than the platform-abstracting enum a cross-platform
10//! API would need. The type lives outside the front-end modules because the
11//! blocking and async APIs both hand it back from `wait`.
12
13use core::fmt;
14
15/// The exit status of a child process that has terminated.
16///
17/// Obtained from either front end's `Child::wait` or `Child::try_wait`. The
18/// wrapped value is exactly what `GetExitCodeProcess` reported, read only
19/// after the process handle was confirmed signaled — so it can never be the
20/// `STILL_ACTIVE` sentinel of a still-running process.
21///
22/// # Examples
23///
24/// ```
25/// use conpty_oxide::ExitStatus;
26///
27/// # fn check(status: ExitStatus) {
28/// if status.success() {
29///     println!("clean exit");
30/// } else {
31///     println!("failed with {}", status.code());
32/// }
33/// # }
34/// ```
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
36pub struct ExitStatus(u32);
37
38impl ExitStatus {
39    /// Wraps a raw exit code obtained from `GetExitCodeProcess`.
40    #[must_use]
41    #[cfg(any(feature = "blocking", feature = "tokio", test))]
42    pub(super) const fn from_raw(code: u32) -> Self {
43        Self(code)
44    }
45
46    /// Returns the raw exit code.
47    ///
48    /// Unlike `std::process::ExitStatus::code` this is not an [`Option`]: a
49    /// Windows process always has an exit code, including when it was
50    /// terminated by `Child::kill`.
51    #[must_use]
52    pub const fn code(&self) -> u32 {
53        self.0
54    }
55
56    /// Returns whether the process exited with code `0`.
57    #[must_use]
58    pub const fn success(&self) -> bool {
59        self.0 == 0
60    }
61}
62
63/// Formats as `exit code: <code>`, matching `std::process::ExitStatus` on
64/// Windows: decimal for ordinary codes, hexadecimal when the high bit is set.
65///
66/// The hexadecimal case matters more here than it would elsewhere, because
67/// the code this crate documents most — `STATUS_CONTROL_C_EXIT`, reported by
68/// a child whose terminal went away — is in that range: it renders as
69/// `exit code: 0xc000013a`, the spelling `NTSTATUS` values are written in
70/// everywhere, rather than the unrecognizable `3221225786`.
71impl fmt::Display for ExitStatus {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        if self.0 & 0x8000_0000 != 0 {
74            write!(f, "exit code: {:#x}", self.0)
75        } else {
76            write!(f, "exit code: {}", self.0)
77        }
78    }
79}
80
81#[cfg(test)]
82#[path = "status_tests.rs"]
83mod tests;