Crates.io | try-specialize |
lib.rs | try-specialize |
version | 0.1.1 |
source | src |
created_at | 2024-10-12 14:21:25.674897 |
updated_at | 2024-10-12 18:56:13.69746 |
description | Zero-cost specialization in generic context on stable Rust |
homepage | |
repository | https://github.com/zheland/try-specialize |
max_upload_size | |
id | 1406545 |
size | 298,062 |
The try-specialize
crate provides limited, zero-cost
specialization in generic context on stable Rust.
use try_specialize::TrySpecialize;
fn example_specialize_by_value<T>(value: T) -> Result<u32, T> {
value.try_specialize()
}
fn example_specialize_by_ref<T: ?Sized>(value: &T) -> Option<&str> {
value.try_specialize_ref()
}
While specialization in Rust can be a tempting solution in many use cases, it is usually more idiomatic to use traits instead. Traits are the idiomatic way to achieve polymorphism in Rust, promoting better code clarity, reusability, and maintainability.
However, specialization can be suitable when you need to optimize performance by providing specialized implementations for some types without altering the code logic. It's also useful in specific, type-level programming use cases like comparisons between types from different libraries.
For a simple use cases, consider the castaway
crate, which offers a much
simpler API. On nightly Rust, consider using min_specialization
feature
instead. The Rust standard library already uses min_specialization
for
many optimizations. For a more detailed comparison, see the
Alternative crates section below.
This crate offers a comprehensive API for addressing various specialization challenges, reducing the need for unsafe code. It provides specialization from unconstrained types, to unconstrained types, between 'static types, and between type references and mutable references, and more.
Library tests ensure that specializations are
performed at compile time and are fully optimized with no runtime cost at
opt-level >= 1
. Note that the release profile uses opt-level = 3
by default.
Add this to your Cargo.toml
:
[dependencies]
try-specialize = "0.1.1"
Then, you can use TrySpecialize
trait methods like
TrySpecialize::try_specialize
, TrySpecialize::try_specialize_ref
and
TrySpecialize::try_specialize_static
. To check the possibility of
specialization in advance and use it infallibly multiple times, including
reversed or mapped specialization, use Specialization
struct methods.
Note that unlike casting, subtyping, and coercion, specialization does
not alter the underlying type or data. It merely qualifies the underlying
types of generics, succeeding only when the underlying types of T1
and
T2
are equal.
Specialize type to any LifetimeFree
type:
use try_specialize::TrySpecialize;
fn func<T>(value: T) {
match value.try_specialize::<(u32, String)>() {
Ok(value) => specialized_impl(value),
Err(value) => default_impl(value),
}
}
Specialize 'static
type to any 'static
type:
use try_specialize::TrySpecialize;
fn func<T>(value: T)
where
T: 'static,
{
match value.try_specialize_static::<(u32, &'static str)>() {
Ok(value) => specialized_impl(value),
Err(value) => default_impl(value),
}
}
Specialize Sized
or Unsized
type reference to any LifetimeFree
type
reference:
use try_specialize::TrySpecialize;
fn func<T>(value: &T)
where
T: ?Sized, // Relax the implicit `Sized` bound.
{
match value.try_specialize_ref::<str>() {
Some(value) => specialized_impl(value),
None => default_impl(value),
}
}
Specialize Sized
or Unsized
type mutable reference to any
LifetimeFree
type mutable reference:
use try_specialize::TrySpecialize;
fn func<T>(value: &mut T)
where
T: ?Sized, // Relax the implicit `Sized` bound.
{
match value.try_specialize_mut::<[u8]>() {
Some(value) => specialized_impl(value),
None => default_impl(value),
}
}
Specialize a third-party library container with generic types:
use try_specialize::{Specialization, TypeFn};
fn func<K, V>(value: hashbrown::HashMap<K, V>) {
struct MapIntoHashMap;
impl<K, V> TypeFn<(K, V)> for MapIntoHashMap {
type Output = hashbrown::HashMap<K, V>;
}
if let Some(spec) = Specialization::<(K, V), (u32, char)>::try_new() {
let spec = spec.map::<MapIntoHashMap>();
let value: hashbrown::HashMap<u32, char> = spec.specialize(value);
specialized_impl(value);
} else {
default_impl(value);
}
}
For a more comprehensive example, see the examples/encode.rs
, which
implements custom data encoders and decoders with per-type encoding and
decoding errors and optimized byte array encoding and decoding.
The part of this example related to the Encode
implementation for a slice:
// ...
impl<T> Encode for [T]
where
T: Encode,
{
type EncodeError = T::EncodeError;
#[inline]
fn encode_to<W>(&self, writer: &mut W) -> Result<(), Self::EncodeError>
where
W: ?Sized + Write,
{
if let Some(spec) = Specialization::<[T], [u8]>::try_new() {
// Specialize self from `[T; N]` to `[u32; N]`
let bytes: &[u8] = spec.specialize_ref(self);
// Map type specialization to its associated error specialization.
let spec_err = spec.rev().map::<MapToEncodeError>();
writer
.write_all(bytes)
// Specialize error from `io::Error` to `Self::EncodeError`.
.map_err(|err| spec_err.specialize(err))?;
} else {
for item in self {
item.encode_to(writer)?;
}
}
Ok(())
}
}
// ...
Find values by type in generic composite types:
use try_specialize::{LifetimeFree, TrySpecialize};
pub trait ConsListLookup {
fn find<T>(&self) -> Option<&T>
where
T: ?Sized + LifetimeFree;
}
impl ConsListLookup for () {
#[inline]
fn find<T>(&self) -> Option<&T>
where
T: ?Sized + LifetimeFree,
{
None
}
}
impl<T1, T2> ConsListLookup for (T1, T2)
where
T2: ConsListLookup,
{
#[inline]
fn find<T>(&self) -> Option<&T>
where
T: ?Sized + LifetimeFree,
{
self.0.try_specialize_ref().or_else(|| self.1.find::<T>())
}
}
#[derive(Eq, PartialEq, Debug)]
struct StaticStr(&'static str);
// SAFETY: It is safe to implement `LifetimeFree` for structs with no
// parameters.
unsafe impl LifetimeFree for StaticStr {}
let input = (
123_i32,
(
[1_u32, 2, 3, 4],
(1_i32, (StaticStr("foo"), (('a', false), ()))),
),
);
assert_eq!(input.find::<u32>(), None);
assert_eq!(input.find::<i32>(), Some(&123_i32));
assert_eq!(input.find::<[u32; 4]>(), Some(&[1, 2, 3, 4]));
assert_eq!(input.find::<[u32]>(), None);
assert_eq!(input.find::<StaticStr>(), Some(&StaticStr("foo")));
assert_eq!(input.find::<char>(), None);
assert_eq!(input.find::<(char, bool)>(), Some(&('a', false)));
alloc
(implied by std
, enabled by default): enables LifetimeFree
implementations for alloc
types, like Box
, Arc
, String
, Vec
,
BTreeMap
etc.std
(enabled by default): enables alloc
feature and LifetimeFree
implementations for std
types, like OsStr
, Path
, PathBuf
,
Instant
, HashMap
etc.unreliable
: enables functions, methods and macros that rely on Rust
standard library undocumented behavior. Refer to the unreliable
module
documentation for details.'static
types compares their TypeId::of
s.LifetimeFree
type treats
them as 'static
and compares their TypeId::of
s.transmute_copy
when the
equality of types is established.PartialEq
implementation
for Arc<T>
. Arc::eq
uses fast path comparing references before
comparing data if T
implements Eq
.castaway
: A similar crate with a much simpler macro-based API. The
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. Internally uses unsafe
code 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. Internally uses
unsafe
code for type specialization.downcast-rs
: Specialized on trait objects (dyn
) downcasting. Can't
be used to specialize unconstrained types.syllogism
and syllogism_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.specialize
: Requires nightly.
Adds a simple macro to inline nightly min_specialization
usage into
simple if let
expressions.specialized-dispatch
: Requires nightly. Adds a macro to inline nightly
min_specialization
usage into a match
-like macro.spez
: Specializes expression types, using Autoref-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.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 |
crate
|
crate
|
crate
|
crates
|
crate
|
crate
|
crates
|
|
Licensed under either of
at your option.
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.