Skip to main content

Discriminant

Struct Discriminant 

Source
pub struct Discriminant { /* private fields */ }
Expand description

Type describing the layout, alignment, and type of a container

Discriminant is central to the safety and performance of local pooling. It describes 2 things in just 8 bytes.

  • The unique location in the source code of the implementation of IsoPoolable. This is accomplished by a proc macro that generates a global table of unique location ids for cross crate source code locations. This unique id ensures that different container types can’t be mixed in the same pool.

  • The layout and alignment of all the type parameters of the container. Discriminant has 3 slots that can be filled with either type parameters or const SIZE parameters. If your container has more parameters than that then you can’t locally pool it, and you can’t implement IsoPoolable. If you try you will likely cause undefined behavior.

In order to squeeze all this information into just 8 bytes there are some limitations.

  • You can’t have more than 0xFFFF implementations of IsoPoolable in the same project. This includes all the crates depended on by the project.

  • Your type parameters must have size <= 0x0FFF bytes and alignment of 1, 2, 4, 8, or 16. Alignments > 16 will be rejected.

  • const SIZE parameters must be <= 0x7FFF.

If any of these constraints are violated the Discriminant constructors will return None. If you desire you may panic at that point to cause a compile error. If you do not panic and instead leave DISCRIMINANT as None then local pool operations on that type will work just fine, but nothing will be pooled. Objects will be freed when they are dropped and take will allocate new objects each time it is called.

§Discriminant Collisions and Why They’re Safe

Two different types can have the same discriminant if they have the same size and alignment. For example:

#[repr(C)]
struct Padded1 { a: u8, _pad: [u8; 7], b: u64 }  // size 16, align 8

#[repr(C)]
struct Padded2 { x: u64, y: u64 }                 // size 16, align 8

If you pool Vec<Padded1> and Vec<Padded2>, they would get the same discriminant because Padded1 and Padded2 have identical size and alignment. This means a Vec<Padded1> allocation could be reused as a Vec<Padded2> allocation.

This is safe because:

  1. Containers are always empty when returned to pools (reset() ensures this)
  2. An empty Vec<T> only cares about T’s size and alignment for its allocation
  3. The actual bit patterns inside T don’t matter when the Vec is empty
  4. When you take from the pool and populate it with your type, it’s initialized correctly

The discriminant system is designed to ensure that different container types never share pools (via the LocationId), and that the memory layout of type parameters is compatible. As long as containers are properly emptied before pooling (which reset() guarantees), the system is memory safe even with discriminant collisions.

Implementations§

Source§

impl Discriminant

Source

pub const fn empty(id: LocationId) -> Discriminant

return a new empty discriminant

Source

pub const fn new(id: LocationId) -> Option<Discriminant>

build a discriminant for a type with no type variables (just a location id). Always returns Some

Source

pub const fn add_param<T>(self) -> Option<Self>

Add a type parameter.

Discriminant has 3 slots. Each slot can hold either a type parameter or a const SIZE. This will return None if the discriminant is full, or the type parameter’s size or alignment are too big.

Source

pub const fn add_size<const SIZE: usize>(self) -> Option<Self>

Add a const SIZE

Discriminant has 3 slots. Each slot can hold either a type parameter or a const SIZE. This will return None if the discriminant is full, or if the size is too large.

Source

pub const fn new_p1<T>(id: LocationId) -> Option<Discriminant>

build a discriminant with one type param

Source

pub const fn new_p1_size<T, const SIZE: usize>( id: LocationId, ) -> Option<Discriminant>

build a discriminant with one type param and a size

Source

pub const fn new_p2<T, U>(id: LocationId) -> Option<Discriminant>

build a discriminant with two type params

Source

pub const fn new_p2_size<T, U, const SIZE: usize>( id: LocationId, ) -> Option<Discriminant>

build a discriminant with two type params and a size

Source

pub const fn new_p3<T, U, V>(id: LocationId) -> Option<Discriminant>

build a discriminant with three type params

Trait Implementations§

Source§

impl Clone for Discriminant

Source§

fn clone(&self) -> Discriminant

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Discriminant

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Hash for Discriminant

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl PartialEq for Discriminant

Source§

fn eq(&self, other: &Discriminant) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Copy for Discriminant

Source§

impl Eq for Discriminant

Source§

impl StructuralPartialEq for Discriminant

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.