From 415e3d94fca956ccd2e2c27b644cdd9c56e6c21d Mon Sep 17 00:00:00 2001 From: Max Heller Date: Sat, 14 Mar 2026 21:09:28 -0400 Subject: [PATCH 1/2] move std::io::Error to core Co-authored-by: Jacob Lifshay --- library/alloc/src/io/error.rs | 242 ++++ library/alloc/src/io/mod.rs | 8 + library/alloc/src/lib.rs | 5 + library/alloctests/tests/io.rs | 1 + library/alloctests/tests/io/tests.rs | 25 + library/core/src/io/error.rs | 1082 +++++++++++++++++ library/core/src/io/error/repr_bitpacked.rs | 422 +++++++ library/core/src/io/error/repr_unpacked.rs | 53 + library/core/src/io/mod.rs | 26 + library/core/src/lib.rs | 4 +- library/coretests/tests/io/error.rs | 124 ++ library/coretests/tests/io/mod.rs | 1 + library/coretests/tests/lib.rs | 5 + library/std/Cargo.toml | 1 + library/std/src/io/error.rs | 479 ++------ library/std/src/io/error/repr_bitpacked.rs | 64 +- library/std/src/io/error/tests.rs | 173 +-- library/std/src/io/mod.rs | 22 +- library/std/src/lib.rs | 9 + library/std/src/sys/io/error/generic.rs | 1 + library/std/src/sys/io/error/hermit.rs | 1 + library/std/src/sys/io/error/mod.rs | 5 - library/std/src/sys/io/error/motor.rs | 1 + library/std/src/sys/io/error/sgx.rs | 1 + library/std/src/sys/io/error/solid.rs | 1 + library/std/src/sys/io/error/teeos.rs | 1 + library/std/src/sys/io/error/unix.rs | 1 + library/std/src/sys/io/error/wasi.rs | 1 + library/std/src/sys/io/error/windows.rs | 1 + library/std/src/sys/io/error/xous.rs | 1 + library/std/src/sys/io/mod.rs | 4 +- .../src/sys/net/connection/socket/solid.rs | 1 + library/std/src/sys/pal/itron/error.rs | 1 + src/tools/tidy/src/pal.rs | 2 + .../binary-op-not-allowed-issue-125631.stderr | 6 +- 35 files changed, 2172 insertions(+), 603 deletions(-) create mode 100644 library/alloc/src/io/error.rs create mode 100644 library/alloc/src/io/mod.rs create mode 100644 library/alloctests/tests/io.rs create mode 100644 library/alloctests/tests/io/tests.rs create mode 100644 library/core/src/io/error.rs create mode 100644 library/core/src/io/error/repr_bitpacked.rs create mode 100644 library/core/src/io/error/repr_unpacked.rs create mode 100644 library/coretests/tests/io/error.rs diff --git a/library/alloc/src/io/error.rs b/library/alloc/src/io/error.rs new file mode 100644 index 0000000000000..733e89f6a3649 --- /dev/null +++ b/library/alloc/src/io/error.rs @@ -0,0 +1,242 @@ +use core::alloc::Allocator; +use core::io::error_internals::{AllocVTable, Custom, ErrorBox, ErrorString}; +use core::io::{Error, ErrorKind, const_error}; +use core::{error, ptr, result}; + +use crate::alloc::Global; +use crate::boxed::Box; +use crate::string::String; + +unsafe fn set_alloc_vtable() { + static ALLOC_VTABLE: AllocVTable = + AllocVTable { deallocate: |ptr, layout| unsafe { Global.deallocate(ptr, layout) } }; + unsafe { + ALLOC_VTABLE.install(); + } +} + +fn into_error_box(value: Box) -> ErrorBox { + unsafe { + set_alloc_vtable(); + ErrorBox::from_raw(Box::into_raw(value)) + } +} + +fn into_box(v: ErrorBox) -> Box { + unsafe { Box::from_raw(v.into_raw()) } +} + +impl From for ErrorString { + fn from(value: String) -> Self { + unsafe { + set_alloc_vtable(); + let (buf, length, capacity) = value.into_raw_parts(); + ErrorString::from_raw_parts( + ErrorBox::from_raw(ptr::slice_from_raw_parts_mut(buf.cast(), capacity)).into(), + length, + ) + } + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl From for Error { + /// Converts a [`crate::ffi::NulError`] into a [`Error`]. + fn from(_: crate::ffi::NulError) -> Error { + const_error!(ErrorKind::InvalidInput, "data provided contains a nul byte") + } +} + +#[stable(feature = "io_error_from_try_reserve", since = "1.78.0")] +impl From for Error { + /// Converts `TryReserveError` to an error with [`ErrorKind::OutOfMemory`]. + /// + /// `TryReserveError` won't be available as the error `source()`, + /// but this may change in the future. + fn from(_: crate::collections::TryReserveError) -> Error { + // ErrorData::Custom allocates, which isn't great for handling OOM errors. + ErrorKind::OutOfMemory.into() + } +} + +impl Error { + /// Creates a new I/O error from a known kind of error as well as an + /// arbitrary error payload. + /// + /// This function is used to generically create I/O errors which do not + /// originate from the OS itself. The `error` argument is an arbitrary + /// payload which will be contained in this [`Error`]. + /// + /// Note that this function allocates memory on the heap. + /// If no extra payload is required, use the `From` conversion from + /// `ErrorKind`. + /// + /// # Examples + /// + /// ``` + /// use std::io::{Error, ErrorKind}; + /// + /// // errors can be created from strings + /// let custom_error = Error::new(ErrorKind::Other, "oh no!"); + /// + /// // errors can also be created from other errors + /// let custom_error2 = Error::new(ErrorKind::Interrupted, custom_error); + /// + /// // creating an error without payload (and without memory allocation) + /// let eof_error = Error::from(ErrorKind::UnexpectedEof); + /// ``` + #[stable(feature = "rust1", since = "1.0.0")] + #[cfg_attr(not(test), rustc_diagnostic_item = "io_error_new")] + #[rustc_allow_incoherent_impl] + #[inline(never)] + pub fn new(kind: ErrorKind, error: E) -> Error + where + E: Into>, + { + Self::_new(kind, error.into()) + } + + /// Creates a new I/O error from an arbitrary error payload. + /// + /// This function is used to generically create I/O errors which do not + /// originate from the OS itself. It is a shortcut for [`Error::new`] + /// with [`ErrorKind::Other`]. + /// + /// # Examples + /// + /// ``` + /// use std::io::Error; + /// + /// // errors can be created from strings + /// let custom_error = Error::other("oh no!"); + /// + /// // errors can also be created from other errors + /// let custom_error2 = Error::other(custom_error); + /// ``` + #[stable(feature = "io_error_other", since = "1.74.0")] + #[rustc_allow_incoherent_impl] + pub fn other(error: E) -> Error + where + E: Into>, + { + Self::_new(ErrorKind::Other, error.into()) + } + + #[rustc_allow_incoherent_impl] + fn _new(kind: ErrorKind, error: Box) -> Error { + Error::_new_custom(into_error_box(Box::new(Custom { kind, error: into_error_box(error) }))) + } + + /// Consumes the `Error`, returning its inner error (if any). + /// + /// If this [`Error`] was constructed via [`new`] then this function will + /// return [`Some`], otherwise it will return [`None`]. + /// + /// [`new`]: https://doc.rust-lang.org/std/io/struct.Error.html#method.new + /// + /// # Examples + /// + /// ``` + /// use std::io::{Error, ErrorKind}; + /// + /// fn print_error(err: Error) { + /// if let Some(inner_err) = err.into_inner() { + /// println!("Inner error: {inner_err}"); + /// } else { + /// println!("No inner error"); + /// } + /// } + /// + /// fn main() { + /// // Will print "No inner error". + /// print_error(Error::last_os_error()); + /// // Will print "Inner error: ...". + /// print_error(Error::new(ErrorKind::Other, "oh no!")); + /// } + /// ``` + #[stable(feature = "io_error_inner", since = "1.3.0")] + #[must_use = "`self` will be dropped if the result is not used"] + #[inline] + #[rustc_allow_incoherent_impl] + pub fn into_inner(self) -> Option> { + self.into_inner_impl().map(into_box) + } + + /// Attempts to downcast the custom boxed error to `E`. + /// + /// If this [`Error`] contains a custom boxed error, + /// then it would attempt downcasting on the boxed error, + /// otherwise it will return [`Err`]. + /// + /// If the custom boxed error has the same type as `E`, it will return [`Ok`], + /// otherwise it will also return [`Err`]. + /// + /// This method is meant to be a convenience routine for calling + /// `Box::downcast` on the custom boxed error, returned by + /// [`Error::into_inner`]. + /// + /// + /// # Examples + /// + /// ``` + /// use std::fmt; + /// use std::io; + /// use std::error::Error; + /// + /// #[derive(Debug)] + /// enum E { + /// Io(io::Error), + /// SomeOtherVariant, + /// } + /// + /// impl fmt::Display for E { + /// // ... + /// # fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + /// # todo!() + /// # } + /// } + /// impl Error for E {} + /// + /// impl From for E { + /// fn from(err: io::Error) -> E { + /// err.downcast::() + /// .unwrap_or_else(E::Io) + /// } + /// } + /// + /// impl From for io::Error { + /// fn from(err: E) -> io::Error { + /// match err { + /// E::Io(io_error) => io_error, + /// e => io::Error::new(io::ErrorKind::Other, e), + /// } + /// } + /// } + /// + /// # fn main() { + /// let e = E::SomeOtherVariant; + /// // Convert it to an io::Error + /// let io_error = io::Error::from(e); + /// // Cast it back to the original variant + /// let e = E::from(io_error); + /// assert!(matches!(e, E::SomeOtherVariant)); + /// + /// let io_error = io::Error::from(io::ErrorKind::AlreadyExists); + /// // Convert it to E + /// let e = E::from(io_error); + /// // Cast it back to the original variant + /// let io_error = io::Error::from(e); + /// assert_eq!(io_error.kind(), io::ErrorKind::AlreadyExists); + /// assert!(io_error.get_ref().is_none()); + /// assert!(io_error.raw_os_error().is_none()); + /// # } + /// ``` + #[stable(feature = "io_error_downcast", since = "1.79.0")] + #[rustc_allow_incoherent_impl] + pub fn downcast(self) -> result::Result, Self> + where + E: error::Error + Send + Sync + 'static, + { + self.downcast_impl::().map(|p| unsafe { Box::from_raw(p) }) + } +} diff --git a/library/alloc/src/io/mod.rs b/library/alloc/src/io/mod.rs new file mode 100644 index 0000000000000..d924e8a5d4613 --- /dev/null +++ b/library/alloc/src/io/mod.rs @@ -0,0 +1,8 @@ +//! Traits, helpers, and type definitions for core I/O functionality. + +#![unstable(feature = "alloc_io", issue = "none")] + +pub use core::io::*; + +#[cfg(target_has_atomic_load_store = "ptr")] +mod error; diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs index 7ac9cdc3833d3..584a5abaf1683 100644 --- a/library/alloc/src/lib.rs +++ b/library/alloc/src/lib.rs @@ -109,6 +109,8 @@ #![feature(const_try)] #![feature(copied_into_inner)] #![feature(core_intrinsics)] +#![feature(core_io)] +#![feature(core_io_error_internals)] #![feature(deprecated_suggestion)] #![feature(deref_pure_trait)] #![feature(dispatch_from_dyn)] @@ -125,6 +127,7 @@ #![feature(generic_atomic)] #![feature(hasher_prefixfree_extras)] #![feature(inplace_iteration)] +#![feature(io_const_error)] #![feature(iter_advance_by)] #![feature(iter_next_chunk)] #![feature(layout_for_ptr)] @@ -169,6 +172,7 @@ #![feature(allocator_internals)] #![feature(allow_internal_unstable)] #![feature(cfg_sanitize)] +#![feature(cfg_target_has_atomic)] #![feature(const_precise_live_drops)] #![feature(const_trait_impl)] #![feature(coroutine_trait)] @@ -224,6 +228,7 @@ pub mod collections; pub mod ffi; pub mod fmt; pub mod intrinsics; +pub mod io; #[cfg(not(no_rc))] pub mod rc; pub mod slice; diff --git a/library/alloctests/tests/io.rs b/library/alloctests/tests/io.rs new file mode 100644 index 0000000000000..d4a42f71a19ae --- /dev/null +++ b/library/alloctests/tests/io.rs @@ -0,0 +1 @@ +mod error; diff --git a/library/alloctests/tests/io/tests.rs b/library/alloctests/tests/io/tests.rs new file mode 100644 index 0000000000000..95a5774026298 --- /dev/null +++ b/library/alloctests/tests/io/tests.rs @@ -0,0 +1,25 @@ +use core::io::{Error, ErrorKind}; +use core::{error, fmt}; + +#[test] +fn test_downcasting() { + #[derive(Debug)] + struct TestError; + + impl fmt::Display for TestError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("asdf") + } + } + + impl error::Error for TestError {} + + // we have to call all of these UFCS style right now since method + // resolution won't implicitly drop the Send+Sync bounds + let mut err = Error::new(ErrorKind::Other, TestError); + assert!(err.get_ref().unwrap().is::()); + assert_eq!("asdf", err.get_ref().unwrap().to_string()); + assert!(err.get_mut().unwrap().is::()); + let extracted = err.into_inner().unwrap(); + extracted.downcast::().unwrap(); +} diff --git a/library/core/src/io/error.rs b/library/core/src/io/error.rs new file mode 100644 index 0000000000000..12a6198bc09e3 --- /dev/null +++ b/library/core/src/io/error.rs @@ -0,0 +1,1082 @@ +//! implementation detail of [`Error`] + +#[cfg(all(target_pointer_width = "64", not(target_os = "uefi")))] +mod repr_bitpacked; +#[cfg(all(target_pointer_width = "64", not(target_os = "uefi")))] +use repr_bitpacked::Repr; +#[cfg(target_pointer_width = "64")] +#[unstable(feature = "core_io_error_internals", issue = "none")] +pub use repr_bitpacked::kind_from_prim; + +#[cfg(any(not(target_pointer_width = "64"), target_os = "uefi"))] +mod repr_unpacked; +#[cfg(any(not(target_pointer_width = "64"), target_os = "uefi"))] +use repr_unpacked::Repr; + +use crate::alloc::Layout; +use crate::mem::ManuallyDrop; +use crate::ops::{Deref, DerefMut}; +use crate::ptr::{self, NonNull, Unique}; +use crate::sync::atomic::{AtomicPtr, Ordering}; +use crate::{error, fmt, mem, result, str}; + +/// implementation detail of [`Error`] +#[unstable(feature = "core_io_error_internals", issue = "none")] +pub struct ErrorString { + bytes: ErrorBox<[mem::MaybeUninit]>, + length: usize, +} + +impl ErrorString { + /// implementation detail of [`Error`] + /// + /// Safety: `(*bytes)[..length]` must be initialized and valid UTF-8 + pub unsafe fn from_raw_parts(bytes: ErrorBox<[mem::MaybeUninit]>, length: usize) -> Self { + Self { bytes, length } + } +} + +impl Deref for ErrorString { + type Target = str; + fn deref(&self) -> &Self::Target { + // SAFETY: guaranteed safe by ErrorString's safety invariant + unsafe { str::from_utf8_unchecked(&(*self.bytes)[..self.length].assume_init_ref()) } + } +} + +impl fmt::Debug for ErrorString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (**self).fmt(f) + } +} + +impl fmt::Display for ErrorString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (**self).fmt(f) + } +} + +/// implementation detail of [`Error`] +/// +/// Safety: `self.0` must point to allocated memory and +/// [`AllocVTable::install`] must have been run +#[unstable(feature = "core_io_error_internals", issue = "none")] +pub struct ErrorBox(Unique); + +impl fmt::Debug for ErrorBox { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + T::fmt(self, f) + } +} + +impl fmt::Display for ErrorBox { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + T::fmt(self, f) + } +} + +impl Deref for ErrorBox { + type Target = T; + + fn deref(&self) -> &Self::Target { + // SAFETY: guaranteed safe by ErrorBox's safety invariant + unsafe { self.0.as_ref() } + } +} + +impl DerefMut for ErrorBox { + fn deref_mut(&mut self) -> &mut Self::Target { + // SAFETY: guaranteed safe by ErrorBox's safety invariant + unsafe { self.0.as_mut() } + } +} + +impl ErrorBox { + /// implementation detail of [`Error`] + /// + /// Safety: `v` must point to allocated memory and + /// [`AllocVTable::install`] must have been run + pub unsafe fn from_raw(v: *mut T) -> Self { + // SAFETY: guaranteed by caller + unsafe { Self(Unique::new_unchecked(v)) } + } + /// implementation detail of [`Error`] + pub fn into_raw(self) -> *mut T { + ManuallyDrop::new(self).0.as_ptr() + } + /// implementation detail of [`Error`] + pub fn into_inner(self) -> T + where + T: Sized, + { + let mut b = ManuallyDrop::new(self); + // SAFETY: guaranteed safe by ErrorBox's safety invariant + unsafe { + let _mem = ErrorBoxMem::new(&mut b); + ptr::read(b.0.as_ptr()) + } + } +} + +struct ErrorBoxMem { + layout: Layout, + ptr: NonNull, +} + +impl ErrorBoxMem { + /// Safety: `b` must not have had its internals dropped. `b` must not be dropped. + unsafe fn new(b: &mut ErrorBox) -> Self { + Self { + layout: Layout::for_value::(&**b), + // SAFETY: guaranteed safe by caller + ptr: unsafe { NonNull::new_unchecked(b.0.as_ptr().cast()) }, + } + } +} + +impl Drop for ErrorBoxMem { + fn drop(&mut self) { + // SAFETY: guaranteed safe by `ErrorBoxMem::new` + unsafe { (AllocVTable::get().deallocate)(self.ptr, self.layout) } + } +} + +impl Drop for ErrorBox { + fn drop(&mut self) { + // SAFETY: guaranteed safe by ErrorBox's safety invariant + unsafe { + let _mem = ErrorBoxMem::new(self); + ptr::drop_in_place(self.0.as_ptr()); + } + } +} + +/// implementation detail of [`Error`] +#[derive(Debug)] +#[unstable(feature = "core_io_error_internals", issue = "none")] +pub struct AllocVTable { + /// implementation detail of [`Error`] + /// + /// Safety: same as [`Allocator::deallocate`] + /// + /// [`Allocator::deallocate`]: core::alloc::Allocator::deallocate + pub deallocate: unsafe fn(ptr: NonNull, layout: Layout), +} + +static ALLOC_VTABLE: AtomicPtr = AtomicPtr::new(ptr::null_mut()); + +impl AllocVTable { + /// implementation detail of [`Error`] + /// + /// Safety: `self` must be in a `static` variable that has never been + /// written. All members must be valid. + pub unsafe fn install(&'static self) { + // see `get` for why `Relaxed` is sufficient. + if ALLOC_VTABLE.load(Ordering::Relaxed).is_null() { + ALLOC_VTABLE.store(<*const _>::from(self).cast_mut(), Ordering::Relaxed); + } + } + /// implementation detail of [`Error`] + /// + /// Safety: `install` must have been called + pub unsafe fn get() -> &'static Self { + // SAFETY: `install` has been called and it set `ALLOC_VTABLE` to point + // to a `static` variable that has never been written, so no writes can + // possibly need to be communicated through other memory so `Relaxed` + // here and in `install` are sufficient. + unsafe { &*ALLOC_VTABLE.load(Ordering::Relaxed) } + } +} + +/// implementation detail of [`Error`] +#[derive(Debug)] +#[unstable(feature = "core_io_error_internals", issue = "none")] +pub struct StdVTable { + /// implementation detail of [`Error`] + pub decode_error_kind: fn(RawOsError) -> ErrorKind, + /// implementation detail of [`Error`] + pub error_string: fn(RawOsError) -> ErrorString, +} + +static STD_VTABLE: AtomicPtr = AtomicPtr::new(ptr::null_mut()); + +impl StdVTable { + /// implementation detail of [`Error`] + /// + /// Safety: `self` must be in a `static` variable that has never been + /// written. All members must be valid. + pub unsafe fn install(&'static self) { + // see `get` for why `Relaxed` is sufficient. + if STD_VTABLE.load(Ordering::Relaxed).is_null() { + STD_VTABLE.store(<*const _>::from(self).cast_mut(), Ordering::Relaxed); + } + } + /// implementation detail of [`Error`] + /// + /// Safety: `install` must have been called + pub unsafe fn get() -> &'static Self { + // SAFETY: `install` has been called and it set `STD_VTABLE` to point + // to a `static` variable that has never been written, so no writes can + // possibly need to be communicated through other memory so `Relaxed` + // here and in `install` are sufficient. + unsafe { &*STD_VTABLE.load(Ordering::Relaxed) } + } +} + +/// A specialized [`Result`] type for I/O operations. +/// +/// This type is broadly used across [`std::io`] for any operation which may +/// produce an error. +/// +/// This typedef is generally used to avoid writing out [`io::Error`] directly and +/// is otherwise a direct mapping to [`Result`]. +/// +/// While usual Rust style is to import types directly, aliases of [`Result`] +/// often are not, to make it easier to distinguish between them. [`Result`] is +/// generally assumed to be [`std::result::Result`][`Result`], and so users of this alias +/// will generally use `io::Result` instead of shadowing the [prelude]'s import +/// of [`std::result::Result`][`Result`]. +/// +/// [`std::io`]: https://doc.rust-lang.org/std/io/index.html +/// [`io::Error`]: Error +/// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html +/// [prelude]: https://doc.rust-lang.org/std/prelude/index.html +/// +/// # Examples +/// +/// A convenience function that bubbles an `io::Result` to its caller: +/// +/// ``` +/// use std::io; +/// +/// fn get_string() -> io::Result { +/// let mut buffer = String::new(); +/// +/// io::stdin().read_line(&mut buffer)?; +/// +/// Ok(buffer) +/// } +/// ``` +#[stable(feature = "rust1", since = "1.0.0")] +#[doc(search_unbox)] +pub type Result = result::Result; + +/// The error type for I/O operations of the [`Read`], [`Write`], [`Seek`], and +/// associated traits. +/// +/// Errors mostly originate from the underlying OS, but custom instances of +/// `Error` can be created with crafted error messages and a particular value of +/// [`ErrorKind`]. +/// +/// [`Read`]: https://doc.rust-lang.org/std/io/trait.Read.html +/// [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html +/// [`Seek`]: https://doc.rust-lang.org/std/io/trait.Seek.html +// Safety: [`StdVTable::install`] must have been run before any `Error` +// instances containing OS errors are created. +// [`AllocVTable::install`] must have been run before any instances containing +// `AllocBox` are created. +#[stable(feature = "rust1", since = "1.0.0")] +#[rustc_has_incoherent_inherent_impls] +pub struct Error { + repr: Repr, +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl fmt::Debug for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.repr, f) + } +} + +// Only derive debug in tests, to make sure it +// doesn't accidentally get printed. +#[cfg_attr(test, derive(Debug))] +enum ErrorData { + Os(RawOsError), + Simple(ErrorKind), + SimpleMessage(&'static SimpleMessage), + Custom(C), +} + +/// The type of raw OS error codes returned by [`Error::raw_os_error`]. +/// +/// This is an [`i32`] on all currently supported platforms, but platforms +/// added in the future (such as UEFI) may use a different primitive type like +/// [`usize`]. Use `as`or [`into`] conversions where applicable to ensure maximum +/// portability. +/// +/// [`into`]: Into::into +#[unstable(feature = "raw_os_error_ty", issue = "107792")] +pub type RawOsError = cfg_select! { + target_os = "uefi" => usize, + _ => i32, +}; + +// `#[repr(align(4))]` is probably redundant, it should have that value or +// higher already. We include it just because repr_bitpacked.rs's encoding +// requires an alignment >= 4 (note that `#[repr(align)]` will not reduce the +// alignment required by the struct, only increase it). +// +// If we add more variants to ErrorData, this can be increased to 8, but it +// should probably be behind `#[cfg_attr(target_pointer_width = "64", ...)]` or +// whatever cfg we're using to enable the `repr_bitpacked` code, since only the +// that version needs the alignment, and 8 is higher than the alignment we'll +// have on 32 bit platforms. +// +// (For the sake of being explicit: the alignment requirement here only matters +// if `error/repr_bitpacked.rs` is in use — for the unpacked repr it doesn't +// matter at all) +#[doc(hidden)] +#[unstable(feature = "io_const_error_internals", issue = "none")] +#[repr(align(4))] +#[derive(Debug)] +pub struct SimpleMessage { + pub kind: ErrorKind, + pub message: &'static str, +} + +/// Creates a new I/O error from a known kind of error and a string literal. +/// +/// Contrary to [`Error::new`], this macro does not allocate and can be used in +/// `const` contexts. +/// +/// # Example +/// ``` +/// #![feature(io_const_error)] +/// use std::io::{const_error, Error, ErrorKind}; +/// +/// const FAIL: Error = const_error!(ErrorKind::Unsupported, "tried something that never works"); +/// +/// fn not_here() -> Result<(), Error> { +/// Err(FAIL) +/// } +/// ``` +/// +/// [`Error::new`]: https://doc.rust-lang.org/std/io/struct.Error.html#method.new +#[rustc_macro_transparency = "semiopaque"] +#[unstable(feature = "io_const_error", issue = "133448")] +#[allow_internal_unstable( + hint_must_use, + io_const_error_internals, + core_io, + core_io_error_internals +)] +pub macro const_error($kind:expr, $message:expr $(,)?) { + $crate::hint::must_use($crate::io::Error::from_static_message( + const { &$crate::io::SimpleMessage { kind: $kind, message: $message } }, + )) +} + +// As with `SimpleMessage`: `#[repr(align(4))]` here is just because +// repr_bitpacked's encoding requires it. In practice it almost certainly be +// already be this high or higher. +#[derive(Debug)] +#[repr(align(4))] +#[unstable(feature = "core_io_error_internals", issue = "none")] +/// implementation detail of [`Error`] +pub struct Custom { + /// implementation detail of [`Error`] + pub kind: ErrorKind, + /// implementation detail of [`Error`] + pub error: ErrorBox, +} + +/// A list specifying general categories of I/O error. +/// +/// This list is intended to grow over time and it is not recommended to +/// exhaustively match against it. +/// +/// It is used with the [`io::Error`] type. +/// +/// [`io::Error`]: Error +/// +/// # Handling errors and matching on `ErrorKind` +/// +/// In application code, use `match` for the `ErrorKind` values you are +/// expecting; use `_` to match "all other errors". +/// +/// In comprehensive and thorough tests that want to verify that a test doesn't +/// return any known incorrect error kind, you may want to cut-and-paste the +/// current full list of errors from here into your test code, and then match +/// `_` as the correct case. This seems counterintuitive, but it will make your +/// tests more robust. In particular, if you want to verify that your code does +/// produce an unrecognized error kind, the robust solution is to check for all +/// the recognized error kinds and fail in those cases. +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[stable(feature = "rust1", since = "1.0.0")] +#[cfg_attr(not(test), rustc_diagnostic_item = "io_errorkind")] +#[allow(deprecated)] +#[non_exhaustive] +pub enum ErrorKind { + /// An entity was not found, often a file. + #[stable(feature = "rust1", since = "1.0.0")] + NotFound, + /// The operation lacked the necessary privileges to complete. + #[stable(feature = "rust1", since = "1.0.0")] + PermissionDenied, + /// The connection was refused by the remote server. + #[stable(feature = "rust1", since = "1.0.0")] + ConnectionRefused, + /// The connection was reset by the remote server. + #[stable(feature = "rust1", since = "1.0.0")] + ConnectionReset, + /// The remote host is not reachable. + #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] + HostUnreachable, + /// The network containing the remote host is not reachable. + #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] + NetworkUnreachable, + /// The connection was aborted (terminated) by the remote server. + #[stable(feature = "rust1", since = "1.0.0")] + ConnectionAborted, + /// The network operation failed because it was not connected yet. + #[stable(feature = "rust1", since = "1.0.0")] + NotConnected, + /// A socket address could not be bound because the address is already in + /// use elsewhere. + #[stable(feature = "rust1", since = "1.0.0")] + AddrInUse, + /// A nonexistent interface was requested or the requested address was not + /// local. + #[stable(feature = "rust1", since = "1.0.0")] + AddrNotAvailable, + /// The system's networking is down. + #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] + NetworkDown, + /// The operation failed because a pipe was closed. + #[stable(feature = "rust1", since = "1.0.0")] + BrokenPipe, + /// An entity already exists, often a file. + #[stable(feature = "rust1", since = "1.0.0")] + AlreadyExists, + /// The operation needs to block to complete, but the blocking operation was + /// requested to not occur. + #[stable(feature = "rust1", since = "1.0.0")] + WouldBlock, + /// A filesystem object is, unexpectedly, not a directory. + /// + /// For example, a filesystem path was specified where one of the intermediate directory + /// components was, in fact, a plain file. + #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] + NotADirectory, + /// The filesystem object is, unexpectedly, a directory. + /// + /// A directory was specified when a non-directory was expected. + #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] + IsADirectory, + /// A non-empty directory was specified where an empty directory was expected. + #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] + DirectoryNotEmpty, + /// The filesystem or storage medium is read-only, but a write operation was attempted. + #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] + ReadOnlyFilesystem, + /// Loop in the filesystem or IO subsystem; often, too many levels of symbolic links. + /// + /// There was a loop (or excessively long chain) resolving a filesystem object + /// or file IO object. + /// + /// On Unix this is usually the result of a symbolic link loop; or, of exceeding the + /// system-specific limit on the depth of symlink traversal. + #[unstable(feature = "io_error_more", issue = "86442")] + FilesystemLoop, + /// Stale network file handle. + /// + /// With some network filesystems, notably NFS, an open file (or directory) can be invalidated + /// by problems with the network or server. + #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] + StaleNetworkFileHandle, + /// A parameter was incorrect. + #[stable(feature = "rust1", since = "1.0.0")] + InvalidInput, + /// Data not valid for the operation were encountered. + /// + /// Unlike [`InvalidInput`], this typically means that the operation + /// parameters were valid, however the error was caused by malformed + /// input data. + /// + /// For example, a function that reads a file into a string will error with + /// `InvalidData` if the file's contents are not valid UTF-8. + /// + /// [`InvalidInput`]: ErrorKind::InvalidInput + #[stable(feature = "io_invalid_data", since = "1.2.0")] + InvalidData, + /// The I/O operation's timeout expired, causing it to be canceled. + #[stable(feature = "rust1", since = "1.0.0")] + TimedOut, + /// An error returned when an operation could not be completed because a + /// call to [`write`] returned [`Ok(0)`]. + /// + /// This typically means that an operation could only succeed if it wrote a + /// particular number of bytes but only a smaller number of bytes could be + /// written. + /// + /// [`write`]: https://doc.rust-lang.org/std/io/trait.Write.html#tymethod.write + /// [`Ok(0)`]: Ok + #[stable(feature = "rust1", since = "1.0.0")] + WriteZero, + /// The underlying storage (typically, a filesystem) is full. + /// + /// This does not include out of quota errors. + #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] + StorageFull, + /// Seek on unseekable file. + /// + /// Seeking was attempted on an open file handle which is not suitable for seeking - for + /// example, on Unix, a named pipe opened with `File::open`. + #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] + NotSeekable, + /// Filesystem quota or some other kind of quota was exceeded. + #[stable(feature = "io_error_quota_exceeded", since = "1.85.0")] + QuotaExceeded, + /// File larger than allowed or supported. + /// + /// This might arise from a hard limit of the underlying filesystem or file access API, or from + /// an administratively imposed resource limitation. Simple disk full, and out of quota, have + /// their own errors. + #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] + FileTooLarge, + /// Resource is busy. + #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] + ResourceBusy, + /// Executable file is busy. + /// + /// An attempt was made to write to a file which is also in use as a running program. (Not all + /// operating systems detect this situation.) + #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] + ExecutableFileBusy, + /// Deadlock (avoided). + /// + /// A file locking operation would result in deadlock. This situation is typically detected, if + /// at all, on a best-effort basis. + #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] + Deadlock, + /// Cross-device or cross-filesystem (hard) link or rename. + #[stable(feature = "io_error_crosses_devices", since = "1.85.0")] + CrossesDevices, + /// Too many (hard) links to the same filesystem object. + /// + /// The filesystem does not support making so many hardlinks to the same file. + #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] + TooManyLinks, + /// A filename was invalid. + /// + /// This error can also occur if a length limit for a name was exceeded. + #[stable(feature = "io_error_invalid_filename", since = "1.87.0")] + InvalidFilename, + /// Program argument list too long. + /// + /// When trying to run an external program, a system or process limit on the size of the + /// arguments would have been exceeded. + #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] + ArgumentListTooLong, + /// This operation was interrupted. + /// + /// Interrupted operations can typically be retried. + #[stable(feature = "rust1", since = "1.0.0")] + Interrupted, + + /// This operation is unsupported on this platform. + /// + /// This means that the operation can never succeed. + #[stable(feature = "unsupported_error", since = "1.53.0")] + Unsupported, + + // ErrorKinds which are primarily categorisations for OS error + // codes should be added above. + // + /// An error returned when an operation could not be completed because an + /// "end of file" was reached prematurely. + /// + /// This typically means that an operation could only succeed if it read a + /// particular number of bytes but only a smaller number of bytes could be + /// read. + #[stable(feature = "read_exact", since = "1.6.0")] + UnexpectedEof, + + /// An operation could not be completed, because it failed + /// to allocate enough memory. + #[stable(feature = "out_of_memory_error", since = "1.54.0")] + OutOfMemory, + + /// The operation was partially successful and needs to be checked + /// later on due to not blocking. + #[unstable(feature = "io_error_inprogress", issue = "130840")] + InProgress, + + // "Unusual" error kinds which do not correspond simply to (sets + // of) OS error codes, should be added just above this comment. + // `Other` and `Uncategorized` should remain at the end: + // + /// A custom error that does not fall under any other I/O error kind. + /// + /// This can be used to construct your own [`Error`]s that do not match any + /// [`ErrorKind`]. + /// + /// This [`ErrorKind`] is not used by the standard library. + /// + /// Errors from the standard library that do not fall under any of the I/O + /// error kinds cannot be `match`ed on, and will only match a wildcard (`_`) pattern. + /// New [`ErrorKind`]s might be added in the future for some of those. + #[stable(feature = "rust1", since = "1.0.0")] + Other, + + /// Any I/O error from the standard library that's not part of this list. + /// + /// Errors that are `Uncategorized` now may move to a different or a new + /// [`ErrorKind`] variant in the future. It is not recommended to match + /// an error against `Uncategorized`; use a wildcard match (`_`) instead. + #[unstable(feature = "io_error_uncategorized", issue = "none")] + #[doc(hidden)] + Uncategorized, +} + +impl ErrorKind { + pub(crate) fn as_str(&self) -> &'static str { + use ErrorKind::*; + match *self { + // tidy-alphabetical-start + AddrInUse => "address in use", + AddrNotAvailable => "address not available", + AlreadyExists => "entity already exists", + ArgumentListTooLong => "argument list too long", + BrokenPipe => "broken pipe", + ConnectionAborted => "connection aborted", + ConnectionRefused => "connection refused", + ConnectionReset => "connection reset", + CrossesDevices => "cross-device link or rename", + Deadlock => "deadlock", + DirectoryNotEmpty => "directory not empty", + ExecutableFileBusy => "executable file busy", + FileTooLarge => "file too large", + FilesystemLoop => "filesystem loop or indirection limit (e.g. symlink loop)", + HostUnreachable => "host unreachable", + InProgress => "in progress", + Interrupted => "operation interrupted", + InvalidData => "invalid data", + InvalidFilename => "invalid filename", + InvalidInput => "invalid input parameter", + IsADirectory => "is a directory", + NetworkDown => "network down", + NetworkUnreachable => "network unreachable", + NotADirectory => "not a directory", + NotConnected => "not connected", + NotFound => "entity not found", + NotSeekable => "seek on unseekable file", + Other => "other error", + OutOfMemory => "out of memory", + PermissionDenied => "permission denied", + QuotaExceeded => "quota exceeded", + ReadOnlyFilesystem => "read-only filesystem or storage medium", + ResourceBusy => "resource busy", + StaleNetworkFileHandle => "stale network file handle", + StorageFull => "no storage space", + TimedOut => "timed out", + TooManyLinks => "too many links", + Uncategorized => "uncategorized error", + UnexpectedEof => "unexpected end of file", + Unsupported => "unsupported", + WouldBlock => "operation would block", + WriteZero => "write zero", + // tidy-alphabetical-end + } + } +} + +#[stable(feature = "io_errorkind_display", since = "1.60.0")] +impl fmt::Display for ErrorKind { + /// Shows a human-readable description of the `ErrorKind`. + /// + /// This is similar to `impl Display for Error`, but doesn't require first converting to Error. + /// + /// # Examples + /// ``` + /// use std::io::ErrorKind; + /// assert_eq!("entity not found", ErrorKind::NotFound.to_string()); + /// ``` + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.write_str(self.as_str()) + } +} + +/// Intended for use for errors not exposed to the user, where allocating onto +/// the heap (for normal construction via Error::new) is too costly. +#[stable(feature = "io_error_from_errorkind", since = "1.14.0")] +impl From for Error { + /// Converts an [`ErrorKind`] into an [`Error`]. + /// + /// This conversion creates a new error with a simple representation of error kind. + /// + /// # Examples + /// + /// ``` + /// use std::io::{Error, ErrorKind}; + /// + /// let not_found = ErrorKind::NotFound; + /// let error = Error::from(not_found); + /// assert_eq!("entity not found", format!("{error}")); + /// ``` + #[inline] + fn from(kind: ErrorKind) -> Error { + Error { repr: Repr::new_simple(kind) } + } +} + +impl Error { + /// implementation detail of [`Error::new`] + /// + /// [`Error::new`]: https://doc.rust-lang.org/std/io/struct.Error.html#method.new + #[doc(hidden)] + #[unstable(feature = "core_io_error_internals", issue = "none")] + pub fn _new_custom(v: ErrorBox) -> Error { + Error { repr: Repr::new_custom(v) } + } + + /// Creates a new I/O error from a known kind of error as well as a constant + /// message. + /// + /// This function does not allocate. + /// + /// You should not use this directly, and instead use the `const_error!` + /// macro: `io::const_error!(ErrorKind::Something, "some_message")`. + /// + /// This function should maybe change to `from_static_message(kind: ErrorKind)` in the future, when const generics allow that. + #[inline] + #[doc(hidden)] + #[unstable(feature = "core_io_error_internals", issue = "none")] + pub const fn from_static_message(msg: &'static SimpleMessage) -> Error { + Self { repr: Repr::new_simple_message(msg) } + } + + /// implementation detail of [`Error::from_raw_os_error`] + /// + /// [`Error::from_raw_os_error`]: https://doc.rust-lang.org/std/io/struct.Error.html#method.from_raw_os_error + #[doc(hidden)] + #[unstable(feature = "core_io_error_internals", issue = "none")] + #[must_use] + #[inline] + pub unsafe fn from_raw_os_error_impl(code: RawOsError) -> Error { + Error { repr: Repr::new_os(code) } + } + + /// Returns the OS error that this error represents (if any). + /// + /// If this [`Error`] was constructed via [`last_os_error`] or + /// [`from_raw_os_error`], then this function will return [`Some`], otherwise + /// it will return [`None`]. + /// + /// [`last_os_error`]: https://doc.rust-lang.org/std/io/struct.Error.html#method.last_os_error + /// [`from_raw_os_error`]: https://doc.rust-lang.org/std/io/struct.Error.html#method.from_raw_os_error + /// + /// # Examples + /// + /// ``` + /// use std::io::{Error, ErrorKind}; + /// + /// fn print_os_error(err: &Error) { + /// if let Some(raw_os_err) = err.raw_os_error() { + /// println!("raw OS error: {raw_os_err:?}"); + /// } else { + /// println!("Not an OS error"); + /// } + /// } + /// + /// fn main() { + /// // Will print "raw OS error: ...". + /// print_os_error(&Error::last_os_error()); + /// // Will print "Not an OS error". + /// print_os_error(&Error::new(ErrorKind::Other, "oh no!")); + /// } + /// ``` + #[stable(feature = "rust1", since = "1.0.0")] + #[must_use] + #[inline] + pub fn raw_os_error(&self) -> Option { + match self.repr.data() { + ErrorData::Os(i) => Some(i), + ErrorData::Custom(..) => None, + ErrorData::Simple(..) => None, + ErrorData::SimpleMessage(..) => None, + } + } + + /// Returns a reference to the inner error wrapped by this error (if any). + /// + /// If this [`Error`] was constructed via [`new`] then this function will + /// return [`Some`], otherwise it will return [`None`]. + /// + /// [`new`]: https://doc.rust-lang.org/std/io/struct.Error.html#method.new + /// + /// # Examples + /// + /// ``` + /// use std::io::{Error, ErrorKind}; + /// + /// fn print_error(err: &Error) { + /// if let Some(inner_err) = err.get_ref() { + /// println!("Inner error: {inner_err:?}"); + /// } else { + /// println!("No inner error"); + /// } + /// } + /// + /// fn main() { + /// // Will print "No inner error". + /// print_error(&Error::last_os_error()); + /// // Will print "Inner error: ...". + /// print_error(&Error::new(ErrorKind::Other, "oh no!")); + /// } + /// ``` + #[stable(feature = "io_error_inner", since = "1.3.0")] + #[must_use] + #[inline] + pub fn get_ref(&self) -> Option<&(dyn error::Error + Send + Sync + 'static)> { + match self.repr.data() { + ErrorData::Os(..) => None, + ErrorData::Simple(..) => None, + ErrorData::SimpleMessage(..) => None, + ErrorData::Custom(c) => Some(&*c.error), + } + } + + /// Returns a mutable reference to the inner error wrapped by this error + /// (if any). + /// + /// If this [`Error`] was constructed via [`new`] then this function will + /// return [`Some`], otherwise it will return [`None`]. + /// + /// [`new`]: https://doc.rust-lang.org/std/io/struct.Error.html#method.new + /// + /// # Examples + /// + /// ``` + /// use std::io::{Error, ErrorKind}; + /// use std::{error, fmt}; + /// use std::fmt::Display; + /// + /// #[derive(Debug)] + /// struct MyError { + /// v: String, + /// } + /// + /// impl MyError { + /// fn new() -> MyError { + /// MyError { + /// v: "oh no!".to_string() + /// } + /// } + /// + /// fn change_message(&mut self, new_message: &str) { + /// self.v = new_message.to_string(); + /// } + /// } + /// + /// impl error::Error for MyError {} + /// + /// impl Display for MyError { + /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + /// write!(f, "MyError: {}", &self.v) + /// } + /// } + /// + /// fn change_error(mut err: Error) -> Error { + /// if let Some(inner_err) = err.get_mut() { + /// inner_err.downcast_mut::().unwrap().change_message("I've been changed!"); + /// } + /// err + /// } + /// + /// fn print_error(err: &Error) { + /// if let Some(inner_err) = err.get_ref() { + /// println!("Inner error: {inner_err}"); + /// } else { + /// println!("No inner error"); + /// } + /// } + /// + /// fn main() { + /// // Will print "No inner error". + /// print_error(&change_error(Error::last_os_error())); + /// // Will print "Inner error: ...". + /// print_error(&change_error(Error::new(ErrorKind::Other, MyError::new()))); + /// } + /// ``` + #[stable(feature = "io_error_inner", since = "1.3.0")] + #[must_use] + #[inline] + pub fn get_mut(&mut self) -> Option<&mut (dyn error::Error + Send + Sync + 'static)> { + match self.repr.data_mut() { + ErrorData::Os(..) => None, + ErrorData::Simple(..) => None, + ErrorData::SimpleMessage(..) => None, + ErrorData::Custom(c) => Some(&mut *c.error), + } + } + + /// implementation detail of [`Error::into_inner`] + /// + /// [`Error::into_inner`]: https://doc.rust-lang.org/std/io/struct.Error.html#method.into_inner + #[doc(hidden)] + #[unstable(feature = "core_io_error_internals", issue = "none")] + #[inline] + pub fn into_inner_impl(self) -> Option> { + match self.repr.into_data() { + ErrorData::Os(..) => None, + ErrorData::Simple(..) => None, + ErrorData::SimpleMessage(..) => None, + ErrorData::Custom(c) => Some(c.into_inner().error), + } + } + + /// implementation detail of [`Error::downcast`] + /// + /// [`Error::downcast`]: https://doc.rust-lang.org/std/io/struct.Error.html#method.downcast + #[doc(hidden)] + #[unstable(feature = "core_io_error_internals", issue = "none")] + pub fn downcast_impl(self) -> result::Result<*mut E, Self> + where + E: error::Error + Send + Sync + 'static, + { + match self.repr.into_data() { + ErrorData::Custom(b) if b.error.is::() => Ok(b.into_inner().error.into_raw().cast()), + repr_data => Err(Self { repr: Repr::new(repr_data) }), + } + } + + /// Returns the corresponding [`ErrorKind`] for this error. + /// + /// This may be a value set by Rust code constructing custom `io::Error`s, + /// or if this `io::Error` was sourced from the operating system, + /// it will be a value inferred from the system's error encoding. + /// See [`last_os_error`] for more details. + /// + /// [`last_os_error`]: https://doc.rust-lang.org/std/io/struct.Error.html#method.last_os_error + /// + /// # Examples + /// + /// ``` + /// use std::io::{Error, ErrorKind}; + /// + /// fn print_error(err: Error) { + /// println!("{:?}", err.kind()); + /// } + /// + /// fn main() { + /// // As no error has (visibly) occurred, this may print anything! + /// // It likely prints a placeholder for unidentified (non-)errors. + /// print_error(Error::last_os_error()); + /// // Will print "AddrInUse". + /// print_error(Error::new(ErrorKind::AddrInUse, "oh no!")); + /// } + /// ``` + #[stable(feature = "rust1", since = "1.0.0")] + #[must_use] + #[inline] + pub fn kind(&self) -> ErrorKind { + match self.repr.data() { + // SAFETY: `Error`'s safety invariant guarantees + // `StdVTable::install` has been run + ErrorData::Os(code) => unsafe { (StdVTable::get().decode_error_kind)(code) }, + ErrorData::Custom(c) => c.kind, + ErrorData::Simple(kind) => kind, + ErrorData::SimpleMessage(m) => m.kind, + } + } +} + +impl fmt::Debug for Repr { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.data() { + ErrorData::Os(code) => { + // SAFETY: `Error`'s safety invariant guarantees + // `StdVTable::install` has been run + let std_vtable = unsafe { StdVTable::get() }; + fmt.debug_struct("Os") + .field("code", &code) + .field("kind", &(std_vtable.decode_error_kind)(code)) + .field("message", &(std_vtable.error_string)(code)) + .finish() + } + ErrorData::Custom(c) => fmt::Debug::fmt(&c, fmt), + ErrorData::Simple(kind) => fmt.debug_tuple("Kind").field(&kind).finish(), + ErrorData::SimpleMessage(msg) => fmt + .debug_struct("Error") + .field("kind", &msg.kind) + .field("message", &msg.message) + .finish(), + } + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl fmt::Display for Error { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.repr.data() { + ErrorData::Os(code) => { + // SAFETY: `Error`'s safety invariant guarantees + // `StdVTable::install` has been run + let detail = unsafe { (StdVTable::get().error_string)(code) }; + write!(fmt, "{detail} (os error {code})") + } + ErrorData::Custom(ref c) => c.error.fmt(fmt), + ErrorData::Simple(kind) => write!(fmt, "{}", kind.as_str()), + ErrorData::SimpleMessage(msg) => msg.message.fmt(fmt), + } + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl error::Error for Error { + #[allow(deprecated)] + fn cause(&self) -> Option<&dyn error::Error> { + match self.repr.data() { + ErrorData::Os(..) => None, + ErrorData::Simple(..) => None, + ErrorData::SimpleMessage(..) => None, + ErrorData::Custom(c) => c.error.cause(), + } + } + + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self.repr.data() { + ErrorData::Os(..) => None, + ErrorData::Simple(..) => None, + ErrorData::SimpleMessage(..) => None, + ErrorData::Custom(c) => c.error.source(), + } + } +} + +fn _assert_error_is_sync_send() { + fn _is_sync_send() {} + _is_sync_send::(); +} + +/// Common errors constants for use in std +#[allow(dead_code, missing_docs)] +#[doc(hidden)] +#[unstable(feature = "io_const_error_internals", issue = "none")] +impl Error { + pub const INVALID_UTF8: Self = + const_error!(ErrorKind::InvalidData, "stream did not contain valid UTF-8"); + + pub const READ_EXACT_EOF: Self = + const_error!(ErrorKind::UnexpectedEof, "failed to fill whole buffer"); + + pub const UNKNOWN_THREAD_COUNT: Self = const_error!( + ErrorKind::NotFound, + "the number of hardware threads is not known for the target platform", + ); + + pub const UNSUPPORTED_PLATFORM: Self = + const_error!(ErrorKind::Unsupported, "operation not supported on this platform"); + + pub const WRITE_ALL_EOF: Self = + const_error!(ErrorKind::WriteZero, "failed to write whole buffer"); + + pub const ZERO_TIMEOUT: Self = + const_error!(ErrorKind::InvalidInput, "cannot set a 0 duration timeout"); + + pub const NO_ADDRESSES: Self = + const_error!(ErrorKind::InvalidInput, "could not resolve to any addresses"); +} diff --git a/library/core/src/io/error/repr_bitpacked.rs b/library/core/src/io/error/repr_bitpacked.rs new file mode 100644 index 0000000000000..45b0f0f489d70 --- /dev/null +++ b/library/core/src/io/error/repr_bitpacked.rs @@ -0,0 +1,422 @@ +//! This is a densely packed error representation which is used on targets with +//! 64-bit pointers. +//! +//! (Note that `bitpacked` vs `unpacked` here has no relationship to +//! `#[repr(packed)]`, it just refers to attempting to use any available bits in +//! a more clever manner than `rustc`'s default layout algorithm would). +//! +//! Conceptually, it stores the same data as the "unpacked" equivalent we use on +//! other targets. Specifically, you can imagine it as an optimized version of +//! the following enum (which is roughly equivalent to what's stored by +//! `repr_unpacked::Repr`, e.g. `super::ErrorData>`): +//! +//! ```ignore (exposition-only) +//! enum ErrorData { +//! Os(i32), +//! Simple(ErrorKind), +//! SimpleMessage(&'static SimpleMessage), +//! Custom(Box), +//! } +//! ``` +//! +//! However, it packs this data into a 64bit non-zero value. +//! +//! This optimization not only allows `io::Error` to occupy a single pointer, +//! but improves `io::Result` as well, especially for situations like +//! `io::Result<()>` (which is now 64 bits) or `io::Result` (which is now +//! 128 bits), which are quite common. +//! +//! # Layout +//! Tagged values are 64 bits, with the 2 least significant bits used for the +//! tag. This means there are 4 "variants": +//! +//! - **Tag 0b00**: The first variant is equivalent to +//! `ErrorData::SimpleMessage`, and holds a `&'static SimpleMessage` directly. +//! +//! `SimpleMessage` has an alignment >= 4 (which is requested with +//! `#[repr(align)]` and checked statically at the bottom of this file), which +//! means every `&'static SimpleMessage` should have the both tag bits as 0, +//! meaning its tagged and untagged representation are equivalent. +//! +//! This means we can skip tagging it, which is necessary as this variant can +//! be constructed from a `const fn`, which probably cannot tag pointers (or +//! at least it would be difficult). +//! +//! - **Tag 0b01**: The other pointer variant holds the data for +//! `ErrorData::Custom` and the remaining 62 bits are used to store a +//! `Box`. `Custom` also has alignment >= 4, so the bottom two bits +//! are free to use for the tag. +//! +//! The only important thing to note is that `ptr::wrapping_add` and +//! `ptr::wrapping_sub` are used to tag the pointer, rather than bitwise +//! operations. This should preserve the pointer's provenance, which would +//! otherwise be lost. +//! +//! - **Tag 0b10**: Holds the data for `ErrorData::Os(i32)`. We store the `i32` +//! in the pointer's most significant 32 bits, and don't use the bits `2..32` +//! for anything. Using the top 32 bits is just to let us easily recover the +//! `i32` code with the correct sign. +//! +//! - **Tag 0b11**: Holds the data for `ErrorData::Simple(ErrorKind)`. This +//! stores the `ErrorKind` in the top 32 bits as well, although it doesn't +//! occupy nearly that many. Most of the bits are unused here, but it's not +//! like we need them for anything else yet. +//! +//! # Use of `NonNull<()>` +//! +//! Everything is stored in a `NonNull<()>`, which is odd, but actually serves a +//! purpose. +//! +//! Conceptually you might think of this more like: +//! +//! ```ignore (exposition-only) +//! union Repr { +//! // holds integer (Simple/Os) variants, and +//! // provides access to the tag bits. +//! bits: NonZero, +//! // Tag is 0, so this is stored untagged. +//! msg: &'static SimpleMessage, +//! // Tagged (offset) `Box` pointer. +//! tagged_custom: NonNull<()>, +//! } +//! ``` +//! +//! But there are a few problems with this: +//! +//! 1. Union access is equivalent to a transmute, so this representation would +//! require we transmute between integers and pointers in at least one +//! direction, which may be UB (and even if not, it is likely harder for a +//! compiler to reason about than explicit ptr->int operations). +//! +//! 2. Even if all fields of a union have a niche, the union itself doesn't, +//! although this may change in the future. This would make things like +//! `io::Result<()>` and `io::Result` larger, which defeats part of +//! the motivation of this bitpacking. +//! +//! Storing everything in a `NonZero` (or some other integer) would be a +//! bit more traditional for pointer tagging, but it would lose provenance +//! information, couldn't be constructed from a `const fn`, and would probably +//! run into other issues as well. +//! +//! The `NonNull<()>` seems like the only alternative, even if it's fairly odd +//! to use a pointer type to store something that may hold an integer, some of +//! the time. + +use core::marker::PhantomData; +use core::num::NonZeroUsize; +use core::ptr::NonNull; + +use super::{Custom, ErrorBox, ErrorData, ErrorKind, RawOsError, SimpleMessage}; + +// The 2 least-significant bits are used as tag. +const TAG_MASK: usize = 0b11; +const TAG_SIMPLE_MESSAGE: usize = 0b00; +const TAG_CUSTOM: usize = 0b01; +const TAG_OS: usize = 0b10; +const TAG_SIMPLE: usize = 0b11; + +/// The internal representation. +/// +/// See the module docs for more, this is just a way to hack in a check that we +/// indeed are not unwind-safe. +/// +/// ```compile_fail,E0277 +/// fn is_unwind_safe() {} +/// is_unwind_safe::(); +/// ``` +#[repr(transparent)] +#[rustc_insignificant_dtor] +pub(super) struct Repr(NonNull<()>, PhantomData>>); + +// All the types `Repr` stores internally are Send + Sync, and so is it. +unsafe impl Send for Repr {} +unsafe impl Sync for Repr {} + +impl Repr { + pub(super) fn new(dat: ErrorData>) -> Self { + match dat { + ErrorData::Os(code) => Self::new_os(code), + ErrorData::Simple(kind) => Self::new_simple(kind), + ErrorData::SimpleMessage(simple_message) => Self::new_simple_message(simple_message), + ErrorData::Custom(b) => Self::new_custom(b), + } + } + + pub(super) fn new_custom(b: ErrorBox) -> Self { + let p = ErrorBox::into_raw(b).cast::(); + // Should only be possible if an allocator handed out a pointer with + // wrong alignment. + debug_assert_eq!(p.addr() & TAG_MASK, 0); + // Note: We know `TAG_CUSTOM <= size_of::()` (static_assert at + // end of file), and both the start and end of the expression must be + // valid without address space wraparound due to `Box`'s semantics. + // + // This means it would be correct to implement this using `ptr::add` + // (rather than `ptr::wrapping_add`), but it's unclear this would give + // any benefit, so we just use `wrapping_add` instead. + let tagged = p.wrapping_add(TAG_CUSTOM).cast::<()>(); + // Safety: `TAG_CUSTOM + p` is the same as `TAG_CUSTOM | p`, + // because `p`'s alignment means it isn't allowed to have any of the + // `TAG_BITS` set (you can verify that addition and bitwise-or are the + // same when the operands have no bits in common using a truth table). + // + // Then, `TAG_CUSTOM | p` is not zero, as that would require + // `TAG_CUSTOM` and `p` both be zero, and neither is (as `p` came from a + // box, and `TAG_CUSTOM` just... isn't zero -- it's `0b01`). Therefore, + // `TAG_CUSTOM + p` isn't zero and so `tagged` can't be, and the + // `new_unchecked` is safe. + let res = Self(unsafe { NonNull::new_unchecked(tagged) }, PhantomData); + // quickly smoke-check we encoded the right thing (This generally will + // only run in std's tests, unless the user uses -Zbuild-std) + debug_assert!(matches!(res.data(), ErrorData::Custom(_)), "repr(custom) encoding failed"); + res + } + + #[inline] + pub(super) fn new_os(code: RawOsError) -> Self { + let utagged = ((code as usize) << 32) | TAG_OS; + // Safety: `TAG_OS` is not zero, so the result of the `|` is not 0. + let res = Self( + NonNull::without_provenance(unsafe { NonZeroUsize::new_unchecked(utagged) }), + PhantomData, + ); + // quickly smoke-check we encoded the right thing (This generally will + // only run in std's tests, unless the user uses -Zbuild-std) + debug_assert!( + matches!(res.data(), ErrorData::Os(c) if c == code), + "repr(os) encoding failed for {code}" + ); + res + } + + #[inline] + pub(super) fn new_simple(kind: ErrorKind) -> Self { + let utagged = ((kind as usize) << 32) | TAG_SIMPLE; + // Safety: `TAG_SIMPLE` is not zero, so the result of the `|` is not 0. + let res = Self( + NonNull::without_provenance(unsafe { NonZeroUsize::new_unchecked(utagged) }), + PhantomData, + ); + // quickly smoke-check we encoded the right thing (This generally will + // only run in std's tests, unless the user uses -Zbuild-std) + debug_assert!( + matches!(res.data(), ErrorData::Simple(k) if k == kind), + "repr(simple) encoding failed {:?}", + kind, + ); + res + } + + #[inline] + pub(super) const fn new_simple_message(m: &'static SimpleMessage) -> Self { + // Safety: References are never null. + Self(unsafe { NonNull::new_unchecked(m as *const _ as *mut ()) }, PhantomData) + } + + #[inline] + pub(super) fn data(&self) -> ErrorData<&Custom> { + // Safety: We're a Repr, decode_repr is fine. + unsafe { decode_repr(self.0, |c| &*c) } + } + + #[inline] + pub(super) fn data_mut(&mut self) -> ErrorData<&mut Custom> { + // Safety: We're a Repr, decode_repr is fine. + unsafe { decode_repr(self.0, |c| &mut *c) } + } + + #[inline] + pub(super) fn into_data(self) -> ErrorData> { + let this = core::mem::ManuallyDrop::new(self); + // Safety: We're a Repr, decode_repr is fine. The `Box::from_raw` is + // safe because we prevent double-drop using `ManuallyDrop`. + unsafe { decode_repr(this.0, |p| ErrorBox::from_raw(p)) } + } +} + +impl Drop for Repr { + #[inline] + fn drop(&mut self) { + // Safety: We're a Repr, decode_repr is fine. The `Box::from_raw` is + // safe because we're being dropped. + unsafe { + let _ = decode_repr(self.0, |p| ErrorBox::::from_raw(p)); + } + } +} + +// Shared helper to decode a `Repr`'s internal pointer into an ErrorData. +// +// Safety: `ptr`'s bits should be encoded as described in the document at the +// top (it should `some_repr.0`) +#[inline] +unsafe fn decode_repr(ptr: NonNull<()>, make_custom: F) -> ErrorData +where + F: FnOnce(*mut Custom) -> C, +{ + let bits = ptr.as_ptr().addr(); + match bits & TAG_MASK { + TAG_OS => { + let code = ((bits as i64) >> 32) as RawOsError; + ErrorData::Os(code) + } + TAG_SIMPLE => { + let kind_bits = (bits >> 32) as u32; + let kind = kind_from_prim(kind_bits).unwrap_or_else(|| { + debug_assert!(false, "Invalid io::error::Repr bits: `Repr({:#018x})`", bits); + // This means the `ptr` passed in was not valid, which violates + // the unsafe contract of `decode_repr`. + // + // Using this rather than unwrap meaningfully improves the code + // for callers which only care about one variant (usually + // `Custom`) + unsafe { core::hint::unreachable_unchecked() }; + }); + ErrorData::Simple(kind) + } + TAG_SIMPLE_MESSAGE => { + // SAFETY: per tag + unsafe { ErrorData::SimpleMessage(&*ptr.cast::().as_ptr()) } + } + TAG_CUSTOM => { + // It would be correct for us to use `ptr::byte_sub` here (see the + // comment above the `wrapping_add` call in `new_custom` for why), + // but it isn't clear that it makes a difference, so we don't. + let custom = ptr.as_ptr().wrapping_byte_sub(TAG_CUSTOM).cast::(); + ErrorData::Custom(make_custom(custom)) + } + _ => { + // Can't happen, and compiler can tell + unreachable!(); + } + } +} + +// This compiles to the same code as the check+transmute, but doesn't require +// unsafe, or to hard-code max ErrorKind or its size in a way the compiler +// couldn't verify. +/// implementation detail of [`super::Error`] +#[unstable(feature = "core_io_error_internals", issue = "none")] +#[inline] +pub fn kind_from_prim(ek: u32) -> Option { + macro_rules! from_prim { + ($prim:expr => $Enum:ident { $($Variant:ident),* $(,)? }) => {{ + // Force a compile error if the list gets out of date. + const _: fn(e: $Enum) = |e: $Enum| match e { + $($Enum::$Variant => ()),* + }; + match $prim { + $(v if v == ($Enum::$Variant as _) => Some($Enum::$Variant),)* + _ => None, + } + }} + } + from_prim!(ek => ErrorKind { + NotFound, + PermissionDenied, + ConnectionRefused, + ConnectionReset, + HostUnreachable, + NetworkUnreachable, + ConnectionAborted, + NotConnected, + AddrInUse, + AddrNotAvailable, + NetworkDown, + BrokenPipe, + AlreadyExists, + WouldBlock, + NotADirectory, + IsADirectory, + DirectoryNotEmpty, + ReadOnlyFilesystem, + FilesystemLoop, + StaleNetworkFileHandle, + InvalidInput, + InvalidData, + TimedOut, + WriteZero, + StorageFull, + NotSeekable, + QuotaExceeded, + FileTooLarge, + ResourceBusy, + ExecutableFileBusy, + Deadlock, + CrossesDevices, + TooManyLinks, + InvalidFilename, + ArgumentListTooLong, + Interrupted, + Other, + UnexpectedEof, + Unsupported, + OutOfMemory, + InProgress, + Uncategorized, + }) +} + +// Some static checking to alert us if a change breaks any of the assumptions +// that our encoding relies on for correctness and soundness. (Some of these are +// a bit overly thorough/cautious, admittedly) +// +// If any of these are hit on a platform that std supports, we should likely +// just use `repr_unpacked.rs` there instead (unless the fix is easy). +macro_rules! static_assert { + ($condition:expr) => { + const _: () = assert!($condition); + }; + (@usize_eq: $lhs:expr, $rhs:expr) => { + const _: [(); $lhs] = [(); $rhs]; + }; +} + +// The bitpacking we use requires pointers be exactly 64 bits. +static_assert!(@usize_eq: size_of::>(), 8); + +// We also require pointers and usize be the same size. +static_assert!(@usize_eq: size_of::>(), size_of::()); + +// `Custom` and `SimpleMessage` need to be thin pointers. +static_assert!(@usize_eq: size_of::<&'static SimpleMessage>(), 8); +static_assert!(@usize_eq: size_of::>(), 8); + +static_assert!((TAG_MASK + 1).is_power_of_two()); +// And they must have sufficient alignment. +static_assert!(align_of::() >= TAG_MASK + 1); +static_assert!(align_of::() >= TAG_MASK + 1); + +static_assert!(@usize_eq: TAG_MASK & TAG_SIMPLE_MESSAGE, TAG_SIMPLE_MESSAGE); +static_assert!(@usize_eq: TAG_MASK & TAG_CUSTOM, TAG_CUSTOM); +static_assert!(@usize_eq: TAG_MASK & TAG_OS, TAG_OS); +static_assert!(@usize_eq: TAG_MASK & TAG_SIMPLE, TAG_SIMPLE); + +// This is obviously true (`TAG_CUSTOM` is `0b01`), but in `Repr::new_custom` we +// offset a pointer by this value, and expect it to both be within the same +// object, and to not wrap around the address space. See the comment in that +// function for further details. +// +// Actually, at the moment we use `ptr::wrapping_add`, not `ptr::add`, so this +// check isn't needed for that one, although the assertion that we don't +// actually wrap around in that wrapping_add does simplify the safety reasoning +// elsewhere considerably. +static_assert!(size_of::() >= TAG_CUSTOM); + +// These two store a payload which is allowed to be zero, so they must be +// non-zero to preserve the `NonNull`'s range invariant. +static_assert!(TAG_OS != 0); +static_assert!(TAG_SIMPLE != 0); +// We can't tag `SimpleMessage`s, the tag must be 0. +static_assert!(@usize_eq: TAG_SIMPLE_MESSAGE, 0); + +// Check that the point of all of this still holds. +// +// We'd check against `io::Error`, but *technically* it's allowed to vary, +// as it's not `#[repr(transparent)]`/`#[repr(C)]`. We could add that, but +// the `#[repr()]` would show up in rustdoc, which might be seen as a stable +// commitment. +static_assert!(@usize_eq: size_of::(), 8); +static_assert!(@usize_eq: size_of::>(), 8); +static_assert!(@usize_eq: size_of::>(), 8); +static_assert!(@usize_eq: size_of::>(), 16); diff --git a/library/core/src/io/error/repr_unpacked.rs b/library/core/src/io/error/repr_unpacked.rs new file mode 100644 index 0000000000000..d1b7c5d7d41fc --- /dev/null +++ b/library/core/src/io/error/repr_unpacked.rs @@ -0,0 +1,53 @@ +//! This is a fairly simple unpacked error representation that's used on +//! non-64bit targets, where the packed 64 bit representation wouldn't work, and +//! would have no benefit. + +use super::{Custom, ErrorBox, ErrorData, ErrorKind, RawOsError, SimpleMessage}; + +type Inner = ErrorData>; + +pub(super) struct Repr(Inner); + +impl Repr { + #[inline] + pub(super) fn new(dat: ErrorData>) -> Self { + Self(dat) + } + pub(super) fn new_custom(b: ErrorBox) -> Self { + Self(Inner::Custom(b)) + } + #[inline] + pub(super) fn new_os(code: RawOsError) -> Self { + Self(Inner::Os(code)) + } + #[inline] + pub(super) fn new_simple(kind: ErrorKind) -> Self { + Self(Inner::Simple(kind)) + } + #[inline] + pub(super) const fn new_simple_message(m: &'static SimpleMessage) -> Self { + Self(Inner::SimpleMessage(m)) + } + #[inline] + pub(super) fn into_data(self) -> ErrorData> { + self.0 + } + #[inline] + pub(super) fn data(&self) -> ErrorData<&Custom> { + match &self.0 { + Inner::Os(c) => ErrorData::Os(*c), + Inner::Simple(k) => ErrorData::Simple(*k), + Inner::SimpleMessage(m) => ErrorData::SimpleMessage(*m), + Inner::Custom(m) => ErrorData::Custom(&*m), + } + } + #[inline] + pub(super) fn data_mut(&mut self) -> ErrorData<&mut Custom> { + match &mut self.0 { + Inner::Os(c) => ErrorData::Os(*c), + Inner::Simple(k) => ErrorData::Simple(*k), + Inner::SimpleMessage(m) => ErrorData::SimpleMessage(*m), + Inner::Custom(m) => ErrorData::Custom(&mut *m), + } + } +} diff --git a/library/core/src/io/mod.rs b/library/core/src/io/mod.rs index 2f20180cdc9a2..7e7847f5e76c7 100644 --- a/library/core/src/io/mod.rs +++ b/library/core/src/io/mod.rs @@ -1,6 +1,32 @@ //! Traits, helpers, and type definitions for core I/O functionality. +#![unstable(feature = "core_io", issue = "none")] + mod borrowed_buf; #[unstable(feature = "core_io_borrowed_buf", issue = "117693")] pub use self::borrowed_buf::{BorrowedBuf, BorrowedCursor}; + +#[cfg(target_has_atomic_load_store = "ptr")] +mod error; + +#[unstable(feature = "raw_os_error_ty", issue = "107792")] +pub use self::error::RawOsError; +#[doc(hidden)] +#[unstable(feature = "io_const_error_internals", issue = "none")] +pub use self::error::SimpleMessage; +#[cfg(target_has_atomic_load_store = "ptr")] +#[unstable(feature = "io_const_error", issue = "133448")] +pub use self::error::const_error; +#[unstable(feature = "core_io_error", issue = "none")] +#[cfg(target_has_atomic_load_store = "ptr")] +pub use self::error::{Error, ErrorKind, Result}; + +#[cfg(target_has_atomic_load_store = "ptr")] +#[doc(hidden)] +#[unstable(feature = "core_io_error_internals", issue = "none")] +pub mod error_internals { + //! implementation detail of [`Error`] + + pub use super::error::*; +} diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index e12c43068245a..af585535990c6 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -108,6 +108,7 @@ #![feature(coverage_attribute)] #![feature(disjoint_bitor)] #![feature(internal_impls_macro)] +#![feature(io_const_error)] #![feature(link_cfg)] #![feature(offset_of_enum)] #![feature(panic_internals)] @@ -294,7 +295,8 @@ pub mod bstr; pub mod cell; pub mod char; pub mod ffi; -#[unstable(feature = "core_io_borrowed_buf", issue = "117693")] +// TODO: how to make unstable for either of two features? +// #[unstable(feature = "core_io_borrowed_buf", issue = "117693")] pub mod io; pub mod iter; pub mod net; diff --git a/library/coretests/tests/io/error.rs b/library/coretests/tests/io/error.rs new file mode 100644 index 0000000000000..090ed56693eb3 --- /dev/null +++ b/library/coretests/tests/io/error.rs @@ -0,0 +1,124 @@ +use core::io::{Error, ErrorKind, SimpleMessage, const_error}; +use core::{assert_matches, error, fmt}; + +#[test] +fn test_size() { + assert!(size_of::() <= size_of::<[usize; 2]>()); +} + +#[test] +fn test_const() { + const E: Error = const_error!(ErrorKind::NotFound, "hello"); + + assert_eq!(E.kind(), ErrorKind::NotFound); + assert_eq!(E.to_string(), "hello"); + assert!(format!("{E:?}").contains("\"hello\"")); + assert!(format!("{E:?}").contains("NotFound")); +} + +#[test] +fn test_os_packing() { + for code in -20..20 { + let e = Error::from_raw_os_error(code); + assert_eq!(e.raw_os_error(), Some(code)); + } +} + +#[test] +fn test_errorkind_packing() { + assert_eq!(Error::from(ErrorKind::NotFound).kind(), ErrorKind::NotFound); + assert_eq!(Error::from(ErrorKind::PermissionDenied).kind(), ErrorKind::PermissionDenied); + assert_eq!(Error::from(ErrorKind::Uncategorized).kind(), ErrorKind::Uncategorized); +} + +#[test] +fn test_simple_message_packing() { + use core::io::ErrorKind::*; + macro_rules! check_simple_msg { + ($err:expr, $kind:ident, $msg:literal) => {{ + let e = &$err; + // Check that the public api is right. + assert_eq!(e.kind(), $kind); + assert_eq!(e.to_string(), $msg); + }}; + } + + let not_static = const_error!(Uncategorized, "not a constant!"); + check_simple_msg!(not_static, Uncategorized, "not a constant!"); + + const CONST: Error = const_error!(NotFound, "definitely a constant!"); + check_simple_msg!(CONST, NotFound, "definitely a constant!"); + + static STATIC: Error = const_error!(BrokenPipe, "a constant, sort of!"); + check_simple_msg!(STATIC, BrokenPipe, "a constant, sort of!"); +} + +#[derive(Debug, PartialEq)] +struct Bojji(bool); +impl error::Error for Bojji {} +impl fmt::Display for Bojji { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "ah! {:?}", self) + } +} + +#[test] +fn test_custom_error_packing() { + let test = Error::new(ErrorKind::Uncategorized, Bojji(true)); + assert_eq!(test.kind(), ErrorKind::Uncategorized); + assert_matches!( + test.get_ref(), + Some(error) if error.downcast_ref::().as_deref() == Some(&Bojji(true)), + ); +} + +#[derive(Debug)] +struct E; + +impl fmt::Display for E { + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { + Ok(()) + } +} + +impl error::Error for E {} + +#[test] +fn test_std_io_error_downcast() { + // Case 1: custom error, downcast succeeds + let io_error = Error::new(ErrorKind::Other, Bojji(true)); + let e: Box = io_error.downcast().unwrap(); + assert!(e.0); + + // Case 2: custom error, downcast fails + let io_error = Error::new(ErrorKind::Other, Bojji(true)); + let io_error = io_error.downcast::().unwrap_err(); + + // ensures that the custom error is intact + assert_eq!(ErrorKind::Other, io_error.kind()); + let e: Box = io_error.downcast().unwrap(); + assert!(e.0); + + // Case 3: os error + let errno = 20; + let io_error = Error::from_raw_os_error(errno); + let io_error = io_error.downcast::().unwrap_err(); + + assert_eq!(errno, io_error.raw_os_error().unwrap()); + + // Case 4: simple + let kind = ErrorKind::OutOfMemory; + let io_error: Error = kind.into(); + let io_error = io_error.downcast::().unwrap_err(); + + assert_eq!(kind, io_error.kind()); + + // Case 5: simple message + const SIMPLE_MESSAGE: SimpleMessage = + SimpleMessage { kind: ErrorKind::Other, message: "simple message error test" }; + let io_error = Error::from_static_message(&SIMPLE_MESSAGE); + let io_error = io_error.downcast::().unwrap_err(); + + assert_eq!(SIMPLE_MESSAGE.kind, io_error.kind()); + assert_eq!(SIMPLE_MESSAGE.message, format!("{io_error}")); +} diff --git a/library/coretests/tests/io/mod.rs b/library/coretests/tests/io/mod.rs index a24893a525a9d..0ddb0e7c65392 100644 --- a/library/coretests/tests/io/mod.rs +++ b/library/coretests/tests/io/mod.rs @@ -1 +1,2 @@ mod borrowed_buf; +mod error; diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index 72112f8b01133..c96f0d30a8758 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -35,6 +35,7 @@ #![feature(core_intrinsics)] #![feature(core_intrinsics_fallbacks)] #![feature(core_io_borrowed_buf)] +#![feature(core_io_error_internals)] #![feature(core_private_bignum)] #![feature(core_private_diy_float)] #![feature(cstr_display)] @@ -65,6 +66,10 @@ #![feature(int_from_ascii)] #![feature(int_lowest_highest_one)] #![feature(int_roundings)] +#![feature(io_const_error)] +#![feature(io_const_error_internals)] +#![feature(io_error_downcast)] +#![feature(io_error_uncategorized)] #![feature(ip)] #![feature(is_ascii_octdigit)] #![feature(isolate_most_least_significant_one)] diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml index 1b7a41d697367..eb28472272195 100644 --- a/library/std/Cargo.toml +++ b/library/std/Cargo.toml @@ -168,4 +168,5 @@ check-cfg = [ 'cfg(target_has_reliable_f16_math)', 'cfg(target_has_reliable_f128)', 'cfg(target_has_reliable_f128_math)', + 'cfg(bootstrap)', ] diff --git a/library/std/src/io/error.rs b/library/std/src/io/error.rs index e6c6f7d766c02..24f0ff5da7567 100644 --- a/library/std/src/io/error.rs +++ b/library/std/src/io/error.rs @@ -8,17 +8,33 @@ mod tests; // This assumption is invalid on 64-bit UEFI, where error codes are 64-bit. // Therefore, the packed representation is explicitly disabled for UEFI // targets, and the unpacked representation must be used instead. +#[cfg(bootstrap)] #[cfg(all(target_pointer_width = "64", not(target_os = "uefi")))] mod repr_bitpacked; +#[cfg(bootstrap)] #[cfg(all(target_pointer_width = "64", not(target_os = "uefi")))] use repr_bitpacked::Repr; +#[cfg(bootstrap)] #[cfg(any(not(target_pointer_width = "64"), target_os = "uefi"))] mod repr_unpacked; +#[cfg(not(any(bootstrap, test)))] +use core::io::error_internals::{ErrorString, StdVTable}; +#[cfg(bootstrap)] +use core::io::{ErrorKind, RawOsError}; + +#[cfg(bootstrap)] #[cfg(any(not(target_pointer_width = "64"), target_os = "uefi"))] use repr_unpacked::Repr; -use crate::{error, fmt, result, sys}; +#[cfg(not(any(bootstrap, test)))] +use super::RawOsError; +#[cfg(not(bootstrap))] +use super::{Error, ErrorKind}; +#[cfg(any(bootstrap, not(test)))] +use crate::sys; +#[cfg(bootstrap)] +use crate::{error, fmt, result}; /// A specialized [`Result`] type for I/O operations. /// @@ -56,6 +72,7 @@ use crate::{error, fmt, result, sys}; /// ``` #[stable(feature = "rust1", since = "1.0.0")] #[doc(search_unbox)] +#[cfg(bootstrap)] pub type Result = result::Result; /// The error type for I/O operations of the [`Read`], [`Write`], [`Seek`], and @@ -69,45 +86,21 @@ pub type Result = result::Result; /// [`Write`]: crate::io::Write /// [`Seek`]: crate::io::Seek #[stable(feature = "rust1", since = "1.0.0")] +#[cfg(bootstrap)] pub struct Error { repr: Repr, } #[stable(feature = "rust1", since = "1.0.0")] +#[cfg(bootstrap)] impl fmt::Debug for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&self.repr, f) } } -/// Common errors constants for use in std -#[allow(dead_code)] -impl Error { - pub(crate) const INVALID_UTF8: Self = - const_error!(ErrorKind::InvalidData, "stream did not contain valid UTF-8"); - - pub(crate) const READ_EXACT_EOF: Self = - const_error!(ErrorKind::UnexpectedEof, "failed to fill whole buffer"); - - pub(crate) const UNKNOWN_THREAD_COUNT: Self = const_error!( - ErrorKind::NotFound, - "the number of hardware threads is not known for the target platform", - ); - - pub(crate) const UNSUPPORTED_PLATFORM: Self = - const_error!(ErrorKind::Unsupported, "operation not supported on this platform"); - - pub(crate) const WRITE_ALL_EOF: Self = - const_error!(ErrorKind::WriteZero, "failed to write whole buffer"); - - pub(crate) const ZERO_TIMEOUT: Self = - const_error!(ErrorKind::InvalidInput, "cannot set a 0 duration timeout"); - - pub(crate) const NO_ADDRESSES: Self = - const_error!(ErrorKind::InvalidInput, "could not resolve to any addresses"); -} - #[stable(feature = "rust1", since = "1.0.0")] +#[cfg(bootstrap)] impl From for Error { /// Converts a [`alloc::ffi::NulError`] into a [`Error`]. fn from(_: alloc::ffi::NulError) -> Error { @@ -116,6 +109,7 @@ impl From for Error { } #[stable(feature = "io_error_from_try_reserve", since = "1.78.0")] +#[cfg(bootstrap)] impl From for Error { /// Converts `TryReserveError` to an error with [`ErrorKind::OutOfMemory`]. /// @@ -130,6 +124,7 @@ impl From for Error { // Only derive debug in tests, to make sure it // doesn't accidentally get printed. #[cfg_attr(test, derive(Debug))] +#[cfg(bootstrap)] enum ErrorData { Os(RawOsError), Simple(ErrorKind), @@ -137,17 +132,6 @@ enum ErrorData { Custom(C), } -/// The type of raw OS error codes returned by [`Error::raw_os_error`]. -/// -/// This is an [`i32`] on all currently supported platforms, but platforms -/// added in the future (such as UEFI) may use a different primitive type like -/// [`usize`]. Use `as`or [`into`] conversions where applicable to ensure maximum -/// portability. -/// -/// [`into`]: Into::into -#[unstable(feature = "raw_os_error_ty", issue = "107792")] -pub type RawOsError = sys::io::RawOsError; - // `#[repr(align(4))]` is probably redundant, it should have that value or // higher already. We include it just because repr_bitpacked.rs's encoding // requires an alignment >= 4 (note that `#[repr(align)]` will not reduce the @@ -166,6 +150,7 @@ pub type RawOsError = sys::io::RawOsError; #[unstable(feature = "io_const_error_internals", issue = "none")] #[repr(align(4))] #[derive(Debug)] +#[cfg(bootstrap)] pub struct SimpleMessage { pub kind: ErrorKind, pub message: &'static str, @@ -190,6 +175,7 @@ pub struct SimpleMessage { #[rustc_macro_transparency = "semiopaque"] #[unstable(feature = "io_const_error", issue = "133448")] #[allow_internal_unstable(hint_must_use, io_const_error_internals)] +#[cfg(bootstrap)] pub macro const_error($kind:expr, $message:expr $(,)?) { $crate::hint::must_use($crate::io::Error::from_static_message( const { &$crate::io::SimpleMessage { kind: $kind, message: $message } }, @@ -201,331 +187,16 @@ pub macro const_error($kind:expr, $message:expr $(,)?) { // already be this high or higher. #[derive(Debug)] #[repr(align(4))] +#[cfg(bootstrap)] struct Custom { kind: ErrorKind, error: Box, } -/// A list specifying general categories of I/O error. -/// -/// This list is intended to grow over time and it is not recommended to -/// exhaustively match against it. -/// -/// It is used with the [`io::Error`] type. -/// -/// [`io::Error`]: Error -/// -/// # Handling errors and matching on `ErrorKind` -/// -/// In application code, use `match` for the `ErrorKind` values you are -/// expecting; use `_` to match "all other errors". -/// -/// In comprehensive and thorough tests that want to verify that a test doesn't -/// return any known incorrect error kind, you may want to cut-and-paste the -/// current full list of errors from here into your test code, and then match -/// `_` as the correct case. This seems counterintuitive, but it will make your -/// tests more robust. In particular, if you want to verify that your code does -/// produce an unrecognized error kind, the robust solution is to check for all -/// the recognized error kinds and fail in those cases. -#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -#[stable(feature = "rust1", since = "1.0.0")] -#[cfg_attr(not(test), rustc_diagnostic_item = "io_errorkind")] -#[allow(deprecated)] -#[non_exhaustive] -pub enum ErrorKind { - /// An entity was not found, often a file. - #[stable(feature = "rust1", since = "1.0.0")] - NotFound, - /// The operation lacked the necessary privileges to complete. - #[stable(feature = "rust1", since = "1.0.0")] - PermissionDenied, - /// The connection was refused by the remote server. - #[stable(feature = "rust1", since = "1.0.0")] - ConnectionRefused, - /// The connection was reset by the remote server. - #[stable(feature = "rust1", since = "1.0.0")] - ConnectionReset, - /// The remote host is not reachable. - #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] - HostUnreachable, - /// The network containing the remote host is not reachable. - #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] - NetworkUnreachable, - /// The connection was aborted (terminated) by the remote server. - #[stable(feature = "rust1", since = "1.0.0")] - ConnectionAborted, - /// The network operation failed because it was not connected yet. - #[stable(feature = "rust1", since = "1.0.0")] - NotConnected, - /// A socket address could not be bound because the address is already in - /// use elsewhere. - #[stable(feature = "rust1", since = "1.0.0")] - AddrInUse, - /// A nonexistent interface was requested or the requested address was not - /// local. - #[stable(feature = "rust1", since = "1.0.0")] - AddrNotAvailable, - /// The system's networking is down. - #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] - NetworkDown, - /// The operation failed because a pipe was closed. - #[stable(feature = "rust1", since = "1.0.0")] - BrokenPipe, - /// An entity already exists, often a file. - #[stable(feature = "rust1", since = "1.0.0")] - AlreadyExists, - /// The operation needs to block to complete, but the blocking operation was - /// requested to not occur. - #[stable(feature = "rust1", since = "1.0.0")] - WouldBlock, - /// A filesystem object is, unexpectedly, not a directory. - /// - /// For example, a filesystem path was specified where one of the intermediate directory - /// components was, in fact, a plain file. - #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] - NotADirectory, - /// The filesystem object is, unexpectedly, a directory. - /// - /// A directory was specified when a non-directory was expected. - #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] - IsADirectory, - /// A non-empty directory was specified where an empty directory was expected. - #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] - DirectoryNotEmpty, - /// The filesystem or storage medium is read-only, but a write operation was attempted. - #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] - ReadOnlyFilesystem, - /// Loop in the filesystem or IO subsystem; often, too many levels of symbolic links. - /// - /// There was a loop (or excessively long chain) resolving a filesystem object - /// or file IO object. - /// - /// On Unix this is usually the result of a symbolic link loop; or, of exceeding the - /// system-specific limit on the depth of symlink traversal. - #[unstable(feature = "io_error_more", issue = "86442")] - FilesystemLoop, - /// Stale network file handle. - /// - /// With some network filesystems, notably NFS, an open file (or directory) can be invalidated - /// by problems with the network or server. - #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] - StaleNetworkFileHandle, - /// A parameter was incorrect. - #[stable(feature = "rust1", since = "1.0.0")] - InvalidInput, - /// Data not valid for the operation were encountered. - /// - /// Unlike [`InvalidInput`], this typically means that the operation - /// parameters were valid, however the error was caused by malformed - /// input data. - /// - /// For example, a function that reads a file into a string will error with - /// `InvalidData` if the file's contents are not valid UTF-8. - /// - /// [`InvalidInput`]: ErrorKind::InvalidInput - #[stable(feature = "io_invalid_data", since = "1.2.0")] - InvalidData, - /// The I/O operation's timeout expired, causing it to be canceled. - #[stable(feature = "rust1", since = "1.0.0")] - TimedOut, - /// An error returned when an operation could not be completed because a - /// call to [`write`] returned [`Ok(0)`]. - /// - /// This typically means that an operation could only succeed if it wrote a - /// particular number of bytes but only a smaller number of bytes could be - /// written. - /// - /// [`write`]: crate::io::Write::write - /// [`Ok(0)`]: Ok - #[stable(feature = "rust1", since = "1.0.0")] - WriteZero, - /// The underlying storage (typically, a filesystem) is full. - /// - /// This does not include out of quota errors. - #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] - StorageFull, - /// Seek on unseekable file. - /// - /// Seeking was attempted on an open file handle which is not suitable for seeking - for - /// example, on Unix, a named pipe opened with `File::open`. - #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] - NotSeekable, - /// Filesystem quota or some other kind of quota was exceeded. - #[stable(feature = "io_error_quota_exceeded", since = "1.85.0")] - QuotaExceeded, - /// File larger than allowed or supported. - /// - /// This might arise from a hard limit of the underlying filesystem or file access API, or from - /// an administratively imposed resource limitation. Simple disk full, and out of quota, have - /// their own errors. - #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] - FileTooLarge, - /// Resource is busy. - #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] - ResourceBusy, - /// Executable file is busy. - /// - /// An attempt was made to write to a file which is also in use as a running program. (Not all - /// operating systems detect this situation.) - #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] - ExecutableFileBusy, - /// Deadlock (avoided). - /// - /// A file locking operation would result in deadlock. This situation is typically detected, if - /// at all, on a best-effort basis. - #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] - Deadlock, - /// Cross-device or cross-filesystem (hard) link or rename. - #[stable(feature = "io_error_crosses_devices", since = "1.85.0")] - CrossesDevices, - /// Too many (hard) links to the same filesystem object. - /// - /// The filesystem does not support making so many hardlinks to the same file. - #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] - TooManyLinks, - /// A filename was invalid. - /// - /// This error can also occur if a length limit for a name was exceeded. - #[stable(feature = "io_error_invalid_filename", since = "1.87.0")] - InvalidFilename, - /// Program argument list too long. - /// - /// When trying to run an external program, a system or process limit on the size of the - /// arguments would have been exceeded. - #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] - ArgumentListTooLong, - /// This operation was interrupted. - /// - /// Interrupted operations can typically be retried. - #[stable(feature = "rust1", since = "1.0.0")] - Interrupted, - - /// This operation is unsupported on this platform. - /// - /// This means that the operation can never succeed. - #[stable(feature = "unsupported_error", since = "1.53.0")] - Unsupported, - - // ErrorKinds which are primarily categorisations for OS error - // codes should be added above. - // - /// An error returned when an operation could not be completed because an - /// "end of file" was reached prematurely. - /// - /// This typically means that an operation could only succeed if it read a - /// particular number of bytes but only a smaller number of bytes could be - /// read. - #[stable(feature = "read_exact", since = "1.6.0")] - UnexpectedEof, - - /// An operation could not be completed, because it failed - /// to allocate enough memory. - #[stable(feature = "out_of_memory_error", since = "1.54.0")] - OutOfMemory, - - /// The operation was partially successful and needs to be checked - /// later on due to not blocking. - #[unstable(feature = "io_error_inprogress", issue = "130840")] - InProgress, - - // "Unusual" error kinds which do not correspond simply to (sets - // of) OS error codes, should be added just above this comment. - // `Other` and `Uncategorized` should remain at the end: - // - /// A custom error that does not fall under any other I/O error kind. - /// - /// This can be used to construct your own [`Error`]s that do not match any - /// [`ErrorKind`]. - /// - /// This [`ErrorKind`] is not used by the standard library. - /// - /// Errors from the standard library that do not fall under any of the I/O - /// error kinds cannot be `match`ed on, and will only match a wildcard (`_`) pattern. - /// New [`ErrorKind`]s might be added in the future for some of those. - #[stable(feature = "rust1", since = "1.0.0")] - Other, - - /// Any I/O error from the standard library that's not part of this list. - /// - /// Errors that are `Uncategorized` now may move to a different or a new - /// [`ErrorKind`] variant in the future. It is not recommended to match - /// an error against `Uncategorized`; use a wildcard match (`_`) instead. - #[unstable(feature = "io_error_uncategorized", issue = "none")] - #[doc(hidden)] - Uncategorized, -} - -impl ErrorKind { - pub(crate) fn as_str(&self) -> &'static str { - use ErrorKind::*; - match *self { - // tidy-alphabetical-start - AddrInUse => "address in use", - AddrNotAvailable => "address not available", - AlreadyExists => "entity already exists", - ArgumentListTooLong => "argument list too long", - BrokenPipe => "broken pipe", - ConnectionAborted => "connection aborted", - ConnectionRefused => "connection refused", - ConnectionReset => "connection reset", - CrossesDevices => "cross-device link or rename", - Deadlock => "deadlock", - DirectoryNotEmpty => "directory not empty", - ExecutableFileBusy => "executable file busy", - FileTooLarge => "file too large", - FilesystemLoop => "filesystem loop or indirection limit (e.g. symlink loop)", - HostUnreachable => "host unreachable", - InProgress => "in progress", - Interrupted => "operation interrupted", - InvalidData => "invalid data", - InvalidFilename => "invalid filename", - InvalidInput => "invalid input parameter", - IsADirectory => "is a directory", - NetworkDown => "network down", - NetworkUnreachable => "network unreachable", - NotADirectory => "not a directory", - NotConnected => "not connected", - NotFound => "entity not found", - NotSeekable => "seek on unseekable file", - Other => "other error", - OutOfMemory => "out of memory", - PermissionDenied => "permission denied", - QuotaExceeded => "quota exceeded", - ReadOnlyFilesystem => "read-only filesystem or storage medium", - ResourceBusy => "resource busy", - StaleNetworkFileHandle => "stale network file handle", - StorageFull => "no storage space", - TimedOut => "timed out", - TooManyLinks => "too many links", - Uncategorized => "uncategorized error", - UnexpectedEof => "unexpected end of file", - Unsupported => "unsupported", - WouldBlock => "operation would block", - WriteZero => "write zero", - // tidy-alphabetical-end - } - } -} - -#[stable(feature = "io_errorkind_display", since = "1.60.0")] -impl fmt::Display for ErrorKind { - /// Shows a human-readable description of the `ErrorKind`. - /// - /// This is similar to `impl Display for Error`, but doesn't require first converting to Error. - /// - /// # Examples - /// ``` - /// use std::io::ErrorKind; - /// assert_eq!("entity not found", ErrorKind::NotFound.to_string()); - /// ``` - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt.write_str(self.as_str()) - } -} - /// Intended for use for errors not exposed to the user, where allocating onto /// the heap (for normal construction via Error::new) is too costly. #[stable(feature = "io_error_from_errorkind", since = "1.14.0")] +#[cfg(bootstrap)] impl From for Error { /// Converts an [`ErrorKind`] into an [`Error`]. /// @@ -546,6 +217,7 @@ impl From for Error { } } +#[cfg(bootstrap)] impl Error { /// Creates a new I/O error from a known kind of error as well as an /// arbitrary error payload. @@ -1022,6 +694,7 @@ impl Error { } } +#[cfg(bootstrap)] impl fmt::Debug for Repr { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { match self.data() { @@ -1043,6 +716,7 @@ impl fmt::Debug for Repr { } #[stable(feature = "rust1", since = "1.0.0")] +#[cfg(bootstrap)] impl fmt::Display for Error { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { match self.repr.data() { @@ -1058,6 +732,7 @@ impl fmt::Display for Error { } #[stable(feature = "rust1", since = "1.0.0")] +#[cfg(bootstrap)] impl error::Error for Error { #[allow(deprecated)] fn cause(&self) -> Option<&dyn error::Error> { @@ -1079,7 +754,99 @@ impl error::Error for Error { } } +#[cfg(bootstrap)] fn _assert_error_is_sync_send() { fn _is_sync_send() {} _is_sync_send::(); } + +#[cfg(not(any(bootstrap, test)))] +unsafe fn set_std_vtable() { + static STD_VTABLE: StdVTable = StdVTable { + decode_error_kind: sys::io::decode_error_kind, + error_string: |code| ErrorString::from(sys::io::error_string(code)), + }; + unsafe { + STD_VTABLE.install(); + } +} + +#[cfg(not(any(bootstrap, test)))] +impl Error { + /// Returns an error representing the last OS error which occurred. + /// + /// This function reads the value of `errno` for the target platform (e.g. + /// `GetLastError` on Windows) and will return a corresponding instance of + /// [`Error`] for the error code. + /// + /// This should be called immediately after a call to a platform function, + /// otherwise the state of the error value is indeterminate. In particular, + /// other standard library functions may call platform functions that may + /// (or may not) reset the error value even if they succeed. + /// + /// # Examples + /// + /// ``` + /// use std::io::Error; + /// + /// let os_error = Error::last_os_error(); + /// println!("last OS error: {os_error:?}"); + /// ``` + #[stable(feature = "rust1", since = "1.0.0")] + #[doc(alias = "GetLastError")] + #[doc(alias = "errno")] + #[must_use] + #[inline] + #[rustc_allow_incoherent_impl] + pub fn last_os_error() -> Error { + Error::from_raw_os_error(sys::io::errno()) + } + + /// Creates a new instance of an [`Error`] from a particular OS error code. + /// + /// # Examples + /// + /// On Linux: + /// + /// ``` + /// # if cfg!(target_os = "linux") { + /// use std::io; + /// + /// let error = io::Error::from_raw_os_error(22); + /// assert_eq!(error.kind(), io::ErrorKind::InvalidInput); + /// # } + /// ``` + /// + /// On Windows: + /// + /// ``` + /// # if cfg!(windows) { + /// use std::io; + /// + /// let error = io::Error::from_raw_os_error(10022); + /// assert_eq!(error.kind(), io::ErrorKind::InvalidInput); + /// # } + /// ``` + #[stable(feature = "rust1", since = "1.0.0")] + #[must_use] + #[inline] + #[rustc_allow_incoherent_impl] + pub fn from_raw_os_error(code: RawOsError) -> Error { + unsafe { + set_std_vtable(); + Self::from_raw_os_error_impl(code) + } + } + + /// implementation detail of [`Error`] + #[inline] + #[rustc_allow_incoherent_impl] + #[unstable(feature = "core_io_error_internals", issue = "none")] + pub fn is_interrupted(&self) -> bool { + if let Some(code) = self.raw_os_error() { + sys::io::is_interrupted(code) + } else { + self.kind() == ErrorKind::Interrupted + } + } +} diff --git a/library/std/src/io/error/repr_bitpacked.rs b/library/std/src/io/error/repr_bitpacked.rs index 7353816a8171b..880479cd54a91 100644 --- a/library/std/src/io/error/repr_bitpacked.rs +++ b/library/std/src/io/error/repr_bitpacked.rs @@ -102,6 +102,7 @@ //! to use a pointer type to store something that may hold an integer, some of //! the time. +use core::io::error_internals::kind_from_prim; use core::marker::PhantomData; use core::num::NonZeroUsize; use core::ptr::NonNull; @@ -283,69 +284,6 @@ where } } -// This compiles to the same code as the check+transmute, but doesn't require -// unsafe, or to hard-code max ErrorKind or its size in a way the compiler -// couldn't verify. -#[inline] -fn kind_from_prim(ek: u32) -> Option { - macro_rules! from_prim { - ($prim:expr => $Enum:ident { $($Variant:ident),* $(,)? }) => {{ - // Force a compile error if the list gets out of date. - const _: fn(e: $Enum) = |e: $Enum| match e { - $($Enum::$Variant => ()),* - }; - match $prim { - $(v if v == ($Enum::$Variant as _) => Some($Enum::$Variant),)* - _ => None, - } - }} - } - from_prim!(ek => ErrorKind { - NotFound, - PermissionDenied, - ConnectionRefused, - ConnectionReset, - HostUnreachable, - NetworkUnreachable, - ConnectionAborted, - NotConnected, - AddrInUse, - AddrNotAvailable, - NetworkDown, - BrokenPipe, - AlreadyExists, - WouldBlock, - NotADirectory, - IsADirectory, - DirectoryNotEmpty, - ReadOnlyFilesystem, - FilesystemLoop, - StaleNetworkFileHandle, - InvalidInput, - InvalidData, - TimedOut, - WriteZero, - StorageFull, - NotSeekable, - QuotaExceeded, - FileTooLarge, - ResourceBusy, - ExecutableFileBusy, - Deadlock, - CrossesDevices, - TooManyLinks, - InvalidFilename, - ArgumentListTooLong, - Interrupted, - Other, - UnexpectedEof, - Unsupported, - OutOfMemory, - InProgress, - Uncategorized, - }) -} - // Some static checking to alert us if a change breaks any of the assumptions // that our encoding relies on for correctness and soundness. (Some of these are // a bit overly thorough/cautious, admittedly) diff --git a/library/std/src/io/error/tests.rs b/library/std/src/io/error/tests.rs index a8eef06381dae..7196fba619ecb 100644 --- a/library/std/src/io/error/tests.rs +++ b/library/std/src/io/error/tests.rs @@ -1,23 +1,12 @@ -use super::{Custom, Error, ErrorData, ErrorKind, Repr, SimpleMessage, const_error}; +use super::{Error, ErrorKind}; use crate::sys::io::{decode_error_kind, error_string}; -use crate::{assert_matches, error, fmt}; - -#[test] -fn test_size() { - assert!(size_of::() <= size_of::<[usize; 2]>()); -} #[test] fn test_debug_error() { let code = 6; let msg = error_string(code); let kind = decode_error_kind(code); - let err = Error { - repr: Repr::new_custom(Box::new(Custom { - kind: ErrorKind::InvalidInput, - error: Box::new(Error { repr: super::Repr::new_os(code) }), - })), - }; + let err = Error::new(ErrorKind::InvalidInput, Error::from_raw_os_error(code)); let expected = format!( "Custom {{ \ kind: InvalidInput, \ @@ -31,161 +20,3 @@ fn test_debug_error() { ); assert_eq!(format!("{err:?}"), expected); } - -#[test] -fn test_downcasting() { - #[derive(Debug)] - struct TestError; - - impl fmt::Display for TestError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("asdf") - } - } - - impl error::Error for TestError {} - - // we have to call all of these UFCS style right now since method - // resolution won't implicitly drop the Send+Sync bounds - let mut err = Error::new(ErrorKind::Other, TestError); - assert!(err.get_ref().unwrap().is::()); - assert_eq!("asdf", err.get_ref().unwrap().to_string()); - assert!(err.get_mut().unwrap().is::()); - let extracted = err.into_inner().unwrap(); - extracted.downcast::().unwrap(); -} - -#[test] -fn test_const() { - const E: Error = const_error!(ErrorKind::NotFound, "hello"); - - assert_eq!(E.kind(), ErrorKind::NotFound); - assert_eq!(E.to_string(), "hello"); - assert!(format!("{E:?}").contains("\"hello\"")); - assert!(format!("{E:?}").contains("NotFound")); -} - -#[test] -fn test_os_packing() { - for code in -20..20 { - let e = Error::from_raw_os_error(code); - assert_eq!(e.raw_os_error(), Some(code)); - assert_matches!( - e.repr.data(), - ErrorData::Os(c) if c == code, - ); - } -} - -#[test] -fn test_errorkind_packing() { - assert_eq!(Error::from(ErrorKind::NotFound).kind(), ErrorKind::NotFound); - assert_eq!(Error::from(ErrorKind::PermissionDenied).kind(), ErrorKind::PermissionDenied); - assert_eq!(Error::from(ErrorKind::Uncategorized).kind(), ErrorKind::Uncategorized); - // Check that the innards look like what we want. - assert_matches!( - Error::from(ErrorKind::OutOfMemory).repr.data(), - ErrorData::Simple(ErrorKind::OutOfMemory), - ); -} - -#[test] -fn test_simple_message_packing() { - use super::ErrorKind::*; - use super::SimpleMessage; - macro_rules! check_simple_msg { - ($err:expr, $kind:ident, $msg:literal) => {{ - let e = &$err; - // Check that the public api is right. - assert_eq!(e.kind(), $kind); - assert!(format!("{e:?}").contains($msg)); - // and we got what we expected - assert_matches!( - e.repr.data(), - ErrorData::SimpleMessage(SimpleMessage { kind: $kind, message: $msg }) - ); - }}; - } - - let not_static = const_error!(Uncategorized, "not a constant!"); - check_simple_msg!(not_static, Uncategorized, "not a constant!"); - - const CONST: Error = const_error!(NotFound, "definitely a constant!"); - check_simple_msg!(CONST, NotFound, "definitely a constant!"); - - static STATIC: Error = const_error!(BrokenPipe, "a constant, sort of!"); - check_simple_msg!(STATIC, BrokenPipe, "a constant, sort of!"); -} - -#[derive(Debug, PartialEq)] -struct Bojji(bool); -impl error::Error for Bojji {} -impl fmt::Display for Bojji { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "ah! {:?}", self) - } -} - -#[test] -fn test_custom_error_packing() { - use super::Custom; - let test = Error::new(ErrorKind::Uncategorized, Bojji(true)); - assert_matches!( - test.repr.data(), - ErrorData::Custom(Custom { - kind: ErrorKind::Uncategorized, - error, - }) if error.downcast_ref::().as_deref() == Some(&Bojji(true)), - ); -} - -#[derive(Debug)] -struct E; - -impl fmt::Display for E { - fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { - Ok(()) - } -} - -impl error::Error for E {} - -#[test] -fn test_std_io_error_downcast() { - // Case 1: custom error, downcast succeeds - let io_error = Error::new(ErrorKind::Other, Bojji(true)); - let e: Bojji = io_error.downcast().unwrap(); - assert!(e.0); - - // Case 2: custom error, downcast fails - let io_error = Error::new(ErrorKind::Other, Bojji(true)); - let io_error = io_error.downcast::().unwrap_err(); - - // ensures that the custom error is intact - assert_eq!(ErrorKind::Other, io_error.kind()); - let e: Bojji = io_error.downcast().unwrap(); - assert!(e.0); - - // Case 3: os error - let errno = 20; - let io_error = Error::from_raw_os_error(errno); - let io_error = io_error.downcast::().unwrap_err(); - - assert_eq!(errno, io_error.raw_os_error().unwrap()); - - // Case 4: simple - let kind = ErrorKind::OutOfMemory; - let io_error: Error = kind.into(); - let io_error = io_error.downcast::().unwrap_err(); - - assert_eq!(kind, io_error.kind()); - - // Case 5: simple message - const SIMPLE_MESSAGE: SimpleMessage = - SimpleMessage { kind: ErrorKind::Other, message: "simple message error test" }; - let io_error = Error::from_static_message(&SIMPLE_MESSAGE); - let io_error = io_error.downcast::().unwrap_err(); - - assert_eq!(SIMPLE_MESSAGE.kind, io_error.kind()); - assert_eq!(SIMPLE_MESSAGE.message, format!("{io_error}")); -} diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index 623c34c6d2910..8d9d91fccfbed 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -297,19 +297,36 @@ #[cfg(test)] mod tests; +#[stable(feature = "rust1", since = "1.0.0")] +pub use alloc::io::ErrorKind; +#[stable(feature = "rust1", since = "1.0.0")] +#[cfg(not(bootstrap))] +pub use alloc::io::{Error, Result}; +#[unstable(feature = "raw_os_error_ty", issue = "107792")] +pub use core::io::RawOsError; +#[cfg(not(bootstrap))] +#[unstable(feature = "io_const_error", issue = "133448")] +pub use core::io::const_error; +#[cfg(not(bootstrap))] +#[doc(hidden)] +#[unstable(feature = "io_const_error_internals", issue = "none")] +pub use core::io::error_internals::SimpleMessage; #[unstable(feature = "read_buf", issue = "78485")] pub use core::io::{BorrowedBuf, BorrowedCursor}; use core::slice::memchr; #[stable(feature = "bufwriter_into_parts", since = "1.56.0")] pub use self::buffered::WriterPanicked; -#[unstable(feature = "raw_os_error_ty", issue = "107792")] -pub use self::error::RawOsError; +#[cfg(bootstrap)] #[doc(hidden)] #[unstable(feature = "io_const_error_internals", issue = "none")] pub use self::error::SimpleMessage; +#[cfg(bootstrap)] #[unstable(feature = "io_const_error", issue = "133448")] pub use self::error::const_error; +#[cfg(bootstrap)] +#[stable(feature = "rust1", since = "1.0.0")] +pub use self::error::{Error, Result}; #[stable(feature = "anonymous_pipe", since = "1.87.0")] pub use self::pipe::{PipeReader, PipeWriter, pipe}; #[stable(feature = "is_terminal", since = "1.70.0")] @@ -326,7 +343,6 @@ pub use self::{ buffered::{BufReader, BufWriter, IntoInnerError, LineWriter}, copy::copy, cursor::Cursor, - error::{Error, ErrorKind, Result}, stdio::{Stderr, StderrLock, Stdin, StdinLock, Stdout, StdoutLock, stderr, stdin, stdout}, util::{Empty, Repeat, Sink, empty, repeat, sink}, }; diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 6fcb28edc7d84..5c49016b55254 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -319,6 +319,7 @@ // // Library features (core): // tidy-alphabetical-start +#![feature(alloc_io)] #![feature(bstr)] #![feature(bstr_internals)] #![feature(cast_maybe_uninit)] @@ -326,7 +327,10 @@ #![feature(clone_to_uninit)] #![feature(const_convert)] #![feature(core_intrinsics)] +#![feature(core_io)] #![feature(core_io_borrowed_buf)] +#![feature(core_io_error)] +#![feature(core_io_error_internals)] #![feature(cstr_display)] #![feature(drop_guard)] #![feature(duration_constants)] @@ -345,6 +349,10 @@ #![feature(hashmap_internals)] #![feature(hint_must_use)] #![feature(int_from_ascii)] +#![feature(io_const_error_internals)] +#![feature(io_error_inprogress)] +#![feature(io_error_more)] +#![feature(io_error_uncategorized)] #![feature(ip)] #![feature(maybe_uninit_array_assume_init)] #![feature(panic_can_unwind)] @@ -355,6 +363,7 @@ #![feature(ptr_as_uninit)] #![feature(ptr_mask)] #![feature(random)] +#![feature(raw_os_error_ty)] #![feature(slice_internals)] #![feature(slice_ptr_get)] #![feature(slice_range)] diff --git a/library/std/src/sys/io/error/generic.rs b/library/std/src/sys/io/error/generic.rs index fc70fbaba7e8c..a9ac616f954b2 100644 --- a/library/std/src/sys/io/error/generic.rs +++ b/library/std/src/sys/io/error/generic.rs @@ -2,6 +2,7 @@ pub fn errno() -> i32 { 0 } +#[cfg(any(bootstrap, not(test)))] pub fn is_interrupted(_code: i32) -> bool { false } diff --git a/library/std/src/sys/io/error/hermit.rs b/library/std/src/sys/io/error/hermit.rs index 5f42144bb7cfb..785da48acf427 100644 --- a/library/std/src/sys/io/error/hermit.rs +++ b/library/std/src/sys/io/error/hermit.rs @@ -5,6 +5,7 @@ pub fn errno() -> i32 { } #[inline] +#[cfg(any(bootstrap, not(test)))] pub fn is_interrupted(errno: i32) -> bool { errno == hermit_abi::errno::EINTR } diff --git a/library/std/src/sys/io/error/mod.rs b/library/std/src/sys/io/error/mod.rs index d7a0b9b4b301d..4fca658a7dcaa 100644 --- a/library/std/src/sys/io/error/mod.rs +++ b/library/std/src/sys/io/error/mod.rs @@ -48,8 +48,3 @@ cfg_select! { pub use generic::*; } } - -pub type RawOsError = cfg_select! { - target_os = "uefi" => usize, - _ => i32, -}; diff --git a/library/std/src/sys/io/error/motor.rs b/library/std/src/sys/io/error/motor.rs index 7d612d817cdd7..be27232660c5d 100644 --- a/library/std/src/sys/io/error/motor.rs +++ b/library/std/src/sys/io/error/motor.rs @@ -20,6 +20,7 @@ pub fn errno() -> RawOsError { error_code.into() } +#[cfg(any(bootstrap, not(test)))] pub fn is_interrupted(_code: io::RawOsError) -> bool { false // Motor OS doesn't have signals. } diff --git a/library/std/src/sys/io/error/sgx.rs b/library/std/src/sys/io/error/sgx.rs index 8b3e08b0b661b..d18b664064e11 100644 --- a/library/std/src/sys/io/error/sgx.rs +++ b/library/std/src/sys/io/error/sgx.rs @@ -7,6 +7,7 @@ pub fn errno() -> i32 { } #[inline] +#[cfg(any(bootstrap, not(test)))] pub fn is_interrupted(code: i32) -> bool { code == fortanix_sgx_abi::Error::Interrupted as _ } diff --git a/library/std/src/sys/io/error/solid.rs b/library/std/src/sys/io/error/solid.rs index 8e9503272abbc..0fb61eb885f82 100644 --- a/library/std/src/sys/io/error/solid.rs +++ b/library/std/src/sys/io/error/solid.rs @@ -6,6 +6,7 @@ pub fn errno() -> i32 { } #[inline] +#[cfg(any(bootstrap, not(test)))] pub fn is_interrupted(code: i32) -> bool { crate::sys::net::is_interrupted(code) } diff --git a/library/std/src/sys/io/error/teeos.rs b/library/std/src/sys/io/error/teeos.rs index 18826ffc3894f..3122e7abad9b9 100644 --- a/library/std/src/sys/io/error/teeos.rs +++ b/library/std/src/sys/io/error/teeos.rs @@ -5,6 +5,7 @@ pub fn errno() -> i32 { } #[inline] +#[cfg(any(bootstrap, not(test)))] pub fn is_interrupted(errno: i32) -> bool { errno == libc::EINTR } diff --git a/library/std/src/sys/io/error/unix.rs b/library/std/src/sys/io/error/unix.rs index b10343b2752c2..783101e238534 100644 --- a/library/std/src/sys/io/error/unix.rs +++ b/library/std/src/sys/io/error/unix.rs @@ -94,6 +94,7 @@ pub fn set_errno(e: i32) { } #[inline] +#[cfg(any(bootstrap, not(test)))] pub fn is_interrupted(errno: i32) -> bool { errno == libc::EINTR } diff --git a/library/std/src/sys/io/error/wasi.rs b/library/std/src/sys/io/error/wasi.rs index 719fc0c900adc..c241a409027c0 100644 --- a/library/std/src/sys/io/error/wasi.rs +++ b/library/std/src/sys/io/error/wasi.rs @@ -18,6 +18,7 @@ pub fn set_errno(val: i32) { } #[inline] +#[cfg(any(bootstrap, not(test)))] pub fn is_interrupted(errno: i32) -> bool { errno == libc::EINTR } diff --git a/library/std/src/sys/io/error/windows.rs b/library/std/src/sys/io/error/windows.rs index d7607082a30a0..22fdb9a2d0820 100644 --- a/library/std/src/sys/io/error/windows.rs +++ b/library/std/src/sys/io/error/windows.rs @@ -6,6 +6,7 @@ pub fn errno() -> i32 { } #[inline] +#[cfg(any(bootstrap, not(test)))] pub fn is_interrupted(_errno: i32) -> bool { false } diff --git a/library/std/src/sys/io/error/xous.rs b/library/std/src/sys/io/error/xous.rs index 2e9ea8e4f0928..912072128c850 100644 --- a/library/std/src/sys/io/error/xous.rs +++ b/library/std/src/sys/io/error/xous.rs @@ -4,6 +4,7 @@ pub fn errno() -> i32 { 0 } +#[cfg(any(bootstrap, not(test)))] pub fn is_interrupted(_code: i32) -> bool { false } diff --git a/library/std/src/sys/io/mod.rs b/library/std/src/sys/io/mod.rs index b3587ab63696a..f5b753dd81fa1 100644 --- a/library/std/src/sys/io/mod.rs +++ b/library/std/src/sys/io/mod.rs @@ -56,13 +56,15 @@ mod kernel_copy; not(any(target_os = "dragonfly", target_os = "vxworks", target_os = "rtems")) ))] pub use error::errno_location; +#[cfg(any(bootstrap, not(test)))] +pub use error::is_interrupted; #[cfg_attr(not(target_os = "linux"), allow(unused_imports))] #[cfg(any( all(target_family = "unix", not(any(target_os = "vxworks", target_os = "rtems"))), target_os = "wasi", ))] pub use error::set_errno; -pub use error::{RawOsError, decode_error_kind, errno, error_string, is_interrupted}; +pub use error::{decode_error_kind, errno, error_string}; pub use io_slice::{IoSlice, IoSliceMut}; pub use is_terminal::is_terminal; pub use kernel_copy::{CopyState, kernel_copy}; diff --git a/library/std/src/sys/net/connection/socket/solid.rs b/library/std/src/sys/net/connection/socket/solid.rs index e20a3fbb76b72..09607cb1726a7 100644 --- a/library/std/src/sys/net/connection/socket/solid.rs +++ b/library/std/src/sys/net/connection/socket/solid.rs @@ -81,6 +81,7 @@ pub fn error_name(er: abi::ER) -> Option<&'static str> { } #[inline] +#[cfg(any(bootstrap, not(test)))] pub fn is_interrupted(er: abi::ER) -> bool { er == netc::SOLID_NET_ERR_BASE - libc::EINTR } diff --git a/library/std/src/sys/pal/itron/error.rs b/library/std/src/sys/pal/itron/error.rs index 87be7d5b3546e..94412efbbfd65 100644 --- a/library/std/src/sys/pal/itron/error.rs +++ b/library/std/src/sys/pal/itron/error.rs @@ -79,6 +79,7 @@ pub fn error_name(er: abi::ER) -> Option<&'static str> { } #[inline] +#[cfg(any(bootstrap, not(test)))] pub fn is_interrupted(er: abi::ER) -> bool { er == abi::E_RLWAI } diff --git a/src/tools/tidy/src/pal.rs b/src/tools/tidy/src/pal.rs index f1ac90ba0bc5e..2a75fbba2deb3 100644 --- a/src/tools/tidy/src/pal.rs +++ b/src/tools/tidy/src/pal.rs @@ -56,6 +56,8 @@ const EXCEPTION_PATHS: &[&str] = &[ "library/core/src/os", // Platform-specific public interfaces "library/std/src/sys", // Platform-specific code for std lives here. "library/std/src/os", // Platform-specific public interfaces + // need platform-specific code to select correct RawOsError type and Repr mod. + "library/core/src/io/error.rs", // Temporary `std` exceptions // FIXME: platform-specific code should be moved to `sys` "library/std/src/io/stdio.rs", diff --git a/tests/ui/binop/binary-op-not-allowed-issue-125631.stderr b/tests/ui/binop/binary-op-not-allowed-issue-125631.stderr index a997fbee1f2a0..ad8802bf1370f 100644 --- a/tests/ui/binop/binary-op-not-allowed-issue-125631.stderr +++ b/tests/ui/binop/binary-op-not-allowed-issue-125631.stderr @@ -12,7 +12,7 @@ note: an implementation of `PartialEq` might be missing for `T1` LL | struct T1; | ^^^^^^^^^ must implement `PartialEq` note: `std::io::Error` does not implement `PartialEq` - --> $SRC_DIR/std/src/io/error.rs:LL:COL + --> $SRC_DIR/core/src/io/error.rs:LL:COL | = note: `std::io::Error` is defined in another crate help: consider annotating `T1` with `#[derive(PartialEq)]` @@ -34,7 +34,7 @@ note: `Thread` does not implement `PartialEq` | = note: `Thread` is defined in another crate note: `std::io::Error` does not implement `PartialEq` - --> $SRC_DIR/std/src/io/error.rs:LL:COL + --> $SRC_DIR/core/src/io/error.rs:LL:COL | = note: `std::io::Error` is defined in another crate @@ -58,7 +58,7 @@ note: `Thread` does not implement `PartialEq` | = note: `Thread` is defined in another crate note: `std::io::Error` does not implement `PartialEq` - --> $SRC_DIR/std/src/io/error.rs:LL:COL + --> $SRC_DIR/core/src/io/error.rs:LL:COL | = note: `std::io::Error` is defined in another crate help: consider annotating `T1` with `#[derive(PartialEq)]` From 6b92a0a91f211ee02e1cd8c84798862012261fc6 Mon Sep 17 00:00:00 2001 From: Max Heller Date: Sun, 15 Mar 2026 12:28:17 -0400 Subject: [PATCH 2/2] Replace io::Error vtables with extern functions --- library/alloc/src/io/error.rs | 33 ++--- library/alloc/src/io/mod.rs | 1 - library/core/src/io/error.rs | 146 +++++++------------- library/core/src/io/error/repr_bitpacked.rs | 9 -- library/core/src/io/mod.rs | 4 - library/coretests/tests/io/error.rs | 4 +- library/coretests/tests/lib.rs | 1 - library/std/src/io/error.rs | 31 ++--- 8 files changed, 85 insertions(+), 144 deletions(-) diff --git a/library/alloc/src/io/error.rs b/library/alloc/src/io/error.rs index 733e89f6a3649..95932087c5c48 100644 --- a/library/alloc/src/io/error.rs +++ b/library/alloc/src/io/error.rs @@ -1,25 +1,20 @@ -use core::alloc::Allocator; -use core::io::error_internals::{AllocVTable, Custom, ErrorBox, ErrorString}; +use core::alloc::{Allocator, Layout}; +use core::io::error_internals::{Custom, ErrorBox, ErrorString}; use core::io::{Error, ErrorKind, const_error}; -use core::{error, ptr, result}; +use core::ptr::{self, NonNull}; +use core::{error, result}; use crate::alloc::Global; use crate::boxed::Box; use crate::string::String; -unsafe fn set_alloc_vtable() { - static ALLOC_VTABLE: AllocVTable = - AllocVTable { deallocate: |ptr, layout| unsafe { Global.deallocate(ptr, layout) } }; - unsafe { - ALLOC_VTABLE.install(); - } +#[rustc_std_internal_symbol] +unsafe fn __rust_io_error_deallocate(ptr: NonNull, layout: Layout) { + unsafe { Global.deallocate(ptr, layout) } } fn into_error_box(value: Box) -> ErrorBox { - unsafe { - set_alloc_vtable(); - ErrorBox::from_raw(Box::into_raw(value)) - } + unsafe { ErrorBox::from_raw(Box::into_raw(value)) } } fn into_box(v: ErrorBox) -> Box { @@ -29,7 +24,6 @@ fn into_box(v: ErrorBox) -> Box { impl From for ErrorString { fn from(value: String) -> Self { unsafe { - set_alloc_vtable(); let (buf, length, capacity) = value.into_raw_parts(); ErrorString::from_raw_parts( ErrorBox::from_raw(ptr::slice_from_raw_parts_mut(buf.cast(), capacity)).into(), @@ -233,10 +227,17 @@ impl Error { /// ``` #[stable(feature = "io_error_downcast", since = "1.79.0")] #[rustc_allow_incoherent_impl] - pub fn downcast(self) -> result::Result, Self> + pub fn downcast(self) -> result::Result where E: error::Error + Send + Sync + 'static, { - self.downcast_impl::().map(|p| unsafe { Box::from_raw(p) }) + self.downcast_impl::().map(|p| { + // Safety: Guaranteed by downcast_impl::() + let Ok(err) = unsafe { Box::from_raw(p) }.downcast() else { + // Safety: downcast_impl::() guarantees that the pointer can be downcast to E + unsafe { core::hint::unreachable_unchecked() } + }; + *err + }) } } diff --git a/library/alloc/src/io/mod.rs b/library/alloc/src/io/mod.rs index d924e8a5d4613..b5dc0acb862a9 100644 --- a/library/alloc/src/io/mod.rs +++ b/library/alloc/src/io/mod.rs @@ -4,5 +4,4 @@ pub use core::io::*; -#[cfg(target_has_atomic_load_store = "ptr")] mod error; diff --git a/library/core/src/io/error.rs b/library/core/src/io/error.rs index 12a6198bc09e3..475ecb51bc6e3 100644 --- a/library/core/src/io/error.rs +++ b/library/core/src/io/error.rs @@ -17,7 +17,6 @@ use crate::alloc::Layout; use crate::mem::ManuallyDrop; use crate::ops::{Deref, DerefMut}; use crate::ptr::{self, NonNull, Unique}; -use crate::sync::atomic::{AtomicPtr, Ordering}; use crate::{error, fmt, mem, result, str}; /// implementation detail of [`Error`] @@ -58,8 +57,7 @@ impl fmt::Display for ErrorString { /// implementation detail of [`Error`] /// -/// Safety: `self.0` must point to allocated memory and -/// [`AllocVTable::install`] must have been run +/// Safety: `self.0` must point to allocated memory #[unstable(feature = "core_io_error_internals", issue = "none")] pub struct ErrorBox(Unique); @@ -94,8 +92,7 @@ impl DerefMut for ErrorBox { impl ErrorBox { /// implementation detail of [`Error`] /// - /// Safety: `v` must point to allocated memory and - /// [`AllocVTable::install`] must have been run + /// Safety: `v` must point to allocated memory pub unsafe fn from_raw(v: *mut T) -> Self { // SAFETY: guaranteed by caller unsafe { Self(Unique::new_unchecked(v)) } @@ -137,7 +134,7 @@ impl ErrorBoxMem { impl Drop for ErrorBoxMem { fn drop(&mut self) { // SAFETY: guaranteed safe by `ErrorBoxMem::new` - unsafe { (AllocVTable::get().deallocate)(self.ptr, self.layout) } + unsafe { _alloc::__rust_io_error_deallocate(self.ptr, self.layout) } } } @@ -151,75 +148,38 @@ impl Drop for ErrorBox { } } -/// implementation detail of [`Error`] -#[derive(Debug)] +/// implementation detail of [`Error`] in alloc #[unstable(feature = "core_io_error_internals", issue = "none")] -pub struct AllocVTable { - /// implementation detail of [`Error`] - /// - /// Safety: same as [`Allocator::deallocate`] - /// - /// [`Allocator::deallocate`]: core::alloc::Allocator::deallocate - pub deallocate: unsafe fn(ptr: NonNull, layout: Layout), -} - -static ALLOC_VTABLE: AtomicPtr = AtomicPtr::new(ptr::null_mut()); - -impl AllocVTable { - /// implementation detail of [`Error`] - /// - /// Safety: `self` must be in a `static` variable that has never been - /// written. All members must be valid. - pub unsafe fn install(&'static self) { - // see `get` for why `Relaxed` is sufficient. - if ALLOC_VTABLE.load(Ordering::Relaxed).is_null() { - ALLOC_VTABLE.store(<*const _>::from(self).cast_mut(), Ordering::Relaxed); - } - } - /// implementation detail of [`Error`] - /// - /// Safety: `install` must have been called - pub unsafe fn get() -> &'static Self { - // SAFETY: `install` has been called and it set `ALLOC_VTABLE` to point - // to a `static` variable that has never been written, so no writes can - // possibly need to be communicated through other memory so `Relaxed` - // here and in `install` are sufficient. - unsafe { &*ALLOC_VTABLE.load(Ordering::Relaxed) } +pub mod _alloc { + use core::alloc::Layout; + use core::ptr::NonNull; + + unsafe extern "Rust" { + /// implementation detail of `Error` + /// + /// Safety: same as [`Allocator::deallocate`] + /// + /// [`Allocator::deallocate`]: core::alloc::Allocator::deallocate + // #[lang = "io_error_deallocate"] + #[rustc_std_internal_symbol] + pub unsafe fn __rust_io_error_deallocate(ptr: NonNull, layout: Layout); } } -/// implementation detail of [`Error`] -#[derive(Debug)] +/// implementation detail of [`Error`] in std #[unstable(feature = "core_io_error_internals", issue = "none")] -pub struct StdVTable { - /// implementation detail of [`Error`] - pub decode_error_kind: fn(RawOsError) -> ErrorKind, - /// implementation detail of [`Error`] - pub error_string: fn(RawOsError) -> ErrorString, -} - -static STD_VTABLE: AtomicPtr = AtomicPtr::new(ptr::null_mut()); - -impl StdVTable { - /// implementation detail of [`Error`] - /// - /// Safety: `self` must be in a `static` variable that has never been - /// written. All members must be valid. - pub unsafe fn install(&'static self) { - // see `get` for why `Relaxed` is sufficient. - if STD_VTABLE.load(Ordering::Relaxed).is_null() { - STD_VTABLE.store(<*const _>::from(self).cast_mut(), Ordering::Relaxed); - } - } - /// implementation detail of [`Error`] - /// - /// Safety: `install` must have been called - pub unsafe fn get() -> &'static Self { - // SAFETY: `install` has been called and it set `STD_VTABLE` to point - // to a `static` variable that has never been written, so no writes can - // possibly need to be communicated through other memory so `Relaxed` - // here and in `install` are sufficient. - unsafe { &*STD_VTABLE.load(Ordering::Relaxed) } +pub mod _std { + use super::{ErrorKind, ErrorString, RawOsError}; + + unsafe extern "Rust" { + /// implementation detail of `Error` + // #[lang = "io_error_decode_error_kind"] + #[rustc_std_internal_symbol] + pub safe fn __rust_io_error_decode_error_kind(error: RawOsError) -> ErrorKind; + /// implementation detail of `Error` + // #[lang = "io_error_error_string"] + #[rustc_std_internal_symbol] + pub safe fn __rust_io_error_error_string(error: RawOsError) -> ErrorString; } } @@ -271,10 +231,6 @@ pub type Result = result::Result; /// [`Read`]: https://doc.rust-lang.org/std/io/trait.Read.html /// [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html /// [`Seek`]: https://doc.rust-lang.org/std/io/trait.Seek.html -// Safety: [`StdVTable::install`] must have been run before any `Error` -// instances containing OS errors are created. -// [`AllocVTable::install`] must have been run before any instances containing -// `AllocBox` are created. #[stable(feature = "rust1", since = "1.0.0")] #[rustc_has_incoherent_inherent_impls] pub struct Error { @@ -930,16 +886,26 @@ impl Error { /// implementation detail of [`Error::downcast`] /// + /// Returns an owned pointer to a valid allocation that can be downcast to `E`. + /// /// [`Error::downcast`]: https://doc.rust-lang.org/std/io/struct.Error.html#method.downcast #[doc(hidden)] #[unstable(feature = "core_io_error_internals", issue = "none")] - pub fn downcast_impl(self) -> result::Result<*mut E, Self> + pub fn downcast_impl(self) -> result::Result<*mut (dyn error::Error + Send + Sync), Self> where E: error::Error + Send + Sync + 'static, { - match self.repr.into_data() { - ErrorData::Custom(b) if b.error.is::() => Ok(b.into_inner().error.into_raw().cast()), - repr_data => Err(Self { repr: Repr::new(repr_data) }), + if let ErrorData::Custom(c) = self.repr.data() + && c.error.is::() + { + if let ErrorData::Custom(b) = self.repr.into_data() { + Ok(b.into_inner().error.into_raw()) + } else { + // Safety: We have just checked that the condition is true + unsafe { crate::hint::unreachable_unchecked() } + } + } else { + Err(self) } } @@ -974,9 +940,7 @@ impl Error { #[inline] pub fn kind(&self) -> ErrorKind { match self.repr.data() { - // SAFETY: `Error`'s safety invariant guarantees - // `StdVTable::install` has been run - ErrorData::Os(code) => unsafe { (StdVTable::get().decode_error_kind)(code) }, + ErrorData::Os(code) => _std::__rust_io_error_decode_error_kind(code), ErrorData::Custom(c) => c.kind, ErrorData::Simple(kind) => kind, ErrorData::SimpleMessage(m) => m.kind, @@ -987,16 +951,12 @@ impl Error { impl fmt::Debug for Repr { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { match self.data() { - ErrorData::Os(code) => { - // SAFETY: `Error`'s safety invariant guarantees - // `StdVTable::install` has been run - let std_vtable = unsafe { StdVTable::get() }; - fmt.debug_struct("Os") - .field("code", &code) - .field("kind", &(std_vtable.decode_error_kind)(code)) - .field("message", &(std_vtable.error_string)(code)) - .finish() - } + ErrorData::Os(code) => fmt + .debug_struct("Os") + .field("code", &code) + .field("kind", &_std::__rust_io_error_decode_error_kind(code)) + .field("message", &_std::__rust_io_error_error_string(code)) + .finish(), ErrorData::Custom(c) => fmt::Debug::fmt(&c, fmt), ErrorData::Simple(kind) => fmt.debug_tuple("Kind").field(&kind).finish(), ErrorData::SimpleMessage(msg) => fmt @@ -1013,9 +973,7 @@ impl fmt::Display for Error { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { match self.repr.data() { ErrorData::Os(code) => { - // SAFETY: `Error`'s safety invariant guarantees - // `StdVTable::install` has been run - let detail = unsafe { (StdVTable::get().error_string)(code) }; + let detail = _std::__rust_io_error_error_string(code); write!(fmt, "{detail} (os error {code})") } ErrorData::Custom(ref c) => c.error.fmt(fmt), diff --git a/library/core/src/io/error/repr_bitpacked.rs b/library/core/src/io/error/repr_bitpacked.rs index 45b0f0f489d70..dd2d3d9bd0aa5 100644 --- a/library/core/src/io/error/repr_bitpacked.rs +++ b/library/core/src/io/error/repr_bitpacked.rs @@ -133,15 +133,6 @@ unsafe impl Send for Repr {} unsafe impl Sync for Repr {} impl Repr { - pub(super) fn new(dat: ErrorData>) -> Self { - match dat { - ErrorData::Os(code) => Self::new_os(code), - ErrorData::Simple(kind) => Self::new_simple(kind), - ErrorData::SimpleMessage(simple_message) => Self::new_simple_message(simple_message), - ErrorData::Custom(b) => Self::new_custom(b), - } - } - pub(super) fn new_custom(b: ErrorBox) -> Self { let p = ErrorBox::into_raw(b).cast::(); // Should only be possible if an allocator handed out a pointer with diff --git a/library/core/src/io/mod.rs b/library/core/src/io/mod.rs index 7e7847f5e76c7..ee4880d24f010 100644 --- a/library/core/src/io/mod.rs +++ b/library/core/src/io/mod.rs @@ -7,7 +7,6 @@ mod borrowed_buf; #[unstable(feature = "core_io_borrowed_buf", issue = "117693")] pub use self::borrowed_buf::{BorrowedBuf, BorrowedCursor}; -#[cfg(target_has_atomic_load_store = "ptr")] mod error; #[unstable(feature = "raw_os_error_ty", issue = "107792")] @@ -15,14 +14,11 @@ pub use self::error::RawOsError; #[doc(hidden)] #[unstable(feature = "io_const_error_internals", issue = "none")] pub use self::error::SimpleMessage; -#[cfg(target_has_atomic_load_store = "ptr")] #[unstable(feature = "io_const_error", issue = "133448")] pub use self::error::const_error; #[unstable(feature = "core_io_error", issue = "none")] -#[cfg(target_has_atomic_load_store = "ptr")] pub use self::error::{Error, ErrorKind, Result}; -#[cfg(target_has_atomic_load_store = "ptr")] #[doc(hidden)] #[unstable(feature = "core_io_error_internals", issue = "none")] pub mod error_internals { diff --git a/library/coretests/tests/io/error.rs b/library/coretests/tests/io/error.rs index 090ed56693eb3..2df99edfa6549 100644 --- a/library/coretests/tests/io/error.rs +++ b/library/coretests/tests/io/error.rs @@ -87,7 +87,7 @@ impl error::Error for E {} fn test_std_io_error_downcast() { // Case 1: custom error, downcast succeeds let io_error = Error::new(ErrorKind::Other, Bojji(true)); - let e: Box = io_error.downcast().unwrap(); + let e = io_error.downcast::().unwrap(); assert!(e.0); // Case 2: custom error, downcast fails @@ -96,7 +96,7 @@ fn test_std_io_error_downcast() { // ensures that the custom error is intact assert_eq!(ErrorKind::Other, io_error.kind()); - let e: Box = io_error.downcast().unwrap(); + let e = io_error.downcast::().unwrap(); assert!(e.0); // Case 3: os error diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index c96f0d30a8758..68cf064ef9c7b 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -68,7 +68,6 @@ #![feature(int_roundings)] #![feature(io_const_error)] #![feature(io_const_error_internals)] -#![feature(io_error_downcast)] #![feature(io_error_uncategorized)] #![feature(ip)] #![feature(is_ascii_octdigit)] diff --git a/library/std/src/io/error.rs b/library/std/src/io/error.rs index 24f0ff5da7567..0a92fafb99433 100644 --- a/library/std/src/io/error.rs +++ b/library/std/src/io/error.rs @@ -18,8 +18,8 @@ use repr_bitpacked::Repr; #[cfg(bootstrap)] #[cfg(any(not(target_pointer_width = "64"), target_os = "uefi"))] mod repr_unpacked; -#[cfg(not(any(bootstrap, test)))] -use core::io::error_internals::{ErrorString, StdVTable}; +#[cfg(not(bootstrap))] +use core::io::error_internals::ErrorString; #[cfg(bootstrap)] use core::io::{ErrorKind, RawOsError}; @@ -27,11 +27,10 @@ use core::io::{ErrorKind, RawOsError}; #[cfg(any(not(target_pointer_width = "64"), target_os = "uefi"))] use repr_unpacked::Repr; -#[cfg(not(any(bootstrap, test)))] +#[cfg(not(bootstrap))] use super::RawOsError; #[cfg(not(bootstrap))] use super::{Error, ErrorKind}; -#[cfg(any(bootstrap, not(test)))] use crate::sys; #[cfg(bootstrap)] use crate::{error, fmt, result}; @@ -760,15 +759,16 @@ fn _assert_error_is_sync_send() { _is_sync_send::(); } -#[cfg(not(any(bootstrap, test)))] -unsafe fn set_std_vtable() { - static STD_VTABLE: StdVTable = StdVTable { - decode_error_kind: sys::io::decode_error_kind, - error_string: |code| ErrorString::from(sys::io::error_string(code)), - }; - unsafe { - STD_VTABLE.install(); - } +#[cfg(not(bootstrap))] +#[rustc_std_internal_symbol] +fn __rust_io_error_decode_error_kind(error: RawOsError) -> ErrorKind { + sys::io::decode_error_kind(error) +} + +#[cfg(not(bootstrap))] +#[rustc_std_internal_symbol] +fn __rust_io_error_error_string(error: RawOsError) -> ErrorString { + ErrorString::from(sys::io::error_string(error)) } #[cfg(not(any(bootstrap, test)))] @@ -832,10 +832,7 @@ impl Error { #[inline] #[rustc_allow_incoherent_impl] pub fn from_raw_os_error(code: RawOsError) -> Error { - unsafe { - set_std_vtable(); - Self::from_raw_os_error_impl(code) - } + unsafe { Self::from_raw_os_error_impl(code) } } /// implementation detail of [`Error`]