Safe code relies on the unsafe code and trusts that unsafe code is implemented correctly.
But unsafe code should not trust safe code to be correct. For example, Ord
trait might be
implemented incorrectly by some type T
and this might break the unsafe code in BTreeMap
.
So BTreeMap
must deal with this. But if incorrectness in a safe code cannot be detected, it is
marked as unsafe, like Send
, Sync
and GlobalAllocator
.
- Dereference raw pointers
- Call
unsafe
functions - Implement
unsafe
traits - Mutate statics
- Access fields of
union
s
By default, composite structure have an alignment equal to the maximum of their fields' alignments. Eg.
struct A {
a: u8,
b: u32,
c: u16
}
A
will be 32-bit aligned.
Rust does not guarantee A
and B
have their data laid out in exactly the same way. (Padding or ordering might be different)
struct A {
a: i32,
b: u64
}
struct B {
a: i32,
b: u64
}
Because they lack a statically known size, these types can only exist behind a pointer.
Example: [T]
, str
and dyn MyTrait
.
Types that have no size occupies no space.
enum Void {}
This can be used like Result<T, Void>
where you must return a Result
but it is guaranteed that
any error won't occur.
The order, size, and alignment of fields is exactly what you would expect from C or C++.
This can only be used on struct with a single non-zero-sized field. The effect is that the layout and ABI of the whole struct is guaranteed to be the same as that one field.
These specify the size to make fieldless enum (C-like enums). Only works on fieldless enums.
Strip any padding, and only align the type to a byte. This will like have negative side-effects and can cause undefined behavior.
Force the type to have an alignment of at least n
.
-
Each elided lifetime in input position becomes a distinct lifetime parameter.
-
If there is exactly one input lifetime poisition (elided or not), that lifetime is assigned to all elided output lifetimes.
-
If there are multiple input lifetime positions, but one of them is
&self
or&mut self
, the lifetime ofself
is assigned to all elided output lifetimes. -
Otherwise, it is an error to elide an output lifetime.
Example, dereferencing a raw pointer. Such a lifetime becomes as big as context demands.
struct Closure<F> {
func: F
}
impl<F> Closure<F>
where for<'a> F: Fn(&'a (u8, u16)) -> &'a u8,
{
fn call<'a>(&'a self) -> &'a u8 {
(self.func)(&self.data)
}
}
F
is covariant ifF<Sub>
is a subtype ofF<Super>
(subtyping "passes through")F
is contravariant ifF<Super>
is a subtype ofF<Sub>
(subtyping is "inverted")F
is invariant otherwise (no subtyping relationship exists)
'a T U
-----------------------------------------------------
&'a T covariant covariant -
&'a mut T covariant invariant -
Box<T> - covariant -
Vec<T> - covariant -
UnsafeCell<T> - invariant -
Cell<T> - invariant -
fn(T) -> U - contravariant covariant
*const T - covariant -
*mut T - invariant -
unsafe impl<#[may_dangle] 'a> Drop for Inspector<'a> {
fn drop(&mut self) {
println!("... {}", self.1);
}
}
- When the order of drop is important, it is best to use
ManuallyDrop
.
If Vec
would defined like this:
struct Vec<T> {
data: *const T,
len: usize,
cap: usize,
}
The drop checker will determine that Vec<T>
does not own any values of type T. This will make it conclude that it doesn't need to worry about Vec dropping any T's in its destructor for determining drop check soundness.
To strictly tell the drop checker that Vec<T>
owns T, we use PhantomData
.
struct Vec<T> {
data: *const T, // *const for variance
len: usize,
cap: usize,
_marker: marker::PhantomData<T>,
}