Skip to main content

windows_result/
error.rs

1use super::*;
2use core::num::NonZeroI32;
3
4#[expect(unused_imports)]
5use core::mem::size_of;
6
7/// A Windows error code with optional COM error information.
8///
9/// # Extended error information
10///
11/// By default, `Error` retains an `IErrorInfo` object when available. This may include a message,
12/// source, and WinRT stack trace.
13///
14/// Set `RUSTFLAGS=--cfg=windows_slim_errors` to omit `IErrorInfo`. This reduces `Error` to the
15/// four-byte [`HRESULT`] and removes its `Drop` implementation, but discards extended information.
16///
17/// This is controlled by a `--cfg` option rather than a Cargo feature because this compilation
18/// option sets a policy that applies to an entire graph of crates. Individual crates that take a
19/// dependency on the `windows-result` crate are not in a good position to decide whether they want
20/// slim errors or full errors.  Cargo features are meant to be additive, but specifying the size
21/// and contents of `Error` is not a feature so much as a whole-program policy decision.
22///
23/// # References
24///
25/// * [`IErrorInfo`][error-info]
26///
27/// [error-info]: https://learn.microsoft.com/windows/win32/api/oaidl/nn-oaidl-ierrorinfo
28#[derive(Clone)]
29pub struct Error {
30    /// The `HRESULT` error code, but represented using [`NonZeroI32`]. [`NonZeroI32`] provides
31    /// a "niche" to the Rust compiler, which is a space-saving optimization. This allows the
32    /// compiler to use more compact representation for enum variants (such as [`Result`]) that
33    /// contain instances of [`Error`].
34    code: NonZeroI32,
35
36    /// Contains details about the error, such as error text.
37    info: ErrorInfo,
38}
39
40/// We remap S_OK to this error because the S_OK representation (zero) is reserved for niche
41/// optimizations.
42const S_EMPTY_ERROR: NonZeroI32 = const_nonzero_i32(u32::from_be_bytes(*b"S_OK") as i32);
43
44/// Converts an HRESULT into a NonZeroI32. If the input is S_OK (zero), then this is converted to
45/// S_EMPTY_ERROR. This is necessary because NonZeroI32, as the name implies, cannot represent the
46/// value zero. So we remap it to a value no one should be using, during storage.
47const fn const_nonzero_i32(i: i32) -> NonZeroI32 {
48    if let Some(nz) = NonZeroI32::new(i) {
49        nz
50    } else {
51        panic!();
52    }
53}
54
55fn nonzero_hresult(hr: HRESULT) -> NonZeroI32 {
56    if let Some(nz) = NonZeroI32::new(hr.0) {
57        nz
58    } else {
59        S_EMPTY_ERROR
60    }
61}
62
63impl Error {
64    /// Creates an error object without any failure information.
65    pub const fn empty() -> Self {
66        Self {
67            code: S_EMPTY_ERROR,
68            info: ErrorInfo::empty(),
69        }
70    }
71
72    /// Creates a new error object, capturing the stack and other information about the
73    /// point of failure.
74    pub fn new<T: AsRef<str>>(code: HRESULT, message: T) -> Self {
75        let message: &str = message.as_ref();
76        if message.is_empty() {
77            Self::from_hresult(code)
78        } else {
79            ErrorInfo::originate_error(code, message);
80            code.into()
81        }
82    }
83
84    /// Creates a new error object with an error code, but without additional error information.
85    pub fn from_hresult(code: HRESULT) -> Self {
86        Self {
87            code: nonzero_hresult(code),
88            info: ErrorInfo::empty(),
89        }
90    }
91
92    /// Creates a new `Error` from the Win32 error code returned by `GetLastError()`.
93    pub fn from_thread() -> Self {
94        Self::from_hresult(HRESULT::from_thread())
95    }
96
97    /// The error code describing the error.
98    pub const fn code(&self) -> HRESULT {
99        if self.code.get() == S_EMPTY_ERROR.get() {
100            HRESULT(0)
101        } else {
102            HRESULT(self.code.get())
103        }
104    }
105
106    /// The error message describing the error.
107    pub fn message(&self) -> String {
108        if let Some(message) = self.info.message() {
109            return message;
110        }
111
112        // Otherwise fallback to a generic error code description.
113        self.code().message()
114    }
115
116    /// The error object describing the error.
117    #[cfg(windows)]
118    pub fn as_ptr(&self) -> *mut core::ffi::c_void {
119        self.info.as_ptr()
120    }
121}
122
123#[cfg(feature = "std")]
124impl std::error::Error for Error {}
125
126impl From<Error> for HRESULT {
127    fn from(error: Error) -> Self {
128        let code = error.code();
129        error.info.into_thread();
130        code
131    }
132}
133
134impl From<HRESULT> for Error {
135    fn from(code: HRESULT) -> Self {
136        Self {
137            code: nonzero_hresult(code),
138            info: ErrorInfo::from_thread(),
139        }
140    }
141}
142
143#[cfg(feature = "std")]
144impl From<Error> for std::io::Error {
145    fn from(from: Error) -> Self {
146        // If the HRESULT wraps a Win32 error (FACILITY_WIN32), unwrap it to the
147        // underlying Win32 error code so that `std::io::Error::kind` can decode
148        // it into a meaningful `ErrorKind`. For HRESULTs from other facilities
149        // (such as COM `E_*` codes or custom facilities), preserve the full
150        // HRESULT value to avoid losing information. This mirrors the behavior
151        // of .NET's `Marshal.GetExceptionForHR` and the conventions used by
152        // Rust's own Win32 error decoding.
153        if let Some(win32) = WIN32_ERROR::from_error(&from) {
154            Self::from_raw_os_error(win32.0 as i32)
155        } else {
156            Self::from_raw_os_error(from.code().0)
157        }
158    }
159}
160
161#[cfg(feature = "std")]
162impl From<std::io::Error> for Error {
163    fn from(from: std::io::Error) -> Self {
164        match from.raw_os_error() {
165            Some(status) => WIN32_ERROR(status as u32).into(),
166            None => HRESULT(E_UNEXPECTED).into(),
167        }
168    }
169}
170
171impl From<alloc::string::FromUtf16Error> for Error {
172    fn from(_: alloc::string::FromUtf16Error) -> Self {
173        WIN32_ERROR(ERROR_NO_UNICODE_TRANSLATION as u32).into()
174    }
175}
176
177impl From<alloc::string::FromUtf8Error> for Error {
178    fn from(_: alloc::string::FromUtf8Error) -> Self {
179        WIN32_ERROR(ERROR_NO_UNICODE_TRANSLATION as u32).into()
180    }
181}
182
183impl From<core::num::TryFromIntError> for Error {
184    fn from(_: core::num::TryFromIntError) -> Self {
185        WIN32_ERROR(ERROR_INVALID_DATA as u32).into()
186    }
187}
188
189impl core::fmt::Debug for Error {
190    fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
191        let mut debug = fmt.debug_struct("Error");
192        debug
193            .field("code", &self.code())
194            .field("message", &self.message())
195            .finish()
196    }
197}
198
199impl core::fmt::Display for Error {
200    fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
201        let message = self.message();
202        if message.is_empty() {
203            core::write!(fmt, "{}", self.code())
204        } else {
205            core::write!(fmt, "{} ({})", message, self.code())
206        }
207    }
208}
209
210impl core::hash::Hash for Error {
211    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
212        self.code.hash(state);
213        // We do not hash the error info.
214    }
215}
216
217// Equality tests only the HRESULT, not the error info (if any).
218impl PartialEq for Error {
219    fn eq(&self, other: &Self) -> bool {
220        self.code == other.code
221    }
222}
223
224impl Eq for Error {}
225
226impl PartialOrd for Error {
227    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
228        Some(self.cmp(other))
229    }
230}
231
232impl Ord for Error {
233    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
234        self.code.cmp(&other.code)
235    }
236}
237
238use error_info::*;
239
240#[cfg(all(windows, not(windows_slim_errors)))]
241mod error_info {
242    use super::*;
243    use crate::com::ComPtr;
244
245    /// This type stores error detail, represented by a COM `IErrorInfo` object.
246    ///
247    /// # References
248    ///
249    /// * [`IErrorInfo`][error-info]
250    ///
251    /// [error-info]: https://learn.microsoft.com/windows/win32/api/oaidl/nn-oaidl-ierrorinfo
252    #[derive(Clone, Default)]
253    pub(crate) struct ErrorInfo {
254        pub(super) ptr: Option<ComPtr>,
255    }
256
257    impl ErrorInfo {
258        pub(crate) const fn empty() -> Self {
259            Self { ptr: None }
260        }
261
262        pub(crate) fn from_thread() -> Self {
263            let mut ptr: *mut core::ffi::c_void = core::ptr::null_mut();
264            unsafe { GetErrorInfo(0, &mut ptr) };
265            Self {
266                ptr: core::ptr::NonNull::new(ptr).map(ComPtr),
267            }
268        }
269
270        pub(crate) fn into_thread(self) {
271            if let Some(ptr) = self.ptr {
272                unsafe {
273                    SetErrorInfo(0, ptr.as_raw());
274                }
275            }
276        }
277
278        pub(crate) fn originate_error(code: HRESULT, message: &str) {
279            let message: Vec<_> = message.encode_utf16().collect();
280            unsafe {
281                RoOriginateErrorW(code.0, message.len() as u32, message.as_ptr());
282            }
283        }
284
285        pub(crate) fn message(&self) -> Option<String> {
286            use crate::bstr::BasicString;
287
288            let ptr = self.ptr.as_ref()?;
289
290            let mut message = BasicString::default();
291
292            // First attempt to retrieve the restricted error information.
293            if let Some(info) = ptr.cast(&IID_IRestrictedErrorInfo) {
294                let mut fallback = BasicString::default();
295                let mut code = 0;
296
297                unsafe {
298                    com_call!(
299                        IRestrictedErrorInfo_Vtbl,
300                        info.GetErrorDetails(
301                            &mut fallback as *mut _ as _,
302                            &mut code,
303                            &mut message as *mut _ as _,
304                            &mut BasicString::default() as *mut _ as _
305                        )
306                    );
307                }
308
309                if message.is_empty() {
310                    message = fallback;
311                };
312            }
313
314            // Next attempt to retrieve the regular error information.
315            if message.is_empty() {
316                unsafe {
317                    com_call!(
318                        IErrorInfo_Vtbl,
319                        ptr.GetDescription(&mut message as *mut _ as _)
320                    );
321                }
322            }
323
324            Some(String::from_utf16_lossy(wide_trim_end(&message)))
325        }
326
327        pub(crate) fn as_ptr(&self) -> *mut core::ffi::c_void {
328            if let Some(info) = self.ptr.as_ref() {
329                info.as_raw()
330            } else {
331                core::ptr::null_mut()
332            }
333        }
334    }
335
336    unsafe impl Send for ErrorInfo {}
337    unsafe impl Sync for ErrorInfo {}
338}
339
340#[cfg(not(all(windows, not(windows_slim_errors))))]
341mod error_info {
342    use super::*;
343
344    // We use this name so that the NatVis <Type> element for ErrorInfo does *not* match this type.
345    // This prevents the NatVis description from failing to load.
346    #[derive(Clone, Default)]
347    pub(crate) struct EmptyErrorInfo;
348
349    pub(crate) use self::EmptyErrorInfo as ErrorInfo;
350
351    impl EmptyErrorInfo {
352        pub(crate) const fn empty() -> Self {
353            Self
354        }
355
356        pub(crate) fn from_thread() -> Self {
357            Self
358        }
359
360        pub(crate) fn into_thread(self) {}
361
362        pub(crate) fn originate_error(_code: HRESULT, _message: &str) {}
363
364        pub(crate) fn message(&self) -> Option<String> {
365            None
366        }
367
368        #[cfg(windows)]
369        pub(crate) fn as_ptr(&self) -> *mut core::ffi::c_void {
370            core::ptr::null_mut()
371        }
372    }
373}