by Jon Gjengset [Twitter] [r/rust]
transcribed by Jimmy Hartzell [blog]
organized by myself
_
Did you know that whether or not let _ = x
should move x
is actually
fairly subtle?
bool
Did you know that bool
isn’t just “stored as a byte”, the compiler straight up
declares its representation as the same as that of u8
?
{integer}
Did you know that fasterthanlime’s most recent article
does a great job at explaining {integer}
?
u8
Did you know that as of Rust 1.60, you can now use u8::escape_ascii
to get an iterator of the bytes needed to escape that byte character in most
contexts.
u32
Did you know that u32
now has associated constants for MIN
and MAX
,
so you no longer need to use std::u32::MIN
and can use u32::MIN
directly instead?
u128
Did you know that even though we got u128
a long time ago now, we
still don’t have repr(128)
?
u256
Did you know that because Rust compiles through LLVM, we’re sort of constrained to the primitive types LLVM supports, and LLVM itself only goes up to 128?
usize
Did you know that usize
isn’t really “the size of a pointer”. Instead, it’s
more like “the size of a pointer address difference”, and the two can be
fairly different!
f32
/f64
Did you know that in Rust 1.62, we’ll get
a deterministic ordering function, total_cmp
for floating point numbers?
*
Did you know that *
is (mostly) just syntax sugar for the std::ops::Mul
trait?
std::num::Wrapping<T>
Did you know that there used to also be a trait accompanying Wrapping
,
WrappingOps
, that was removed
last minute before 1.0?
T
Did you know that T
doesn’t imply ownership?
When we say a type is generic over T
, that T
can just as easily be a
reference to something on the stack, and the type system will still be happy.
Even T: 'static
doesn’t imply owned — consider &'static str
for example.
&mut T
Did you know that while &mut T
is defined as meaning “mutable reference” in
the Rust reference, you’re often better off thinking of it as
“mutually-exclusive reference”, as described by David Tolnay.
*const T
Did you know that, at least for the time being, *const T
and *mut T
are
more or less equivalent?
std::ptr::NonNull<T>
Did you know that one of the super neat features of NonNull
is that it enables
the same niche optimization that regular references and the NonZero*
types get, where Option<NonNull<T>>
is the same size as *mut T
?
#[feature(raw_ref_op)] &raw const T
Did you know that originally
the intention was to have &const raw
variable be just a MIR construct and let
&variable as *const _
be automatically changed to &const raw
?
[T; N]
Did you know that while most trait implementations for arrays now use const
generics to impl for any length N
, we can’t yet do the same for Default
.
&[u8]
Did you know that &[u8]
implements Read
and Write
? So
for anything that takes impl Read
, you can provide &mut
slice instead! Comes
in handy for testing. Note that the slice itself is shortened for each read,
hence &mut &[u8]
.
struct S
Did you know that struct S
implicitly declares a constant called S
, which is
why you can make one using just S
?
struct S<const C: usize>
Did you know that with Rust 1.59.0 you can now give C
a default value?
()
Did you know that ()
implements FromIterator
,
so you can .collect::<Result<(), E>>
to just see if anything in an iterator
erred?
((), ())
Did you know that ((), ())
and ()
have the same hash?
(T,)
Did you know that the trailing comma is to distinguish one-element tuples from parenthetical expressions?
impl Trait
Did you know that impl Trait
in argument position and impl Trait
in return
position represent completely different type constructs, even though they “feel”
related?
for<'a> Trait<'a>
Did you know that you can use for<'a>
to say that a bound has to hold for
any lifetime 'a
, not just a specific lifetime you happen to have available
at the time. For example, <T> for<'a>: &'a T: Read
says that any shared
reference to a T
must implement Read
.
Self
Did you know that fn foo(self)
is syntactic sugar for fn foo(self: Self)
,
and that one day you’ll be
able to use other types for self
that involve Self
, like
fn foo(self: Arc<Self>)
?
fn
Specifically, did you know that the name of a function is not an fn
? It’s a
FnDef
,
which can then be coerced
to a FnPtr
?
Fn
Did you know that until Rust 1.35.0, Box<T> where T: Fn
did not impl Fn
, so
you couldn’t (easily) call boxed closures!
FnOnce
Did you know that until Rust 1.35, you couldn’t call a Box<dyn FnOnce>
and
needed a special type
(FnBox
) for it! This was because it requires “unsized rvalues”
to implement, which are still unstable today.
Option<T>
Did you know that Option<T>
implements IntoIterator
,
yielding 0/1 elements, and you can then call Iterator::flatten
to make that be 0/n elements if T: IntoIterator
?
Result<T, E>
Did you know that Rust originally (pre-1.0) had both Result
and an Either
type? They decided to remove Either
way back in 2013
std::ops::ControlFlow<B, C>
Did you know that ControlFlow
is really a stepping stone towards making ?
work for other types than Option
and Result
? The full design has gone
through a lot of iterations, but the latest and greatest is
RFC #3058.
!
Did you know that std::convert::Infallible
is the “original” !
, and that the plan is to one day replace Infallible
with
a type alias for !
?
std::panic::PanicInfo
Did you know that since PanicInfo
is in core
, its Display
implementation cannot access the panic data
if it’s a String
(since it can’t name that type), so trying to print the
PanicInfo
after a std::panic::panic_any(format!("x y z"))
won’t print
"x y z"
?
Vec<T>
Did you know that Vec::swap_remove
is way faster than Vec::remove
,
if you can tolerate changes to ordering?
Did you know that the smallest non-zero capacity for a Vec<T>
depends on the size of T
?
Vec<()>
Did you know that since ()
is a zero-sized type, and the vector never actually
has to store any data, the capacity of Vec<()>
is usize::MAX
!
std::collections::HashMap<K, V>
Did you know that the Rust devs are working on a “raw” entry API
for HashMap
that allows you to (unsafely) avoid re-hashing a key you’ve
already hashed?
std::collections::BTreeMap<K, V>
Did you know that BTreeMap
is one of the few collections that still
doesn’t have a drain
method?
std::ops::Range<Idx>
Did you know that there’s been a lot of debate
around whether or not the Range
types should be Copy
?
std::hash::Hash
Did you know that Hash is responsible for not just one, but two of the issues on the Rust 2 breakage wishlist?
std::fmt::Debug
Did you know that the Formatter
argument to Debug::fmt
makes it really easy to customize debug
representations for structs, enums, lists, and sets? See the debug_*
methods
on it.
std::fmt::Formatter
Did you know that Formatter
is super easy to use if you want more control
over debugging for a custom type? For example, to emit a “list-like” type, just
Formatter::debug_list().entries(self.0.iter()).finish()
.
std::any::Any
Did you know that Any
is really non-magical? It just has a blanket
implementation for all T
that returns TypeId::of::<T>()
, and to downcast it
simply compares the return value of that trait method to see if it’s safe to
cast to downcast to a type! TypeId
is magic though.
Box<T>
Did you know that Box<T>
is a #[fundamental]
type, which means that it’s exempt from the normal rules that don’t allow you to
implement foreign traits for foreign types (assuming T
is a local type)?
std::borrow::Cow<T>
Did you know that there used to be a special IntoCow
trait, but it was
deprecated before 1.0 was
released!
std::borrow::Cow<str>
Did you know that because Cow<'a, T>
is covariant in 'a
; you can always
assign Cow::Borrowed("some string")
to one no matter what it originally held?
std::pin::Pin<T>
Did you know that the names Pin
and Unpin
where heavily debated?
Pin
was almost called Pinned
, for example.
std::mem::MaybeUninit<T>
Did you know that MaybeUninit
arose because the previous mechanism,
std::mem::uninitialized
,
produced immediate undefined behavior when invoked with most types (like
uninitialized::<bool>()
).
std::marker::PhantomData<T>
Did you know that it’s actually kind of tricky to
define PhantomData
yourself?
struct InvariantLifetime<'id>(PhantomData<*mut &'id ()>);
Did you know that PhantomData<T>
has variance like T
, and *mut T
is invariant over T
, and so by placing a
lifetime inside T
you make the outer type invariant over that lifetime?
std::cell::UnsafeCell<T>
Did you know that UnsafeCell
is one of those types that the compiler needs
“special magic” for because it has to instruct LLVM to not assume Rust’s
normal aliasing rules hold once code traverses the boundary of any UnsafeCell
?
std::cell::RefCell<T>
Did you know that RefCell::replace
allows you to replace a value in-place directly like std::mem::replace
?
std::rc::Rc<T>
Did you know that the Rc
type was among the arguments
for why std::mem::forget
shouldn’t be marked as unsafe?
std::sync::Arc<T>
Did you know that Arc
has a make_mut
method that effectively gives you copy-on-write? Given a &mut Arc<T>
, it will
either give you &mut T
if there are no other Arc
s, or it will clone T
,
make the Arc<T>
point to that new T
, and then give you a &mut
to it!
std::sync::Mutex<T>
/RwLock
/Condvar
Did you know that Mara is doing some awesome work
on making Mutex
, RwLock
, and Condvar
much better on a wide array on
platforms?
std::sync::Weak<T>
Did you know that actual deallocation logic
for Arc
is implemented
in Weak
, and is invoked by considering all copies of a particular Arc<T>
to
collectively hold a single Weak<T>
between them?
std::sync::atomic::AtomicU32
Did you know that you’ll often want compare_exchange_weak
over compare_exchange
to get more efficient code on ARM cores.
std::sync::mpsc
Did you know that std::sync::mpsc
has had a known bug since 2017,
and that the implementation may be replaced entirely
with the crossbeam channel
implementation?
std::thread::Thread
Did you know that the ThreadId
that’s available for each Thread
is entirely a std
construct? Creating a
ThreadId
simply increments
a global static counter under a lock.
std::process::Child
Did you know that std
has three different ways
to spawn a child process on Linux (posix_spawn
, clone3
/exec
,
fork
/exec
), depending on what capabilities your kernel version has?
std::future::Ready<T>
Did you know that these days you can just use async move { x }
instead of future::ready(x)
.
The main reason to still use future::ready(x)
is that you can name the future
it returns, which is harder with async
(without type_alias_impl_trait
,
that is).
std::task::Waker
Did you know that Waker
is secretly just
a dyn std::task::Wake + Clone
done in a way that doesn’t require a wide
pointer or support for multi-trait dynamic dispatch?
std::fs::File
Did you know that there are implementations of Read
,
Write
, and Seek
for &File
as well, so multiple threads can share a single File
and call
those concurrently. Whether they should is a different question of course.
std::os::unix::net::UnixStream
Did you know that (on nightly) you can pass UNIX file descriptors over
UnixStream
s too, and thereby give another process access
to a file it may not otherwise be able to open?
std::net::TcpStream
Did you know that in its default configuration, TcpStream
uses
Nagle’s Algorithm to
schedule packet sends, which can introduce unfortunate latency spikes. If you’re
particularly latency sensitive, consider calling TcpStream::set_nodelay(true)
.
std::ffi::c_void
Did you know that the whole c_void
type is a collection of hacks to try to
work around the lack of extern types?
std::ffi::CStr
Did you know that CStr::default
creates a CStr
that points to a const string "\0"
stored in the binary text
segment, which means all default CStr
s point to the same (non-null) string!
std::ffi::OsString
Did you know that there are per-platform extension traits for OsString
that
bake in the assumptions you can safely make on that platform? Such as
strings being [u8]
on Unix
and UTF-16 on Windows.