Crate small_type_id

Source
Expand description

This crate provides 3 things:

  1. TypeId type that is used to identify type.
  2. HasTypeId trait with associated constant TYPE_ID.
  3. HasTypeId derive macro for implementing HasTypeId for types.

TypeId has several guarantees about its layout and those guarantees would be held even in major semver updates:

  1. TYPE_ID is a constant.
  2. Size is 32 bit.
  3. TypeId cannot be zero which allows niche optimizations (size_of::<Option<TypeId>>() is 4 bytes).
  4. Most significant bit (MSB) is guaranteed to be zero:
    • Allows users to use this bit to distinguish with some other kind of id in a union (e.g. runtime types from some scripting engine).

The following guarantees are effective for current version but may change with major release:

  1. TypeId doesn’t depend on layout of type. It is possible to change type from struct to enum and it would have same TypeId.
  2. TypeId doesn’t change between recompilations.
  3. TypeId doesn’t depend on compilation target if the declaration of the type doesn’t move.

TypeId can change on any of following changes:

  • small_type_id crate version changes (including patch releases)
  • placement of the type changes (e.g. type moved into different module)
  • compiler version changes
  • version of a crate, that declare the type, changes (including patch releases).

§Limitations

Doesn’t support non-static types and types with generic parameters. To derive HasTypeId trait on such type, consider using newtype pattern.

#[derive(small_type_id::HasTypeId)]
struct VecWithInts(/* wrapped generic type*/ Vec<i32>);

It is possible that 2 types end up having same type id which would stop program from running. In such case, it is possible to adjust generated ids manually by using #[small_type_id_seed=number] attribute.

#[derive(small_type_id::HasTypeId)]
struct Type1{}
#[derive(small_type_id::HasTypeId)]
#[small_type_id_seed=42]
struct Type2{}
assert_ne!(Type1::TYPE_ID, Type2::TYPE_ID);

§How uniqueness of TypeIds are enforced

Using only 31 bit for TypeId makes collisions quite possible (though unlikely) so it is necessaryto verify uniqueness of generated ids.

All invocations of HasTypeId macro generate code that registers the type in big list of types that have TypeId. Then, verification code is executed before main.

Verification code generally executes with complexity O(n2) although with very small constant on Windows and Linux, e.g. it processes 60000 types faster than 100 ms in debug build.

However, if it is inacceptible, it can be disabled using unsafe_remove_duplicate_checks feature. Enabling this feature is equivalent to running unsafe code so please consult it documentation before enabling.

If duplicate TypeIds detected, program would write some debug information to stderr and terminate with error before reaching main.

§Available Cargo features

  • debug_type_name — Saves type name in derive invocation of HasTypeId macro, allowing to printing conflicting types in case of collision of HasTypeId::TYPE_ID values.

    The purpose of this feature only to debug cases of TypeId collisions.

    It is disabled by default to avoid wasting place in binary for useless strings.

  • unsafe_remove_duplicate_checks — Disables automatic verification of uniqueness of TypeIds. Use iter_registered_types function to run verification yourself.

    The purpose of this feature is

    • to avoid running any code before main
    • to prevent linking with libc or kernel32.

    Please, don’t enable this feauture in library crates. This should be done only in final binary crates because it may affect other libraries.

  • unsafe_dont_register_types — Implies unsafe_remove_duplicate_checks.

    Disables type registration entirely. Exists to be used in embedded environments when every single preserved byte is important.

    If you use this feature, do run tests without it before deploying your code.

    If this feature is enabled, there is no way to ensure that uniqueness of TypeIds is still guaranteed.

  • dont_use_link_section — This features mostly needed for testing code that used on target doesn’t have linking sections implemented on other platforms.

    It disables usage of link sections for gathering of existing type ids for uniqueness check. Instead, it would call registration function for each for each type that implements HasTypeId. That can cause longer compilation times an increase of binary size.

§Semver breaking policy

The following changes are not considered breaking:

  1. Changes of string representation of type ids
  2. Changes of uniqueness verification algorithm
  3. Change of type id generation algorithm (and resulting values of type ids)
  4. Changes of type registration code
  5. Additions of new types, functions, modules
  6. Any changes in private module (users should not use it directly)

§Examples

Use for distinguishing 2 types.

#[derive(small_type_id::HasTypeId)]
struct A;
#[derive(small_type_id::HasTypeId)]
struct B;

assert_ne!(A::TYPE_ID, B::TYPE_ID);
// Test in compile time:
const { assert!(A::TYPE_ID.as_u32() != B::TYPE_ID.as_u32()) };

Detect that types are same or not in generic code in compile time.

const fn is_types_unique<T0, T1, T2>()->bool
where
    T0: HasTypeId,
    T1: HasTypeId,
    T2: HasTypeId,
{
    let types = [T0::TYPE_ID, T1::TYPE_ID, T2::TYPE_ID];
    let mut i = 0;
    // Cannot use for loops because it is a const function.
    while i < types.len() {
        let t = types[i];
        let mut j = i + 1;
        while j < types.len() {
            if t.as_u32() == types[j].as_u32() {
                return false;
            }
            j += 1;
        }
        i += 1;
    }
    true
}

#[derive(small_type_id::HasTypeId)]
struct A;
#[derive(small_type_id::HasTypeId)]
struct B;
#[derive(small_type_id::HasTypeId)]
enum C{ A, B}

const { assert!(is_types_unique::<A, B, C>()) };
const { assert!(! is_types_unique::<A, B, A>()) };
assert!(is_types_unique::<A, B, C>());
assert!(! is_types_unique::<A, B, A>());

Adjusting generated TypeId using #[small_type_id_seed]

#[derive(small_type_id::HasTypeId)]
#[small_type_id_seed=42]
enum MyType {A, B}

§Implementation details

This implementation details are subject to change and should be not relied upon.

Type id is computed by hashing string that contain full module path, type name and crate version. Usage of module path allows to distinguish between types with same name declared in different places, usage of crate version makes same type from different versions of the same crate be interpreted as different types (which is correct behaviour).

Current computation algorithm is xxhash32.

We collect all derived types either by using statics linked to special section (on Linux and Windows), or put them in a linked list by running code before main. We run code before main using crate ctor.

Structs§

ErrorInvalidBytes
Error type for TypeId::from_bytes.
TypeEntry
Entry that describes registered type information.
TypeId
Unique id for a type. Have extra invariants about internal structure, described in module documentation.

Traits§

HasTypeId
Marks that type has TypeId

Functions§

iter_registered_types
Allows iteration over types that implemented HasTypeId trait using derive macro.

Derive Macros§

HasTypeId
Implements HasTypeId trait and registers implementation for runtime verification.