#traits #pointers #dynamic #specialization #reflection #send-sync

nightly no-std hv-alchemy

Heavy Alchemy - the black arts of transmutation, wrapped for your safe usage and enjoyment

1 unstable release

0.1.0 Nov 12, 2021

#1355 in Data structures

MIT/Apache

105KB
2K SLoC

Heavy Alchemy - the black arts of transmutation, wrapped for your safe usage and enjoyment.

hv-alchemy is a set of traits and types which maintain a global runtime registry (static is not possible at the moment, but the linkme crate might come into play at some point) of trait object vtables and other type info such as sizes, alignments, destructors, and more. It requires Rust nightly for the ptr_metadata, unsize, and arbitrary_self_types features.

A few of the things this crate allows you to do:

  • At runtime, ask "do we know if this type implements this object-safe trait?"
  • Downcast a dyn AlchemicalAny to a concrete sized type T
  • Dyncast a dyn AlchemicalAny to an unsized trait object type dyn Trait
  • Copy and clone types without compile-time Copy and Clone bounds (requiring runtime-registered Copy and Clone implementations)
  • Extract Send, Sync, Copy, and Clone constraints which can be used during compile-time and are conditionally accessed during run-time, for specializing behavior according to traits some type implements
  • Extend the TypeTable (vtable registry) for any applicable type at any time with any trait it is statically known to implement
  • Access "types" at runtime by casting Type<T> to Box<dyn AlchemicalAny> or any other trait you implement for it
  • Access the Default::default function pointer item as a fn() -> T for some type which implements Default (checking at runtime and without a compile-time Default bound)
  • Given a *const dyn AlchemicalAny, try to clone it into a fresh allocation using the Layout stored in the type table provided by dyn AlchemicalAny

Please use irresponsibly. hv-alchemy is no_std compatible, but requires alloc.

Caveats

We cannot currently at compile-time register all the different things that some type implements w/ the Alchemy registry. As such you basically have to supply some kind of hook in your APIs (if you're relying on Alchemy) which encourages/provides a way to add relevant trait objects to the TypeTables you're interested in. Something like linkme or the currently broken inventory crate are capable of doing something somewhat like this but they cannot universally quantify over types, as that would require monomorphizing a forall T. bound into a bunch of Type::<T>::of().add::<...>() calls, and for very good reasons which may or may not be obvious to you, Rust currently has no way to do this.

How it works

Alchemy keeps a global static HashMap of TypeIds to &'static TypeTables, which are created through Box::leak (at some point this might be switched to a global Bump arena or similar.) These TypeTables contain again, HashMaps of TypeId to &'static DynVtables; a DynVtable represents the vtable of some trait object dyn SomeTrait for some type SomeType. Specifically, if you want to see if some object implements fmt::Debug, Alchemy lookups go something like this:

  • Get the TypeTable of the object. If we're trying to dyncast a trait object dyn AlchemicalAny, it's as easy as calling the .type_table() method. Otherwise, we could use Type::<T>::of(), if know T statically (and just don't want a static fmt::Debug bound.)
    • If we use Type::<T>::of(), Alchemy takes the TypeId of T and uses it to look for its TypeTable in the global registry. If it's not there, it creates an empty one.
  • Take the TypeId of dyn fmt::Debug, and use that to look for the corresponding DynVtable in the vtables map of the TypeTable.
  • If we find a DynVtable there, we know by invariants that its implementor type will be the type of the thing we're trying to check, and that its trait object type will be dyn fmt::Debug. We can then assume it is safe to use DynVtable::to_dyn_object_pointer on our original reference/value to convert it to an &dyn fmt::Debug. Or if all we wanted to know was whether it did implement Debug, we have our answer.

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Dependencies

~1.5MB
~19K SLoC