try-specialize
The try-specialize crate provides limited, zero-cost
specialization in generic context on stable Rust.
use TrySpecialize;
Sized>
Introduction
While specialization in Rust can be a tempting solution in many use cases, I encourage you to reconsider using traits instead as a more idiomatic alternatives. The use of traits is the idiomatic way to achieve polymorphism in Rust, promoting better code clarity, reusability, and maintainability.
However the specialization in Rust can be suitable when you need to optimize performance by providing specialized optimized implementations for some types without altering the code logic. Also it can be useful in highly specific type-level programming related usecases like comparisons between types from different libraries.
For a simple use cases, I recommend checking out the castaway crate,
which offers a much simpler API and is easier to work with. On nightly Rust,
I recommend to use min_specialization feature instead. Note that Rust
standard library can and already use min_specialization in stable for
many optimizations. For a more detailed comparison, see the
Alternative crates section below.
About
This crate provides a comprehensive API for solving various specialization
challenges to save you from using unsafe code or at least to take over
some of the checks. This crate provides specialization from unconstrained
types, to unconstrained types, between 'static types, between types
references and mutable references, and more.
Library tests ensure that the
specializations are performed at compile time, fully optimized and do not
come with any run-time cost with opt-level >= 1. Note that release
profile uses opt-level = 3 by default.
Usage
Add this to your Cargo.toml:
[]
= "0.1.0"
Then, in most cases, it will be enough to use TrySpecialize trait
methods like TrySpecialize::try_specialize,
TrySpecialize::try_specialize_ref and
TrySpecialize::try_specialize_static. If you want to check the
possibility of specialization in advance and then use it infallibly multiple
times including using reversed or mapped specialization, check
Specialization struct methods.
Note that even in expression position, unlike casting, subtyping, and
coercion, specialization does not alter the underlying type or its data.
It only qualifies the underlying types of generics. Specialization from a
generic type T1 to another generic type T2 succeeds only when the
underlying types of T1 and T2 are equal.
Examples
Specialize type to any LifetimeFree type:
use TrySpecialize;
Specialize 'static type to any 'static type:
use TrySpecialize;
Specialize Sized or Unsized type reference to any LifetimeFree type
reference:
use TrySpecialize;
Specialize Sized or Unsized type mutable reference to any
LifetimeFree type mutable reference:
use TrySpecialize;
Specialize a third-party library container with generic types:
use ;
You can also check out a more comprehensive example that implements custom
data encoders and decoders with customizable per-type encoding and decoding
errors and optimized byte array encoding and decoding. The full example is
available at at: examples/encode.rs.
The part of the example related to the Encode implementation for a slice:
// ...
// ...
Find values by type in generic composite types:
use ;
Documentation
Feature flags
alloc(implied bystd, enabled by default): enablesLifetimeFreeimplementations foralloctypes, likeBox,Arc,String,Vec,BTreeMapetc.std(enabled by default): enablesallocfeature andLifetimeFreeimplementations forstdtypes, likeOsStr,Path,PathBuf,Instant,HashMapetc.unreliable: enables unreliable functions, methods and macros that rely on Rust standard library undocumented behavior. Seeunreliablemodule documentation for details.
How it works
- Type comparison when both types are
'staticis performed usingTypeId::ofcomparison. - Type comparison when one of types is
LifetimeFreeis performed by converting type wrapped as&dyn PhantomData<T>to&dyn PhantomData<T> + 'staticand comparing theirTypeId::of. - Specialization is performed using type comparison and
transmute_copywhen the equality of types is proved. - Unreliable trait implementation check is performed using an expected, but
undocumented behavior of the Rust stdlib
PartialEqimplementation forArc<T>.Arc::equses fast path comparing references before comparing data ifTimplementsEq.
Alternative crates
castaway: A very similar crate and a great simpler alternative that can cover most usecases. Its macro uses Autoref-Based Specialization and automatically determines the appropriate type of specialization, making it much easier to use. However, if no specialization is applicable because of the same Autoref-Based Specialization, the compiler generates completely unclear errors, which makes it difficult to use it in complex cases. Usesunsafecode for type comparison and specialization.coe-rs: Smaller and simpler, but supports only static types and don't safely combine type equality check and specialization. Usesunsafecode for type specialization.downcast-rs: Specialized on trait objects (dyn) downcasting. Can't be used to specialize unconstrained types. Doesn't useunsafecode.syllogismandsyllogism_macro: Requires to provide all possible types to macro that generate a lot of boilerplate code and can't be used to specialize stdlib types because of orphan rules. Doesn't useunsafecode.specialize: Requires nightly. Adds a simple macro to inline nightlymin_specializationusage into simpleif letexpressions.specialized-dispatch: Requires nightly. Adds a macro to inline nightlymin_specializationusage into amatch-like macro.spez: Specializes expression types, usingAutoref-Based Specialization. It won't works in generic context but can be used in the code generated by macros.impls: Determine if a type implements a trait. Can't detect erased type bounds, so not applicable in generic context, but can be used in the code generated by macros.
Comparison of libraries supporting specialization in generic context:
crate try-specialize |
crate castaway |
crate coe-rs |
crate downcast-rs |
crate syllogism |
min_spec... nightly feature |
crate specialize |
crate spec...ch |
|
|---|---|---|---|---|---|---|---|---|
| Rust toolchain | Stable | Stable | Stable | Stable | Stable | Nightly | Nightly | Nightly |
| API complexity | Complex | Simple | Simple | Moderate | Simple | Simple | Simple | Simple |
| API difficulty | Difficult | Easy | Easy | Moderate | Moderate | Easy | Easy | Moderate |
| Zero-cost (compile-time optimized) | YES | YES | YES | no | YES | YES | YES | YES |
| Safely combines type eq check and specialization | YES | YES | no | YES | YES | YES | YES | YES |
| Specialize value references | YES | YES | YES | N/A | YES | YES | YES | no |
| Specialize values | YES | YES | no | N/A | YES | YES | YES | YES |
| Specialize values without consume on failure | YES | YES | no | N/A | YES | YES | no | YES |
| Limited non-static value specialization | YES | YES | no | N/A | YES | YES | YES | YES |
| Full non-static value specialization | no | no | no | N/A | YES | no | no | no |
Specialize trait objects (dyn) |
N/A | N/A | N/A | YES | N/A | N/A | N/A | N/A |
| Compare types without instantiation | YES | no | YES | no | YES | YES | YES | no |
| Support std types | YES | YES | YES | YES | no | YES | YES | YES |
| Specialize from unconstrained type | YES | YES | no | no | no | YES | YES | YES |
| Specialize to unconstrained type | YES | no | no | no | no | YES | YES | YES |
| Check generic implements "erased" trait | YES, but unreliable |
no | no | no | no | YES | YES | YES |
| Specialize to generic with added bounds | no | no | no | no | no | YES | YES | YES |
| API based on | Traits | Macros | Traits | Macros + Traits | Traits | Language | Macros | Macros |
| Type comparison implementation based on | TypeId + transmute |
TypeId + transmute |
TypeId |
N/A | Traits | Language | Nightly feature | Nightly feature |
| Type casting implementation based on | transmute_copy |
ptr::read |
transmute |
std::any::Any |
Traits | Language | Nightly feature | Nightly feature |
Implementation free of unsafe |
no | no | no | YES | YES | YES | YES | YES |
Primitive example of the value specialization using different libraries
crate try_specialize:
use TrySpecialize;
assert_eq!;
assert_eq!;
assert_eq!;
crate castaway:
use cast;
assert_eq!;
assert_eq!;
assert_eq!;
crate coe-rs:
use ;
// Library don't support non-reference.
// specialization. Using reference.
crates downcast-rs:
use ;
impl_downcast!;
// Library requires all specializable
// types to be defined in advance.
// Library support only trait objects (`dyn`).
crate specialize:
// Requires nightly.
use constrain;
// Library don't support non-consuming
// value specialization. Using reference.
Sized>
assert_eq!;
assert_eq!;
assert_eq!;
crate specialized-dispatch:
// Requires nightly.
use specialized_dispatch;
// The library don't support using generics.
// from outer item. Using `Option`.
assert_eq!;
assert_eq!;
assert_eq!;
crates syllogism and syllogism_macro:
use ;
use impl_specialization;
// Library specialization can not be
// implemented for std types because of
// orphan rules. Using custom local types.
;
;
;
// Library requires all specializable
// types to be defined in one place.
impl_specialization!;
assert_eq!;
assert_eq!;
assert_eq!;
min_specialization nightly feature:
// Requires nightly.
// The artificial example looks a bit long.
// More real-world use cases are usually
// on the contrary more clear and understandable.
assert_eq!;
assert_eq!;
assert_eq!;
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or https://opensource.org/licenses/MIT)
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.