diff options
Diffstat (limited to 'library/std/src')
26 files changed, 544 insertions, 80 deletions
diff --git a/library/std/src/collections/hash/map.rs b/library/std/src/collections/hash/map.rs index 27f7191831d..ed32668456d 100644 --- a/library/std/src/collections/hash/map.rs +++ b/library/std/src/collections/hash/map.rs @@ -1,3 +1,5 @@ +// ignore-tidy-filelength + #[cfg(test)] mod tests; @@ -15,7 +17,7 @@ use crate::iter::{FromIterator, FusedIterator}; use crate::ops::Index; use crate::sys; -/// A hash map implemented with quadratic probing and SIMD lookup. +/// A [hash map] implemented with quadratic probing and SIMD lookup. /// /// By default, `HashMap` uses a hashing algorithm selected to provide /// resistance against HashDoS attacks. The algorithm is randomly seeded, and a @@ -60,6 +62,7 @@ use crate::sys; /// The original C++ version of SwissTable can be found [here], and this /// [CppCon talk] gives an overview of how the algorithm works. /// +/// [hash map]: crate::collections#use-a-hashmap-when /// [hashing algorithms available on crates.io]: https://crates.io/keywords/hasher /// [SwissTable]: https://abseil.io/blog/20180927-swisstables /// [here]: https://github.com/abseil/abseil-cpp/blob/master/absl/container/internal/raw_hash_set.h @@ -842,6 +845,37 @@ where self.base.insert(k, v) } + /// Tries to insert a key-value pair into the map, and returns + /// a mutable reference to the value in the entry. + /// + /// If the map already had this key present, nothing is updated, and + /// an error containing the occupied entry and the value is returned. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// #![feature(map_try_insert)] + /// + /// use std::collections::HashMap; + /// + /// let mut map = HashMap::new(); + /// assert_eq!(map.try_insert(37, "a").unwrap(), &"a"); + /// + /// let err = map.try_insert(37, "b").unwrap_err(); + /// assert_eq!(err.entry.key(), &37); + /// assert_eq!(err.entry.get(), &"a"); + /// assert_eq!(err.value, "b"); + /// ``` + #[unstable(feature = "map_try_insert", issue = "82766")] + pub fn try_insert(&mut self, key: K, value: V) -> Result<&mut V, OccupiedError<'_, K, V>> { + match self.entry(key) { + Occupied(entry) => Err(OccupiedError { entry, value }), + Vacant(entry) => Ok(entry.insert(value)), + } + } + /// Removes a key from the map, returning the value at the key if the key /// was previously in the map. /// @@ -1851,6 +1885,41 @@ impl<K: Debug, V> Debug for VacantEntry<'_, K, V> { } } +/// The error returned by [`try_insert`](HashMap::try_insert) when the key already exists. +/// +/// Contains the occupied entry, and the value that was not inserted. +#[unstable(feature = "map_try_insert", issue = "82766")] +pub struct OccupiedError<'a, K: 'a, V: 'a> { + /// The entry in the map that was already occupied. + pub entry: OccupiedEntry<'a, K, V>, + /// The value which was not inserted, because the entry was already occupied. + pub value: V, +} + +#[unstable(feature = "map_try_insert", issue = "82766")] +impl<K: Debug, V: Debug> Debug for OccupiedError<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("OccupiedError") + .field("key", self.entry.key()) + .field("old_value", self.entry.get()) + .field("new_value", &self.value) + .finish() + } +} + +#[unstable(feature = "map_try_insert", issue = "82766")] +impl<'a, K: Debug, V: Debug> fmt::Display for OccupiedError<'a, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "failed to insert {:?}, key {:?} already exists with value {:?}", + self.value, + self.entry.key(), + self.entry.get(), + ) + } +} + #[stable(feature = "rust1", since = "1.0.0")] impl<'a, K, V, S> IntoIterator for &'a HashMap<K, V, S> { type Item = (&'a K, &'a V); diff --git a/library/std/src/collections/hash/map/tests.rs b/library/std/src/collections/hash/map/tests.rs index 467968354e2..819be142227 100644 --- a/library/std/src/collections/hash/map/tests.rs +++ b/library/std/src/collections/hash/map/tests.rs @@ -774,11 +774,11 @@ fn test_occupied_entry_key() { let key = "hello there"; let value = "value goes here"; assert!(a.is_empty()); - a.insert(key.clone(), value.clone()); + a.insert(key, value); assert_eq!(a.len(), 1); assert_eq!(a[key], value); - match a.entry(key.clone()) { + match a.entry(key) { Vacant(_) => panic!(), Occupied(e) => assert_eq!(key, *e.key()), } @@ -793,11 +793,11 @@ fn test_vacant_entry_key() { let value = "value goes here"; assert!(a.is_empty()); - match a.entry(key.clone()) { + match a.entry(key) { Occupied(_) => panic!(), Vacant(e) => { assert_eq!(key, *e.key()); - e.insert(value.clone()); + e.insert(value); } } assert_eq!(a.len(), 1); diff --git a/library/std/src/collections/hash/set.rs b/library/std/src/collections/hash/set.rs index 912e975aa0a..8c801b9f128 100644 --- a/library/std/src/collections/hash/set.rs +++ b/library/std/src/collections/hash/set.rs @@ -19,7 +19,7 @@ use super::map::{map_try_reserve_error, RandomState}; // for `bucket.val` in the case of HashSet. I suppose we would need HKT // to get rid of it properly. -/// A hash set implemented as a `HashMap` where the value is `()`. +/// A [hash set] implemented as a `HashMap` where the value is `()`. /// /// As with the [`HashMap`] type, a `HashSet` requires that the elements /// implement the [`Eq`] and [`Hash`] traits. This can frequently be achieved by @@ -105,6 +105,7 @@ use super::map::{map_try_reserve_error, RandomState}; /// // use the values stored in the set /// ``` /// +/// [hash set]: crate::collections#use-the-set-variant-of-any-of-these-maps-when /// [`HashMap`]: crate::collections::HashMap /// [`RefCell`]: crate::cell::RefCell /// [`Cell`]: crate::cell::Cell diff --git a/library/std/src/collections/mod.rs b/library/std/src/collections/mod.rs index 80a13d52a2a..7f8f9c991fe 100644 --- a/library/std/src/collections/mod.rs +++ b/library/std/src/collections/mod.rs @@ -401,9 +401,10 @@ #![stable(feature = "rust1", since = "1.0.0")] #[stable(feature = "rust1", since = "1.0.0")] -#[rustc_deprecated(reason = "moved to `std::ops::Bound`", since = "1.26.0")] +#[rustc_deprecated(reason = "moved to `std::ops::Bound`", since = "1.52.0")] #[doc(hidden)] -pub use crate::ops::Bound; +pub type Bound<T> = crate::ops::Bound<T>; + #[stable(feature = "rust1", since = "1.0.0")] pub use alloc_crate::collections::{binary_heap, btree_map, btree_set}; #[stable(feature = "rust1", since = "1.0.0")] diff --git a/library/std/src/error.rs b/library/std/src/error.rs index 94338c7b04d..80c35307d52 100644 --- a/library/std/src/error.rs +++ b/library/std/src/error.rs @@ -470,6 +470,24 @@ impl Error for char::DecodeUtf16Error { } } +#[unstable(feature = "map_try_insert", issue = "82766")] +impl<'a, K: Debug + Ord, V: Debug> Error + for crate::collections::btree_map::OccupiedError<'a, K, V> +{ + #[allow(deprecated)] + fn description(&self) -> &str { + "key already exists" + } +} + +#[unstable(feature = "map_try_insert", issue = "82766")] +impl<'a, K: Debug, V: Debug> Error for crate::collections::hash_map::OccupiedError<'a, K, V> { + #[allow(deprecated)] + fn description(&self) -> &str { + "key already exists" + } +} + #[stable(feature = "box_error", since = "1.8.0")] impl<T: Error> Error for Box<T> { #[allow(deprecated, deprecated_in_future)] diff --git a/library/std/src/ffi/os_str.rs b/library/std/src/ffi/os_str.rs index 272eccda894..ce52ffc0241 100644 --- a/library/std/src/ffi/os_str.rs +++ b/library/std/src/ffi/os_str.rs @@ -5,6 +5,7 @@ use crate::borrow::{Borrow, Cow}; use crate::cmp; use crate::fmt; use crate::hash::{Hash, Hasher}; +use crate::iter::{Extend, FromIterator}; use crate::ops; use crate::rc::Rc; use crate::str::FromStr; @@ -1192,3 +1193,88 @@ impl FromStr for OsString { Ok(OsString::from(s)) } } + +#[stable(feature = "osstring_extend", since = "1.52.0")] +impl Extend<OsString> for OsString { + #[inline] + fn extend<T: IntoIterator<Item = OsString>>(&mut self, iter: T) { + for s in iter { + self.push(&s); + } + } +} + +#[stable(feature = "osstring_extend", since = "1.52.0")] +impl<'a> Extend<&'a OsStr> for OsString { + #[inline] + fn extend<T: IntoIterator<Item = &'a OsStr>>(&mut self, iter: T) { + for s in iter { + self.push(s); + } + } +} + +#[stable(feature = "osstring_extend", since = "1.52.0")] +impl<'a> Extend<Cow<'a, OsStr>> for OsString { + #[inline] + fn extend<T: IntoIterator<Item = Cow<'a, OsStr>>>(&mut self, iter: T) { + for s in iter { + self.push(&s); + } + } +} + +#[stable(feature = "osstring_extend", since = "1.52.0")] +impl FromIterator<OsString> for OsString { + #[inline] + fn from_iter<I: IntoIterator<Item = OsString>>(iter: I) -> Self { + let mut iterator = iter.into_iter(); + + // Because we're iterating over `OsString`s, we can avoid at least + // one allocation by getting the first string from the iterator + // and appending to it all the subsequent strings. + match iterator.next() { + None => OsString::new(), + Some(mut buf) => { + buf.extend(iterator); + buf + } + } + } +} + +#[stable(feature = "osstring_extend", since = "1.52.0")] +impl<'a> FromIterator<&'a OsStr> for OsString { + #[inline] + fn from_iter<I: IntoIterator<Item = &'a OsStr>>(iter: I) -> Self { + let mut buf = Self::new(); + for s in iter { + buf.push(s); + } + buf + } +} + +#[stable(feature = "osstring_extend", since = "1.52.0")] +impl<'a> FromIterator<Cow<'a, OsStr>> for OsString { + #[inline] + fn from_iter<I: IntoIterator<Item = Cow<'a, OsStr>>>(iter: I) -> Self { + let mut iterator = iter.into_iter(); + + // Because we're iterating over `OsString`s, we can avoid at least + // one allocation by getting the first owned string from the iterator + // and appending to it all the subsequent strings. + match iterator.next() { + None => OsString::new(), + Some(Cow::Owned(mut buf)) => { + buf.extend(iterator); + buf + } + Some(Cow::Borrowed(buf)) => { + let mut buf = OsString::from(buf); + buf.extend(iterator); + buf + } + } + } +} diff --git a/library/std/src/io/buffered/bufreader.rs b/library/std/src/io/buffered/bufreader.rs index 987371f50ec..02b0fc0c57d 100644 --- a/library/std/src/io/buffered/bufreader.rs +++ b/library/std/src/io/buffered/bufreader.rs @@ -1,6 +1,8 @@ use crate::cmp; use crate::fmt; -use crate::io::{self, BufRead, Initializer, IoSliceMut, Read, Seek, SeekFrom, DEFAULT_BUF_SIZE}; +use crate::io::{ + self, BufRead, Initializer, IoSliceMut, Read, Seek, SeekFrom, SizeHint, DEFAULT_BUF_SIZE, +}; /// The `BufReader<R>` struct adds buffering to any reader. /// @@ -90,10 +92,9 @@ impl<R: Read> BufReader<R> { #[stable(feature = "rust1", since = "1.0.0")] pub fn with_capacity(capacity: usize, inner: R) -> BufReader<R> { unsafe { - let mut buffer = Vec::with_capacity(capacity); - buffer.set_len(capacity); - inner.initializer().initialize(&mut buffer); - BufReader { inner, buf: buffer.into_boxed_slice(), pos: 0, cap: 0 } + let mut buf = Box::new_uninit_slice(capacity).assume_init(); + inner.initializer().initialize(&mut buf); + BufReader { inner, buf, pos: 0, cap: 0 } } } } @@ -435,3 +436,9 @@ impl<R: Seek> Seek for BufReader<R> { }) } } + +impl<T> SizeHint for BufReader<T> { + fn lower_bound(&self) -> usize { + self.buffer().len() + } +} diff --git a/library/std/src/io/impls.rs b/library/std/src/io/impls.rs index 00bf8b9af73..9870cfc4c95 100644 --- a/library/std/src/io/impls.rs +++ b/library/std/src/io/impls.rs @@ -1,6 +1,7 @@ #[cfg(test)] mod tests; +use crate::alloc::Allocator; use crate::cmp; use crate::fmt; use crate::io::{ @@ -357,7 +358,7 @@ impl Write for &mut [u8] { /// Write is implemented for `Vec<u8>` by appending to the vector. /// The vector will grow as needed. #[stable(feature = "rust1", since = "1.0.0")] -impl Write for Vec<u8> { +impl<A: Allocator> Write for Vec<u8, A> { #[inline] fn write(&mut self, buf: &[u8]) -> io::Result<usize> { self.extend_from_slice(buf); diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index 22914987405..17002e3b860 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -2238,6 +2238,19 @@ impl<T: BufRead, U: BufRead> BufRead for Chain<T, U> { } } +impl<T, U> SizeHint for Chain<T, U> { + fn lower_bound(&self) -> usize { + SizeHint::lower_bound(&self.first) + SizeHint::lower_bound(&self.second) + } + + fn upper_bound(&self) -> Option<usize> { + match (SizeHint::upper_bound(&self.first), SizeHint::upper_bound(&self.second)) { + (Some(first), Some(second)) => Some(first + second), + _ => None, + } + } +} + /// Reader adaptor which limits the bytes read from an underlying reader. /// /// This struct is generally created by calling [`take`] on a reader. @@ -2464,6 +2477,30 @@ impl<R: Read> Iterator for Bytes<R> { }; } } + + fn size_hint(&self) -> (usize, Option<usize>) { + SizeHint::size_hint(&self.inner) + } +} + +trait SizeHint { + fn lower_bound(&self) -> usize; + + fn upper_bound(&self) -> Option<usize>; + + fn size_hint(&self) -> (usize, Option<usize>) { + (self.lower_bound(), self.upper_bound()) + } +} + +impl<T> SizeHint for T { + default fn lower_bound(&self) -> usize { + 0 + } + + default fn upper_bound(&self) -> Option<usize> { + None + } } /// An iterator over the contents of an instance of `BufRead` split on a diff --git a/library/std/src/io/tests.rs b/library/std/src/io/tests.rs index f176c2f088c..a85dd0d9827 100644 --- a/library/std/src/io/tests.rs +++ b/library/std/src/io/tests.rs @@ -1,7 +1,7 @@ use super::{repeat, Cursor, SeekFrom}; use crate::cmp::{self, min}; use crate::io::{self, IoSlice, IoSliceMut}; -use crate::io::{BufRead, Read, Seek, Write}; +use crate::io::{BufRead, BufReader, Read, Seek, Write}; use crate::ops::Deref; #[test] @@ -199,6 +199,53 @@ fn chain_bufread() { } #[test] +fn bufreader_size_hint() { + let testdata = b"ABCDEFGHIJKL"; + let mut buf_reader = BufReader::new(&testdata[..]); + assert_eq!(buf_reader.buffer().len(), 0); + + let buffer_length = testdata.len(); + buf_reader.fill_buf().unwrap(); + + // Check that size hint matches buffer contents + let mut buffered_bytes = buf_reader.bytes(); + let (lower_bound, _upper_bound) = buffered_bytes.size_hint(); + assert_eq!(lower_bound, buffer_length); + + // Check that size hint matches buffer contents after advancing + buffered_bytes.next().unwrap().unwrap(); + let (lower_bound, _upper_bound) = buffered_bytes.size_hint(); + assert_eq!(lower_bound, buffer_length - 1); +} + +#[test] +fn empty_size_hint() { + let size_hint = io::empty().bytes().size_hint(); + assert_eq!(size_hint, (0, Some(0))); +} + +#[test] +fn chain_empty_size_hint() { + let chain = io::empty().chain(io::empty()); + let size_hint = chain.bytes().size_hint(); + assert_eq!(size_hint, (0, Some(0))); +} + +#[test] +fn chain_size_hint() { + let testdata = b"ABCDEFGHIJKL"; + let mut buf_reader_1 = BufReader::new(&testdata[..6]); + let mut buf_reader_2 = BufReader::new(&testdata[6..]); + + buf_reader_1.fill_buf().unwrap(); + buf_reader_2.fill_buf().unwrap(); + + let chain = buf_reader_1.chain(buf_reader_2); + let size_hint = chain.bytes().size_hint(); + assert_eq!(size_hint, (testdata.len(), None)); +} + +#[test] fn chain_zero_length_read_is_not_eof() { let a = b"A"; let b = b"B"; diff --git a/library/std/src/io/util.rs b/library/std/src/io/util.rs index e43ce4cdb4b..f472361f916 100644 --- a/library/std/src/io/util.rs +++ b/library/std/src/io/util.rs @@ -4,7 +4,9 @@ mod tests; use crate::fmt; -use crate::io::{self, BufRead, Initializer, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write}; +use crate::io::{ + self, BufRead, Initializer, IoSlice, IoSliceMut, Read, Seek, SeekFrom, SizeHint, Write, +}; /// A reader which is always at EOF. /// @@ -80,6 +82,12 @@ impl fmt::Debug for Empty { } } +impl SizeHint for Empty { + fn upper_bound(&self) -> Option<usize> { + Some(0) + } +} + /// A reader which yields one byte over and over and over and over and over and... /// /// This struct is generally created by calling [`repeat()`]. Please diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index ba49dee38e6..8149858e103 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -228,11 +228,13 @@ #![feature(arbitrary_self_types)] #![feature(array_error_internals)] #![feature(asm)] +#![feature(assert_matches)] #![feature(associated_type_bounds)] #![feature(atomic_mut_ptr)] #![feature(box_syntax)] #![feature(c_variadic)] #![feature(cfg_accessible)] +#![cfg_attr(not(bootstrap), feature(cfg_eval))] #![feature(cfg_target_has_atomic)] #![feature(cfg_target_thread_local)] #![feature(char_error_internals)] @@ -281,6 +283,7 @@ #![feature(linkage)] #![feature(llvm_asm)] #![feature(log_syntax)] +#![feature(map_try_insert)] #![feature(maybe_uninit_extra)] #![feature(maybe_uninit_ref)] #![feature(maybe_uninit_slice)] @@ -289,6 +292,7 @@ #![feature(needs_panic_runtime)] #![feature(negative_impls)] #![feature(never_type)] +#![feature(new_uninit)] #![feature(nll)] #![feature(nonnull_slice_from_raw_parts)] #![feature(once_cell)] @@ -298,6 +302,7 @@ #![feature(panic_internals)] #![feature(panic_unwind)] #![feature(pin_static_ref)] +#![feature(prelude_2021)] #![feature(prelude_import)] #![feature(ptr_internals)] #![feature(raw)] @@ -323,7 +328,7 @@ #![feature(try_blocks)] #![feature(try_reserve)] #![feature(unboxed_closures)] -#![feature(unsafe_block_in_unsafe_fn)] +#![cfg_attr(bootstrap, feature(unsafe_block_in_unsafe_fn))] #![feature(unsafe_cell_raw_get)] #![feature(unwind_attributes)] #![feature(vec_into_raw_parts)] @@ -391,7 +396,11 @@ pub use alloc_crate::vec; #[stable(feature = "rust1", since = "1.0.0")] pub use core::any; #[stable(feature = "simd_arch", since = "1.27.0")] -#[doc(no_inline)] +// The `no_inline`-attribute is required to make the documentation of all +// targets available. +// See https://github.com/rust-lang/rust/pull/57808#issuecomment-457390549 for +// more information. +#[doc(no_inline)] // Note (#82861): required for correct documentation pub use core::arch; #[stable(feature = "core_array", since = "1.36.0")] pub use core::array; @@ -550,8 +559,8 @@ pub use std_detect::detect; #[stable(feature = "rust1", since = "1.0.0")] #[allow(deprecated, deprecated_in_future)] pub use core::{ - assert_eq, assert_ne, debug_assert, debug_assert_eq, debug_assert_ne, matches, r#try, todo, - unimplemented, unreachable, write, writeln, + assert_eq, assert_matches, assert_ne, debug_assert, debug_assert_eq, debug_assert_matches, + debug_assert_ne, matches, r#try, todo, unimplemented, unreachable, write, writeln, }; // Re-export built-in macros defined through libcore. diff --git a/library/std/src/net/parser.rs b/library/std/src/net/parser.rs index e8b89626fbd..7064ed3ed23 100644 --- a/library/std/src/net/parser.rs +++ b/library/std/src/net/parser.rs @@ -35,7 +35,7 @@ macro_rules! impl_helper { impl_helper! { u8 u16 u32 } struct Parser<'a> { - // parsing as ASCII, so can use byte array + // Parsing as ASCII, so can use byte array. state: &'a [u8], } @@ -44,7 +44,7 @@ impl<'a> Parser<'a> { Parser { state: input.as_bytes() } } - /// Run a parser, and restore the pre-parse state if it fails + /// Run a parser, and restore the pre-parse state if it fails. fn read_atomically<T, F>(&mut self, inner: F) -> Option<T> where F: FnOnce(&mut Parser<'_>) -> Option<T>, @@ -126,7 +126,7 @@ impl<'a> Parser<'a> { }) } - /// Read an IPv4 address + /// Read an IPv4 address. fn read_ipv4_addr(&mut self) -> Option<Ipv4Addr> { self.read_atomically(|p| { let mut groups = [0; 4]; @@ -139,18 +139,18 @@ impl<'a> Parser<'a> { }) } - /// Read an IPV6 Address + /// Read an IPv6 Address. fn read_ipv6_addr(&mut self) -> Option<Ipv6Addr> { - /// Read a chunk of an ipv6 address into `groups`. Returns the number + /// Read a chunk of an IPv6 address into `groups`. Returns the number /// of groups read, along with a bool indicating if an embedded - /// trailing ipv4 address was read. Specifically, read a series of - /// colon-separated ipv6 groups (0x0000 - 0xFFFF), with an optional - /// trailing embedded ipv4 address. + /// trailing IPv4 address was read. Specifically, read a series of + /// colon-separated IPv6 groups (0x0000 - 0xFFFF), with an optional + /// trailing embedded IPv4 address. fn read_groups(p: &mut Parser<'_>, groups: &mut [u16]) -> (usize, bool) { let limit = groups.len(); for (i, slot) in groups.iter_mut().enumerate() { - // Try to read a trailing embedded ipv4 address. There must be + // Try to read a trailing embedded IPv4 address. There must be // at least two groups left. if i < limit - 1 { let ipv4 = p.read_separator(':', i, |p| p.read_ipv4_addr()); @@ -188,8 +188,8 @@ impl<'a> Parser<'a> { return None; } - // read `::` if previous code parsed less than 8 groups - // `::` indicates one or more groups of 16 bits of zeros + // Read `::` if previous code parsed less than 8 groups. + // `::` indicates one or more groups of 16 bits of zeros. p.read_given_char(':')?; p.read_given_char(':')?; @@ -206,12 +206,12 @@ impl<'a> Parser<'a> { }) } - /// Read an IP Address, either IPV4 or IPV6. + /// Read an IP Address, either IPv4 or IPv6. fn read_ip_addr(&mut self) -> Option<IpAddr> { self.read_ipv4_addr().map(IpAddr::V4).or_else(move || self.read_ipv6_addr().map(IpAddr::V6)) } - /// Read a : followed by a port in base 10. + /// Read a `:` followed by a port in base 10. fn read_port(&mut self) -> Option<u16> { self.read_atomically(|p| { p.read_given_char(':')?; @@ -219,7 +219,7 @@ impl<'a> Parser<'a> { }) } - /// Read a % followed by a scope id in base 10. + /// Read a `%` followed by a scope ID in base 10. fn read_scope_id(&mut self) -> Option<u32> { self.read_atomically(|p| { p.read_given_char('%')?; @@ -227,7 +227,7 @@ impl<'a> Parser<'a> { }) } - /// Read an IPV4 address with a port + /// Read an IPv4 address with a port. fn read_socket_addr_v4(&mut self) -> Option<SocketAddrV4> { self.read_atomically(|p| { let ip = p.read_ipv4_addr()?; @@ -236,7 +236,7 @@ impl<'a> Parser<'a> { }) } - /// Read an IPV6 address with a port + /// Read an IPv6 address with a port. fn read_socket_addr_v6(&mut self) -> Option<SocketAddrV6> { self.read_atomically(|p| { p.read_given_char('[')?; diff --git a/library/std/src/path.rs b/library/std/src/path.rs index de3b57df44e..57c892f32b1 100644 --- a/library/std/src/path.rs +++ b/library/std/src/path.rs @@ -2472,6 +2472,36 @@ impl Path { fs::metadata(self).is_ok() } + /// Returns `Ok(true)` if the path points at an existing entity. + /// + /// This function will traverse symbolic links to query information about the + /// destination file. In case of broken symbolic links this will return `Ok(false)`. + /// + /// As opposed to the `exists()` method, this one doesn't silently ignore errors + /// unrelated to the path not existing. (E.g. it will return `Err(_)` in case of permission + /// denied on some of the parent directories.) + /// + /// # Examples + /// + /// ```no_run + /// #![feature(path_try_exists)] + /// + /// use std::path::Path; + /// assert!(!Path::new("does_not_exist.txt").try_exists().expect("Can't check existence of file does_not_exist.txt")); + /// assert!(Path::new("/root/secret_file.txt").try_exists().is_err()); + /// ``` + // FIXME: stabilization should modify documentation of `exists()` to recommend this method + // instead. + #[unstable(feature = "path_try_exists", issue = "83186")] + #[inline] + pub fn try_exists(&self) -> io::Result<bool> { + match fs::metadata(self) { + Ok(_) => Ok(true), + Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(false), + Err(error) => Err(error), + } + } + /// Returns `true` if the path exists on disk and is pointing at a regular file. /// /// This function will traverse symbolic links to query information about the diff --git a/library/std/src/prelude/mod.rs b/library/std/src/prelude/mod.rs index eb2095b8196..505b5f3013b 100644 --- a/library/std/src/prelude/mod.rs +++ b/library/std/src/prelude/mod.rs @@ -84,3 +84,37 @@ #![stable(feature = "rust1", since = "1.0.0")] pub mod v1; + +/// The 2015 version of the prelude of The Rust Standard Library. +/// +/// See the [module-level documentation](self) for more. +#[unstable(feature = "prelude_2015", issue = "none")] +pub mod rust_2015 { + #[unstable(feature = "prelude_2015", issue = "none")] + #[doc(no_inline)] + pub use super::v1::*; +} + +/// The 2018 version of the prelude of The Rust Standard Library. +/// +/// See the [module-level documentation](self) for more. +#[unstable(feature = "prelude_2018", issue = "none")] +pub mod rust_2018 { + #[unstable(feature = "prelude_2018", issue = "none")] + #[doc(no_inline)] + pub use super::v1::*; +} + +/// The 2021 version of the prelude of The Rust Standard Library. +/// +/// See the [module-level documentation](self) for more. +#[unstable(feature = "prelude_2021", issue = "none")] +pub mod rust_2021 { + #[unstable(feature = "prelude_2021", issue = "none")] + #[doc(no_inline)] + pub use super::v1::*; + + #[unstable(feature = "prelude_2021", issue = "none")] + #[doc(no_inline)] + pub use core::prelude::rust_2021::*; +} diff --git a/library/std/src/prelude/v1.rs b/library/std/src/prelude/v1.rs index ef9aec54a4c..c5b871edbf2 100644 --- a/library/std/src/prelude/v1.rs +++ b/library/std/src/prelude/v1.rs @@ -1,6 +1,6 @@ //! The first version of the prelude of The Rust Standard Library. //! -//! See the [module-level documentation](../index.html) for more. +//! See the [module-level documentation](super) for more. #![stable(feature = "rust1", since = "1.0.0")] @@ -48,7 +48,7 @@ pub use core::prelude::v1::{ // FIXME: Attribute and internal derive macros are not documented because for them rustdoc generates // dead links which fail link checker testing. #[stable(feature = "builtin_macro_prelude", since = "1.38.0")] -#[allow(deprecated)] +#[allow(deprecated, deprecated_in_future)] #[doc(hidden)] pub use core::prelude::v1::{ bench, global_allocator, test, test_case, RustcDecodable, RustcEncodable, @@ -67,6 +67,15 @@ pub use core::prelude::v1::derive; #[doc(hidden)] pub use core::prelude::v1::cfg_accessible; +#[cfg(not(bootstrap))] +#[unstable( + feature = "cfg_eval", + issue = "82679", + reason = "`cfg_eval` is a recently implemented feature" +)] +#[doc(hidden)] +pub use core::prelude::v1::cfg_eval; + // The file so far is equivalent to src/libcore/prelude/v1.rs, // and below to src/liballoc/prelude.rs. // Those files are duplicated rather than using glob imports diff --git a/library/std/src/process.rs b/library/std/src/process.rs index 6480e654c55..f9cfd11e906 100644 --- a/library/std/src/process.rs +++ b/library/std/src/process.rs @@ -71,11 +71,15 @@ //! .spawn() //! .expect("failed to execute child"); //! -//! { -//! // limited borrow of stdin -//! let stdin = child.stdin.as_mut().expect("failed to get stdin"); +//! // If the child process fills its stdout buffer, it may end up +//! // waiting until the parent reads the stdout, and not be able to +//! // read stdin in the meantime, causing a deadlock. +//! // Writing from another thread ensures that stdout is being read +//! // at the same time, avoiding the problem. +//! let mut stdin = child.stdin.take().expect("failed to get stdin"); +//! std::thread::spawn(move || { //! stdin.write_all(b"test").expect("failed to write to stdin"); -//! } +//! }); //! //! let output = child //! .wait_with_output() @@ -885,7 +889,7 @@ impl Command { } /// Executes a command as a child process, waiting for it to finish and - /// collecting its exit status. + /// collecting its status. /// /// By default, stdin, stdout and stderr are inherited from the parent. /// @@ -899,7 +903,7 @@ impl Command { /// .status() /// .expect("failed to execute process"); /// - /// println!("process exited with: {}", status); + /// println!("process finished with: {}", status); /// /// assert!(status.success()); /// ``` @@ -1145,14 +1149,21 @@ impl Stdio { /// .spawn() /// .expect("Failed to spawn child process"); /// - /// { - /// let stdin = child.stdin.as_mut().expect("Failed to open stdin"); + /// let mut stdin = child.stdin.take().expect("Failed to open stdin"); + /// std::thread::spawn(move || { /// stdin.write_all("Hello, world!".as_bytes()).expect("Failed to write to stdin"); - /// } + /// }); /// /// let output = child.wait_with_output().expect("Failed to read stdout"); /// assert_eq!(String::from_utf8_lossy(&output.stdout), "!dlrow ,olleH"); /// ``` + /// + /// Writing more than a pipe buffer's worth of input to stdin without also reading + /// stdout and stderr at the same time may cause a deadlock. + /// This is an issue when running any program that doesn't guarantee that it reads + /// its entire stdin before writing more than a pipe buffer's worth of output. + /// The size of a pipe buffer varies on different targets. + /// #[stable(feature = "process", since = "1.0.0")] pub fn piped() -> Stdio { Stdio(imp::Stdio::MakePipe) @@ -1368,11 +1379,17 @@ impl From<fs::File> for Stdio { /// Describes the result of a process after it has terminated. /// -/// This `struct` is used to represent the exit status of a child process. +/// This `struct` is used to represent the exit status or other termination of a child process. /// Child processes are created via the [`Command`] struct and their exit /// status is exposed through the [`status`] method, or the [`wait`] method /// of a [`Child`] process. /// +/// An `ExitStatus` represents every possible disposition of a process. On Unix this +/// is the **wait status**. It is *not* simply an *exit status* (a value passed to `exit`). +/// +/// For proper error reporting of failed processes, print the value of `ExitStatus` using its +/// implementation of [`Display`](crate::fmt::Display). +/// /// [`status`]: Command::status /// [`wait`]: Child::wait #[derive(PartialEq, Eq, Clone, Copy, Debug)] @@ -1400,7 +1417,7 @@ impl ExitStatus { /// if status.success() { /// println!("'projects/' directory created"); /// } else { - /// println!("failed to create 'projects/' directory"); + /// println!("failed to create 'projects/' directory: {}", status); /// } /// ``` #[stable(feature = "process", since = "1.0.0")] @@ -1410,9 +1427,14 @@ impl ExitStatus { /// Returns the exit code of the process, if any. /// - /// On Unix, this will return `None` if the process was terminated - /// by a signal; `std::os::unix` provides an extension trait for - /// extracting the signal and other details from the `ExitStatus`. + /// In Unix terms the return value is the **exit status**: the value passed to `exit`, if the + /// process finished by calling `exit`. Note that on Unix the exit status is truncated to 8 + /// bits, and that values that didn't come from a program's call to `exit` may be invented the + /// runtime system (often, for example, 255, 254, 127 or 126). + /// + /// On Unix, this will return `None` if the process was terminated by a signal. + /// [`ExitStatusExt`](crate::os::unix::process::ExitStatusExt) is an + /// extension trait for extracting any such signal, and other details, from the `ExitStatus`. /// /// # Examples /// diff --git a/library/std/src/sys/unix/ext/process.rs b/library/std/src/sys/unix/ext/process.rs index 88a27f27f66..1276edc6af6 100644 --- a/library/std/src/sys/unix/ext/process.rs +++ b/library/std/src/sys/unix/ext/process.rs @@ -62,9 +62,14 @@ pub trait CommandExt: Sealed { /// `fork`. This primarily means that any modifications made to memory on /// behalf of this closure will **not** be visible to the parent process. /// This is often a very constrained environment where normal operations - /// like `malloc` or acquiring a mutex are not guaranteed to work (due to + /// like `malloc`, accessing environment variables through [`std::env`] + /// or acquiring a mutex are not guaranteed to work (due to /// other threads perhaps still running when the `fork` was run). /// + /// For further details refer to the [POSIX fork() specification] + /// and the equivalent documentation for any targeted + /// platform, especially the requirements around *async-signal-safety*. + /// /// This also means that all resources such as file descriptors and /// memory-mapped regions got duplicated. It is your responsibility to make /// sure that the closure does not violate library invariants by making @@ -73,6 +78,10 @@ pub trait CommandExt: Sealed { /// When this closure is run, aspects such as the stdio file descriptors and /// working directory have successfully been changed, so output to these /// locations may not appear where intended. + /// + /// [POSIX fork() specification]: + /// https://pubs.opengroup.org/onlinepubs/9699919799/functions/fork.html + /// [`std::env`]: mod@crate::env #[stable(feature = "process_pre_exec", since = "1.34.0")] unsafe fn pre_exec<F>(&mut self, f: F) -> &mut process::Command where @@ -188,12 +197,20 @@ impl CommandExt for process::Command { /// Unix-specific extensions to [`process::ExitStatus`]. /// +/// On Unix, `ExitStatus` **does not necessarily represent an exit status**, as passed to the +/// `exit` system call or returned by [`ExitStatus::code()`](crate::process::ExitStatus::code). +/// It represents **any wait status**, as returned by one of the `wait` family of system calls. +/// +/// This is because a Unix wait status (a Rust `ExitStatus`) can represent a Unix exit status, but +/// can also represent other kinds of process event. +/// /// This trait is sealed: it cannot be implemented outside the standard library. /// This is so that future additional methods are not breaking changes. #[stable(feature = "rust1", since = "1.0.0")] pub trait ExitStatusExt: Sealed { - /// Creates a new `ExitStatus` from the raw underlying `i32` return value of - /// a process. + /// Creates a new `ExitStatus` from the raw underlying integer status value from `wait` + /// + /// The value should be a **wait status, not an exit status**. #[stable(feature = "exit_status_from", since = "1.12.0")] fn from_raw(raw: i32) -> Self; @@ -222,6 +239,8 @@ pub trait ExitStatusExt: Sealed { fn continued(&self) -> bool; /// Returns the underlying raw `wait` status. + /// + /// The returned integer is a **wait status, not an exit status**. #[unstable(feature = "unix_process_wait_more", issue = "80695")] fn into_raw(self) -> i32; } diff --git a/library/std/src/sys/unix/kernel_copy.rs b/library/std/src/sys/unix/kernel_copy.rs index 200dbf06ff8..9687576bb6a 100644 --- a/library/std/src/sys/unix/kernel_copy.rs +++ b/library/std/src/sys/unix/kernel_copy.rs @@ -61,6 +61,7 @@ use crate::process::{ChildStderr, ChildStdin, ChildStdout}; use crate::ptr; use crate::sync::atomic::{AtomicBool, AtomicU8, Ordering}; use crate::sys::cvt; +use libc::{EBADF, EINVAL, ENOSYS, EOPNOTSUPP, EOVERFLOW, EPERM, EXDEV}; #[cfg(test)] mod tests; @@ -535,7 +536,7 @@ pub(super) fn copy_regular_files(reader: RawFd, writer: RawFd, max_len: u64) -> cvt(copy_file_range(INVALID_FD, ptr::null_mut(), INVALID_FD, ptr::null_mut(), 1, 0)) }; - if matches!(result.map_err(|e| e.raw_os_error()), Err(Some(libc::EBADF))) { + if matches!(result.map_err(|e| e.raw_os_error()), Err(Some(EBADF))) { HAS_COPY_FILE_RANGE.store(AVAILABLE, Ordering::Relaxed); } else { HAS_COPY_FILE_RANGE.store(UNAVAILABLE, Ordering::Relaxed); @@ -573,19 +574,20 @@ pub(super) fn copy_regular_files(reader: RawFd, writer: RawFd, max_len: u64) -> Err(err) => { return match err.raw_os_error() { // when file offset + max_length > u64::MAX - Some(libc::EOVERFLOW) => CopyResult::Fallback(written), - Some( - libc::ENOSYS | libc::EXDEV | libc::EINVAL | libc::EPERM | libc::EOPNOTSUPP, - ) => { + Some(EOVERFLOW) => CopyResult::Fallback(written), + Some(ENOSYS | EXDEV | EINVAL | EPERM | EOPNOTSUPP | EBADF) => { // Try fallback io::copy if either: // - Kernel version is < 4.5 (ENOSYS¹) // - Files are mounted on different fs (EXDEV) // - copy_file_range is broken in various ways on RHEL/CentOS 7 (EOPNOTSUPP) // - copy_file_range file is immutable or syscall is blocked by seccomp¹ (EPERM) // - copy_file_range cannot be used with pipes or device nodes (EINVAL) + // - the writer fd was opened with O_APPEND (EBADF²) // // ¹ these cases should be detected by the initial probe but we handle them here // anyway in case syscall interception changes during runtime + // ² actually invalid file descriptors would cause this too, but in that case + // the fallback code path is expected to encounter the same error again assert_eq!(written, 0); CopyResult::Fallback(0) } @@ -649,7 +651,7 @@ fn sendfile_splice(mode: SpliceMode, reader: RawFd, writer: RawFd, len: u64) -> Ok(ret) => written += ret as u64, Err(err) => { return match err.raw_os_error() { - Some(libc::ENOSYS | libc::EPERM) => { + Some(ENOSYS | EPERM) => { // syscall not supported (ENOSYS) // syscall is disallowed, e.g. by seccomp (EPERM) match mode { @@ -659,12 +661,12 @@ fn sendfile_splice(mode: SpliceMode, reader: RawFd, writer: RawFd, len: u64) -> assert_eq!(written, 0); CopyResult::Fallback(0) } - Some(libc::EINVAL) => { + Some(EINVAL) => { // splice/sendfile do not support this particular file descriptor (EINVAL) assert_eq!(written, 0); CopyResult::Fallback(0) } - Some(os_err) if mode == SpliceMode::Sendfile && os_err == libc::EOVERFLOW => { + Some(os_err) if mode == SpliceMode::Sendfile && os_err == EOVERFLOW => { CopyResult::Fallback(written) } _ => CopyResult::Error(err, written), diff --git a/library/std/src/sys/unix/kernel_copy/tests.rs b/library/std/src/sys/unix/kernel_copy/tests.rs index 77369cdd35f..3fe849e23e2 100644 --- a/library/std/src/sys/unix/kernel_copy/tests.rs +++ b/library/std/src/sys/unix/kernel_copy/tests.rs @@ -65,6 +65,24 @@ fn copy_specialization() -> Result<()> { result.and(rm1).and(rm2) } +#[test] +fn copies_append_mode_sink() -> Result<()> { + let tmp_path = tmpdir(); + let source_path = tmp_path.join("copies_append_mode.source"); + let sink_path = tmp_path.join("copies_append_mode.sink"); + let mut source = + OpenOptions::new().create(true).truncate(true).write(true).read(true).open(&source_path)?; + write!(source, "not empty")?; + source.seek(SeekFrom::Start(0))?; + let mut sink = OpenOptions::new().create(true).append(true).open(&sink_path)?; + + let copied = crate::io::copy(&mut source, &mut sink)?; + + assert_eq!(copied, 9); + + Ok(()) +} + #[bench] fn bench_file_to_file_copy(b: &mut test::Bencher) { const BYTES: usize = 128 * 1024; diff --git a/library/std/src/sys/unix/process/process_unix.rs b/library/std/src/sys/unix/process/process_unix.rs index 9e82df7755e..47aaca82af9 100644 --- a/library/std/src/sys/unix/process/process_unix.rs +++ b/library/std/src/sys/unix/process/process_unix.rs @@ -1,6 +1,7 @@ use crate::convert::TryInto; use crate::fmt; use crate::io::{self, Error, ErrorKind}; +use crate::mem; use crate::ptr; use crate::sys; use crate::sys::cvt; @@ -45,15 +46,14 @@ impl Command { // // Note that as soon as we're done with the fork there's no need to hold // a lock any more because the parent won't do anything and the child is - // in its own process. - let result = unsafe { - let _env_lock = sys::os::env_read_lock(); - cvt(libc::fork())? - }; + // in its own process. Thus the parent drops the lock guard while the child + // forgets it to avoid unlocking it on a new thread, which would be invalid. + let (env_lock, result) = unsafe { (sys::os::env_read_lock(), cvt(libc::fork())?) }; let pid = unsafe { match result { 0 => { + mem::forget(env_lock); drop(input); let Err(err) = self.do_exec(theirs, envp.as_ref()); let errno = err.raw_os_error().unwrap_or(libc::EINVAL) as u32; @@ -74,7 +74,10 @@ impl Command { rtassert!(output.write(&bytes).is_ok()); libc::_exit(1) } - n => n, + n => { + drop(env_lock); + n + } } }; @@ -527,9 +530,22 @@ impl fmt::Display for ExitStatus { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(code) = self.code() { write!(f, "exit code: {}", code) + } else if let Some(signal) = self.signal() { + if self.core_dumped() { + write!(f, "signal: {} (core dumped)", signal) + } else { + write!(f, "signal: {}", signal) + } + } else if let Some(signal) = self.stopped_signal() { + write!(f, "stopped (not terminated) by signal: {}", signal) + } else if self.continued() { + write!(f, "continued (WIFCONTINUED)") } else { - let signal = self.signal().unwrap(); - write!(f, "signal: {}", signal) + write!(f, "unrecognised wait status: {} {:#x}", self.0, self.0) } } } + +#[cfg(test)] +#[path = "process_unix/tests.rs"] +mod tests; diff --git a/library/std/src/sys/unix/process/process_unix/tests.rs b/library/std/src/sys/unix/process/process_unix/tests.rs new file mode 100644 index 00000000000..5819d2c2a5a --- /dev/null +++ b/library/std/src/sys/unix/process/process_unix/tests.rs @@ -0,0 +1,30 @@ +#[test] +fn exitstatus_display_tests() { + // In practice this is the same on every Unix. + // If some weird platform turns out to be different, and this test fails, use #[cfg]. + use crate::os::unix::process::ExitStatusExt; + use crate::process::ExitStatus; + + let t = |v, s| assert_eq!(s, format!("{}", <ExitStatus as ExitStatusExt>::from_raw(v))); + + t(0x0000f, "signal: 15"); + t(0x0008b, "signal: 11 (core dumped)"); + t(0x00000, "exit code: 0"); + t(0x0ff00, "exit code: 255"); + + // On MacOS, 0x0137f is WIFCONTINUED, not WIFSTOPPED. Probably *BSD is similar. + // https://github.com/rust-lang/rust/pull/82749#issuecomment-790525956 + // The purpose of this test is to test our string formatting, not our understanding of the wait + // status magic numbers. So restrict these to Linux. + if cfg!(target_os = "linux") { + t(0x0137f, "stopped (not terminated) by signal: 19"); + t(0x0ffff, "continued (WIFCONTINUED)"); + } + + // Testing "unrecognised wait status" is hard because the wait.h macros typically + // assume that the value came from wait and isn't mad. With the glibc I have here + // this works: + if cfg!(all(target_os = "linux", target_env = "gnu")) { + t(0x000ff, "unrecognised wait status: 255 0xff"); + } +} diff --git a/library/std/src/sys/unix/stack_overflow.rs b/library/std/src/sys/unix/stack_overflow.rs index d7bba50c76a..2a487fff54a 100644 --- a/library/std/src/sys/unix/stack_overflow.rs +++ b/library/std/src/sys/unix/stack_overflow.rs @@ -39,6 +39,7 @@ impl Drop for Handler { ))] mod imp { use super::Handler; + use crate::io; use crate::mem; use crate::ptr; @@ -149,11 +150,11 @@ mod imp { 0, ); if stackp == MAP_FAILED { - panic!("failed to allocate an alternative stack"); + panic!("failed to allocate an alternative stack: {}", io::Error::last_os_error()); } let guard_result = libc::mprotect(stackp, page_size(), PROT_NONE); if guard_result != 0 { - panic!("failed to set up alternative stack guard page"); + panic!("failed to set up alternative stack guard page: {}", io::Error::last_os_error()); } stackp.add(page_size()) } diff --git a/library/std/src/sys/unix/thread.rs b/library/std/src/sys/unix/thread.rs index 40c96307514..01a12dcf5a2 100644 --- a/library/std/src/sys/unix/thread.rs +++ b/library/std/src/sys/unix/thread.rs @@ -231,6 +231,7 @@ pub mod guard { use libc::{mmap, mprotect}; use libc::{MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE}; + use crate::io; use crate::ops::Range; use crate::sync::atomic::{AtomicUsize, Ordering}; use crate::sys::os; @@ -361,12 +362,12 @@ pub mod guard { 0, ); if result != stackaddr || result == MAP_FAILED { - panic!("failed to allocate a guard page"); + panic!("failed to allocate a guard page: {}", io::Error::last_os_error()); } let result = mprotect(stackaddr, page_size, PROT_NONE); if result != 0 { - panic!("failed to protect the guard page"); + panic!("failed to protect the guard page: {}", io::Error::last_os_error()); } let guardaddr = stackaddr as usize; diff --git a/library/std/src/sys/wasi/fs.rs b/library/std/src/sys/wasi/fs.rs index bcf7da46b4b..63c22136273 100644 --- a/library/std/src/sys/wasi/fs.rs +++ b/library/std/src/sys/wasi/fs.rs @@ -650,13 +650,11 @@ fn open_parent(p: &Path) -> io::Result<(ManuallyDrop<WasiFd>, PathBuf)> { ); return Err(io::Error::new(io::ErrorKind::Other, msg)); } - let len = CStr::from_ptr(buf.as_ptr().cast()).to_bytes().len(); - buf.set_len(len); - buf.shrink_to_fit(); + let relative = CStr::from_ptr(relative_path).to_bytes().to_vec(); return Ok(( ManuallyDrop::new(WasiFd::from_raw(fd as u32)), - PathBuf::from(OsString::from_vec(buf)), + PathBuf::from(OsString::from_vec(relative)), )); } } diff --git a/library/std/src/sys_common/thread_local_dtor.rs b/library/std/src/sys_common/thread_local_dtor.rs index 6f5ebf4a271..f9971fb6f21 100644 --- a/library/std/src/sys_common/thread_local_dtor.rs +++ b/library/std/src/sys_common/thread_local_dtor.rs @@ -1,6 +1,6 @@ //! Thread-local destructor //! -//! Besides thread-local "keys" (pointer-sized non-adressable thread-local store +//! Besides thread-local "keys" (pointer-sized non-addressable thread-local store //! with an associated destructor), many platforms also provide thread-local //! destructors that are not associated with any particular data. These are //! often more efficient. |
