├── .gitignore ├── fmt.sh ├── doc.sh ├── rustfmt.toml ├── deny.toml ├── asm.sh ├── tests ├── platform.rs ├── version.rs ├── ranged_values_codegen.rs ├── no-alloc-and-no-std │ ├── src │ │ └── main.rs │ └── Cargo.toml ├── zero_cost_unreliable.rs ├── zero_cost_optimized_results.rs ├── zero_cost_optimized_fn_ptrs.rs └── common │ └── mod.rs ├── .github └── workflows │ ├── coverage.yml │ └── build.yml ├── LICENSE-MIT ├── CHANGELOG.md ├── src ├── unreliable │ ├── mod.rs │ ├── weak_specialization.rs │ ├── try_specialize_weak.rs │ └── impls_trait.rs ├── type_eq.rs ├── type_fn.rs └── try_specialize.rs ├── Cargo.toml ├── examples └── encode.rs ├── LICENSE-APACHE ├── check.sh └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /fmt.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cargo +nightly fmt --all 4 | -------------------------------------------------------------------------------- /doc.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features 4 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | format_code_in_doc_comments = true 2 | format_macro_bodies = true 3 | format_macro_matchers = true 4 | format_strings = true 5 | group_imports = "StdExternalCrate" 6 | imports_granularity = "Module" 7 | normalize_comments = true 8 | wrap_comments = true 9 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | [licenses] 2 | allow = [ 3 | "MIT", 4 | "Apache-2.0", 5 | ] 6 | confidence-threshold = 0.99 7 | 8 | 9 | [[advisories.ignore]] 10 | id = "RUSTSEC-2024-0436" 11 | reason = "ignore unmaintained paste dev-dependency crate" 12 | # See https://github.com/rustsec/advisory-db/pull/2215#issuecomment-2709315704 13 | -------------------------------------------------------------------------------- /asm.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | for module in "--test optimized"; do 4 | output=$( 5 | cargo +nightly rustc \ 6 | --release \ 7 | --message-format=json-render-diagnostics \ 8 | $module \ 9 | -- \ 10 | --emit=llvm-ir \ 11 | --emit=asm 12 | ) 13 | artifact=$( 14 | echo "$output" \ 15 | | rg -o "[^\"]*/release/deps/[0-9a-z_-]*" \ 16 | | tail -n1 17 | ) 18 | echo -e "\e]8;;file://$artifact.s\e\\$artifact.s\e]8;;\e\\" 19 | echo -e "\e]8;;file://$artifact.ll\e\\$artifact.ll\e]8;;\e\\" 20 | done 21 | -------------------------------------------------------------------------------- /tests/platform.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs, reason = "okay in tests")] 2 | #![expect(unused_crate_dependencies, reason = "okay in tests")] 3 | #![cfg(not(miri))] 4 | #![cfg(any(target_arch = "x86", target_arch = "x86_64"))] 5 | 6 | use try_specialize::TrySpecialize; 7 | 8 | #[test] 9 | fn test_lifetime_free_x86() { 10 | #[cfg(target_arch = "x86")] 11 | use core::arch::x86::*; 12 | #[cfg(target_arch = "x86_64")] 13 | use core::arch::x86_64::*; 14 | 15 | // SAFETY: Ignore too old processors without `cpuid` in tests. 16 | let cpuid_result = unsafe { __cpuid(0) }; 17 | assert!(cpuid_result.try_specialize::().is_ok()); 18 | assert!(cpuid_result.try_specialize::().is_err()); 19 | } 20 | -------------------------------------------------------------------------------- /tests/version.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs, reason = "okay in tests")] 2 | #![expect(unused_crate_dependencies, reason = "okay in tests")] 3 | #![cfg(not(miri))] 4 | #![cfg(not(target_arch = "wasm32"))] 5 | 6 | #[cfg(test)] 7 | #[test] 8 | fn test_readme_deps() { 9 | version_sync::assert_markdown_deps_updated!("README.md"); 10 | } 11 | 12 | #[cfg(test)] 13 | #[test] 14 | fn test_html_root_url() { 15 | version_sync::assert_html_root_url_updated!("src/lib.rs"); 16 | } 17 | 18 | #[cfg(test)] 19 | #[test] 20 | fn test_changelog_mentions_version() { 21 | version_sync::assert_contains_regex!("CHANGELOG.md", "^## \\[{version}\\] - "); 22 | version_sync::assert_contains_regex!("CHANGELOG.md", "\\[{version}\\]: "); 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: coverage 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | defaults: 9 | run: 10 | shell: bash 11 | 12 | env: 13 | CARGO_TERM_COLOR: always 14 | 15 | jobs: 16 | coverage: 17 | name: Upload coverage to codecov.io 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | - run: rustup toolchain install nightly --profile minimal --component llvm-tools 22 | - run: cargo install cargo-llvm-cov 23 | - run: cargo +nightly llvm-cov --doctests --all-features --codecov --output-path codecov.json 24 | - uses: codecov/codecov-action@v4 25 | with: 26 | file: codecov.json 27 | token: ${{ secrets.CODECOV_TOKEN }} 28 | fail_ci_if_error: true 29 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 Andrey Zheleznov 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [0.1.2] - 2025-06-07 11 | ### Changed 12 | - Increase minimum supported Rust version to 1.82.0. 13 | - Improve examples in docs and readme. 14 | - Update dev dependencies. 15 | 16 | ## [0.1.1] - 2024-10-12 17 | ### Changed 18 | - Improve docs and readme readability. 19 | 20 | ## [0.1.1] - 2024-10-12 21 | ### Added 22 | - `LifetimeFree` trait and implementation for Rust stdlib types. 23 | - `type_eq` functions for `'static` and non-`'static` types. 24 | - `Specialization` struct for comprehensive type specialization. 25 | - `TrySpecialize` trait for simple type specialization 26 | - `TypeFn` trait for specialization types mapping. 27 | - Specialization to `LifetimeFree` type, from `LifetimeFree` type, 28 | between `'static` types, and `unsafe` variants. 29 | - Specialization by value, by reference, by mutable reference. 30 | - API documentation with examples. 31 | - Tests and doc-tests. 32 | - GitHub CI integration. 33 | - Check and utility scripts. 34 | 35 | [Unreleased]: https://github.com/zheland/try-specialize/compare/v0.1.2...HEAD 36 | [0.1.2]: https://github.com/zheland/try-specialize/compare/v0.1.1...v0.1.2 37 | [0.1.1]: https://github.com/zheland/try-specialize/compare/v0.1.0...v0.1.1 38 | [0.1.0]: https://github.com/zheland/try-specialize/compare/v0.0.0...v0.1.0 39 | -------------------------------------------------------------------------------- /tests/ranged_values_codegen.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs, reason = "okay in tests")] 2 | #![expect(unused_crate_dependencies, reason = "okay in tests")] 3 | #![cfg(not(miri))] 4 | #![cfg(not(debug_assertions))] // Requires `profile.opt-level >= 1` 5 | #![cfg(any(target_arch = "x86", target_arch = "x86_64"))] 6 | 7 | use try_specialize::TrySpecialize; 8 | 9 | mod common; 10 | 11 | #[no_mangle] 12 | #[inline(never)] 13 | extern "Rust" fn take_option_u32_try_specialize_as_u32(value: Option) -> bool { 14 | value.try_specialize::().is_ok() 15 | } 16 | 17 | // Generates suboptimal, but valid bytecode for `Option`s. 18 | // The `Option` tag (passed in %edi) can never be equal to 2, so it will never 19 | // return 1. 20 | #[rustversion::all(before(2024-08-13), before(1.82))] 21 | const fn take_option_u32_try_specialize_as_u32_expected() -> &'static [u8] { 22 | &[ 23 | 0x83, 0xFF, 0x02, // cmpl $2, %edi 24 | 0x0F, 0x94, 0xC0, // sete %al 25 | 0xC3, // retq 26 | ] 27 | } 28 | 29 | // Improved in: 30 | // - , 31 | // - . 32 | #[rustversion::any(since(2024-08-13), since(1.82))] 33 | const fn take_option_u32_try_specialize_as_u32_expected() -> &'static [u8] { 34 | &[ 35 | 0x31, 0xC0, // xorl %eax, %eax 36 | 0xC3, // retq 37 | ] 38 | } 39 | 40 | #[test] 41 | fn test_bytecode_changes() { 42 | for value in [None, Some(0), Some(1), Some(u32::MAX)] { 43 | assert!(!take_option_u32_try_specialize_as_u32(value)); 44 | } 45 | 46 | let fns = [( 47 | common::fn_raw_ptr(take_option_u32_try_specialize_as_u32), 48 | take_option_u32_try_specialize_as_u32_expected(), 49 | )]; 50 | 51 | for (fn_ptr, expected) in fns { 52 | for (offset, &expected) in expected.iter().enumerate() { 53 | // SAFETY: Locally defined extern functions should be readable. 54 | let byte = unsafe { fn_ptr.byte_add(offset).read() }; 55 | assert_eq!( 56 | byte, expected, 57 | "at offset: {offset}, byte: {byte:#X}, expected: {expected:#X}" 58 | ); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/unreliable/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains a set of functions, traits and macros that depend on 2 | //! undocumented standard library behavior and should therefore be used with 3 | //! caution. 4 | //! 5 | //! Library tests ensure that the `impls_trait` checks are performed at compile 6 | //! time and are fully optimized with no runtime cost at `opt-level >= 1`. Note 7 | //! that the release profile uses `opt-level = 3` by default. 8 | //! 9 | //! # Reliability 10 | //! 11 | //! While it is unlikely, there is still a possibility that functions in this 12 | //! module may return false negatives in future Rust versions. 13 | //! 14 | //! The correctness of the results returned by the functions depends on 15 | //! the following: 16 | //! - Documented behavior that if `T` implements `Eq`, two `Rc`s that point to 17 | //! the same allocation are always equal: 18 | //! . 19 | //! - Undocumented behavior that the `Rc::partial_eq` implementation for `T: Eq` 20 | //! will not use `PartialEq::eq` if both `Rc`s point to the same memory 21 | //! location. 22 | //! - The assumption that the undocumented short-circuit behavior described 23 | //! above will be retained for optimization purposes. 24 | //! 25 | //! There is no formal guarantee that the undocumented behavior described above 26 | //! will be retained. If the implementation changes in a future Rust version, 27 | //! the function may return a false negative, that is, it may return `false`, 28 | //! even though `T` implements the trait. However, the implementation guarantees 29 | //! that a false positive result is impossible, i.e., the function will never 30 | //! return true if `T` does not implement the trait in any future Rust version. 31 | //! 32 | //! Details: 33 | //! - , 34 | //! - , 35 | //! - , 36 | //! - . 37 | 38 | mod impls_trait; 39 | mod try_specialize_weak; 40 | mod weak_specialization; 41 | 42 | pub use impls_trait::*; 43 | pub use try_specialize_weak::*; 44 | pub use weak_specialization::*; 45 | -------------------------------------------------------------------------------- /tests/no-alloc-and-no-std/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | #![allow( 4 | dead_code, 5 | missing_docs, 6 | clippy::empty_loop, 7 | clippy::missing_panics_doc, 8 | clippy::panic, 9 | clippy::undocumented_unsafe_blocks, 10 | clippy::unimplemented, 11 | reason = "okay in tests" 12 | )] 13 | 14 | #[cfg(feature = "alloc")] 15 | extern crate alloc; 16 | 17 | #[cfg(feature = "std")] 18 | extern crate std; 19 | 20 | #[cfg(feature = "global-allocator")] 21 | use core::alloc::{GlobalAlloc, Layout}; 22 | #[cfg(feature = "panic-handler")] 23 | use core::panic::PanicInfo; 24 | 25 | #[cfg(feature = "global-allocator")] 26 | struct DummyAllocator; 27 | 28 | #[cfg(feature = "global-allocator")] 29 | #[global_allocator] 30 | static GLOBAL_ALLOCATOR: DummyAllocator = DummyAllocator; 31 | 32 | #[cfg(feature = "panic-handler")] 33 | #[panic_handler] 34 | const fn panic(_: &PanicInfo<'_>) -> ! { 35 | loop {} 36 | } 37 | 38 | #[no_mangle] 39 | pub const extern "C" fn _start() -> ! { 40 | loop {} 41 | } 42 | 43 | #[cfg(feature = "global-allocator")] 44 | unsafe impl GlobalAlloc for DummyAllocator { 45 | unsafe fn alloc(&self, _layout: Layout) -> *mut u8 { 46 | unimplemented!() 47 | } 48 | 49 | unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) { 50 | unimplemented!() 51 | } 52 | } 53 | 54 | #[no_mangle] 55 | pub extern "C" fn test_core() { 56 | use try_specialize::TrySpecialize; 57 | 58 | assert_eq!(42_u32.try_specialize::(), Ok(42)); 59 | assert_eq!(42_i32.try_specialize::(), Err(42)); 60 | assert_eq!("abc".try_specialize::(), Err("abc")); 61 | } 62 | 63 | #[cfg(feature = "alloc")] 64 | #[no_mangle] 65 | pub extern "C" fn test_alloc() { 66 | use alloc::string::String; 67 | 68 | use try_specialize::TrySpecialize; 69 | 70 | assert_eq!(42_u32.try_specialize::(), Err(42)); 71 | assert_eq!(42_i32.try_specialize::(), Err(42)); 72 | assert_eq!("abc".try_specialize::(), Err("abc")); 73 | assert_eq!( 74 | String::from("abc").try_specialize::(), 75 | Ok(String::from("abc")) 76 | ); 77 | } 78 | 79 | #[cfg(feature = "std")] 80 | #[no_mangle] 81 | pub extern "C" fn test_std() { 82 | use alloc::string::String; 83 | use std::path::PathBuf; 84 | 85 | use try_specialize::TrySpecialize; 86 | 87 | assert_eq!(42_u32.try_specialize::(), Err(42)); 88 | assert_eq!(42_i32.try_specialize::(), Err(42)); 89 | assert_eq!("abc".try_specialize::(), Err("abc")); 90 | assert_eq!( 91 | String::from("abc").try_specialize::(), 92 | Err(String::from("abc")) 93 | ); 94 | assert_eq!( 95 | PathBuf::from("abc").try_specialize::(), 96 | Ok(PathBuf::from("abc")) 97 | ); 98 | } 99 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | 9 | defaults: 10 | run: 11 | shell: bash 12 | 13 | env: 14 | CARGO_TERM_COLOR: always 15 | 16 | jobs: 17 | cargo-fmt: 18 | name: Check formatting 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v4 22 | - run: rustup toolchain install nightly --profile minimal --component rustfmt 23 | - run: cargo +nightly fmt --all -- --check 24 | 25 | cargo-clippy: 26 | name: Run linter 27 | runs-on: ubuntu-latest 28 | strategy: 29 | matrix: 30 | rust-toolchain: 31 | - stable 32 | - beta 33 | - nightly 34 | - 1.82.0 35 | steps: 36 | - uses: actions/checkout@v4 37 | - run: rustup toolchain install ${{ matrix.rust-toolchain }} --profile minimal --component clippy 38 | - run: rustup default ${{ matrix.rust-toolchain }} 39 | - run: cargo clippy --all-targets -- -D warnings 40 | - run: cargo clippy --all-targets --no-default-features -- -D warnings 41 | - run: cargo clippy --all-targets --no-default-features --features "alloc" -- -D warnings 42 | - run: cargo clippy --all-targets --no-default-features --features "std" -- -D warnings 43 | - run: cargo clippy --all-targets --no-default-features --features "std,unreliable" -- -D warnings 44 | - run: cargo clippy --all-targets --all-features -- -D warnings 45 | 46 | cargo-test: 47 | name: Test sources 48 | runs-on: ubuntu-latest 49 | strategy: 50 | matrix: 51 | rust-toolchain: 52 | - stable 53 | - beta 54 | - nightly 55 | - 1.82.0 56 | cargo-flags: 57 | - "" 58 | - "--release" 59 | steps: 60 | - uses: actions/checkout@v4 61 | - run: rustup toolchain install ${{ matrix.rust-toolchain }} --profile minimal 62 | - run: rustup default ${{ matrix.rust-toolchain }} 63 | - run: cargo test --all-targets ${{ matrix.cargo-flags }} 64 | - run: cargo test --all-targets ${{ matrix.cargo-flags }} --no-default-features 65 | - run: cargo test --all-targets ${{ matrix.cargo-flags }} --no-default-features --features "alloc" 66 | - run: cargo test --all-targets ${{ matrix.cargo-flags }} --no-default-features --features "std" 67 | - run: cargo test --all-targets ${{ matrix.cargo-flags }} --no-default-features --features "std,unreliable" 68 | - run: cargo test --all-targets ${{ matrix.cargo-flags }} --all-features 69 | 70 | cargo-deny: 71 | name: Check licenses/bans/advisories/sources 72 | runs-on: ubuntu-latest 73 | steps: 74 | - uses: actions/checkout@v4 75 | - run: rustup toolchain install stable --profile minimal 76 | - run: cargo install cargo-deny 77 | - run: cargo deny --workspace --all-features check 78 | -------------------------------------------------------------------------------- /tests/no-alloc-and-no-std/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "no-alloc-and-no-std-test" 3 | version = "0.1.2" 4 | authors = ["Andrey Zheleznov "] 5 | edition = "2021" 6 | publish = false 7 | 8 | [features] 9 | alloc = ["try-specialize/alloc"] 10 | std = ["alloc", "try-specialize/std"] 11 | panic-handler = [] 12 | global-allocator = [] 13 | 14 | [dependencies.try-specialize] 15 | path = "../.." 16 | default-features = false 17 | features = [] 18 | 19 | [profile.dev] 20 | panic = "abort" 21 | 22 | [profile.release] 23 | panic = "abort" 24 | 25 | [lints.rust] 26 | rust_2018_idioms.level = "warn" 27 | rust_2018_idioms.priority = -1 28 | future_incompatible = "warn" 29 | keyword_idents = "warn" 30 | let_underscore = "warn" 31 | meta_variable_misuse = "warn" 32 | missing_abi = "warn" 33 | missing_copy_implementations = "warn" 34 | missing_debug_implementations = "warn" 35 | missing_docs = "warn" 36 | non_ascii_idents = "warn" 37 | refining_impl_trait = "warn" 38 | single_use_lifetimes = "warn" 39 | trivial_casts = "warn" 40 | trivial_numeric_casts = "warn" 41 | unused_crate_dependencies = "warn" 42 | unused_extern_crates = "warn" 43 | unused_import_braces = "warn" 44 | unused_lifetimes = "warn" 45 | unused_qualifications = "warn" 46 | unused_results = "warn" 47 | variant_size_differences = "warn" 48 | 49 | [lints.clippy] 50 | all.level = "warn" 51 | all.priority = -1 52 | pedantic.level = "warn" 53 | pedantic.priority = -1 54 | alloc_instead_of_core = "warn" 55 | allow_attributes = "warn" 56 | allow_attributes_without_reason = "warn" 57 | arithmetic_side_effects = "warn" 58 | as_conversions = "warn" 59 | branches_sharing_code = "warn" 60 | clone_on_ref_ptr = "warn" 61 | dbg_macro = "warn" 62 | debug_assert_with_mut_call = "warn" 63 | decimal_literal_representation = "warn" 64 | default_trait_access = "warn" 65 | empty_line_after_outer_attr = "warn" 66 | empty_structs_with_brackets = "warn" 67 | error_impl_error = "warn" 68 | exit = "warn" 69 | fallible_impl_from = "warn" 70 | filetype_is_file = "warn" 71 | float_cmp_const = "warn" 72 | future_not_send = "warn" 73 | get_unwrap = "warn" 74 | if_then_some_else_none = "warn" 75 | missing_const_for_fn = "warn" 76 | missing_inline_in_public_items = "warn" 77 | modulo_arithmetic = "warn" 78 | multiple_inherent_impl = "warn" 79 | mut_mut = "warn" 80 | nonstandard_macro_braces = "warn" 81 | option_if_let_else = "warn" 82 | panic = "warn" 83 | print_stderr = "warn" 84 | rc_buffer = "warn" 85 | redundant_pub_crate = "warn" 86 | std_instead_of_core = "warn" 87 | string_lit_as_bytes = "warn" 88 | suboptimal_flops = "warn" 89 | suspicious_operation_groupings = "warn" 90 | todo = "warn" 91 | trivial_regex = "warn" 92 | try_err = "warn" 93 | undocumented_unsafe_blocks = "warn" 94 | unimplemented = "warn" 95 | unwrap_used = "warn" 96 | use_self = "warn" 97 | useless_let_if_seq = "warn" 98 | verbose_file_reads = "warn" 99 | wildcard_enum_match_arm = "warn" 100 | module_name_repetitions = "allow" # Items are re-exported to the crate root. 101 | -------------------------------------------------------------------------------- /tests/zero_cost_unreliable.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs, reason = "okay in tests")] 2 | #![expect(unused_crate_dependencies, reason = "okay in tests")] 3 | #![cfg(not(miri))] 4 | #![cfg(not(debug_assertions))] // Requires `profile.opt-level >= 1` 5 | #![cfg(all(feature = "alloc", feature = "unreliable"))] 6 | 7 | use try_specialize::unreliable::{ 8 | impls_clone_weak, impls_copy_weak, impls_eq_weak, impls_lifetime_free_weak, 9 | impls_partial_eq_weak, 10 | }; 11 | 12 | #[cfg(feature = "__test_extern_fn_dce")] 13 | extern "C" { 14 | // This function can not be linked. 15 | // Test checks that the code paths calling this function are optimized away. 16 | fn should_be_unreachable_on_link_time() -> !; 17 | } 18 | 19 | #[inline] 20 | fn expect(value: bool) { 21 | if !value { 22 | #[cfg(feature = "__test_extern_fn_dce")] 23 | unsafe { 24 | should_be_unreachable_on_link_time() 25 | }; 26 | } 27 | } 28 | 29 | #[test] 30 | fn test_zero_cost_unreliable_impls_checks() { 31 | #[derive(Copy, Clone)] 32 | struct Copiable; 33 | #[derive(Clone)] 34 | struct Cloneable; 35 | struct NonCloneable; 36 | 37 | expect(impls_copy_weak::<()>()); 38 | expect(impls_copy_weak::()); 39 | expect(impls_copy_weak::()); 40 | expect(impls_copy_weak::()); 41 | expect(!impls_copy_weak::()); 42 | expect(!impls_copy_weak::()); 43 | 44 | expect(impls_clone_weak::<()>()); 45 | expect(impls_clone_weak::()); 46 | expect(impls_clone_weak::()); 47 | expect(impls_clone_weak::()); 48 | expect(impls_clone_weak::()); 49 | expect(!impls_clone_weak::()); 50 | 51 | expect(impls_eq_weak::<()>()); 52 | expect(impls_eq_weak::()); 53 | expect(!impls_eq_weak::()); 54 | expect(impls_eq_weak::<&String>()); 55 | expect(impls_eq_weak::<&Vec>()); 56 | expect(impls_eq_weak::()); 57 | expect(impls_eq_weak::>()); 58 | expect(impls_eq_weak::<&mut String>()); 59 | expect(impls_eq_weak::<&mut Vec>()); 60 | 61 | expect(impls_partial_eq_weak::<()>()); 62 | expect(impls_partial_eq_weak::()); 63 | expect(impls_partial_eq_weak::()); 64 | expect(impls_partial_eq_weak::<&String>()); 65 | expect(impls_partial_eq_weak::<&Vec>()); 66 | expect(impls_partial_eq_weak::()); 67 | expect(impls_partial_eq_weak::>()); 68 | expect(impls_partial_eq_weak::<&mut String>()); 69 | expect(impls_partial_eq_weak::<&mut Vec>()); 70 | 71 | expect(impls_lifetime_free_weak::<()>()); 72 | expect(impls_lifetime_free_weak::()); 73 | expect(impls_lifetime_free_weak::()); 74 | expect(!impls_lifetime_free_weak::<&String>()); 75 | expect(!impls_lifetime_free_weak::<&Vec>()); 76 | expect(impls_lifetime_free_weak::()); 77 | expect(impls_lifetime_free_weak::>()); 78 | expect(!impls_lifetime_free_weak::<&mut String>()); 79 | expect(!impls_lifetime_free_weak::<&mut Vec>()); 80 | } 81 | -------------------------------------------------------------------------------- /src/unreliable/weak_specialization.rs: -------------------------------------------------------------------------------- 1 | use crate::unreliable::impls_lifetime_free_weak; 2 | use crate::{type_eq_ignore_lifetimes, Specialization}; 3 | 4 | /// A extension trait for [`Specialization`] type for specializing one 5 | /// completely unconstrained type to another completely unconstrained type. 6 | /// 7 | /// # Reliability 8 | /// 9 | /// While it is unlikely, there is still a possibility that the methods of this 10 | /// trait may return false negatives in future Rust versions. 11 | /// 12 | /// The correctness of the results returned by the methods depends on the 13 | /// following: 14 | /// - Documented behavior that if `T` implements `Eq`, two `Rc`s that point to 15 | /// the same allocation are always equal: 16 | /// . 17 | /// - Undocumented behavior that the `Rc::partial_eq` implementation for `T: Eq` 18 | /// will not use `PartialEq::eq` if both `Rc`s point to the same memory 19 | /// location. 20 | /// - The assumption that the undocumented short-circuit behavior described 21 | /// above will be retained for optimization purposes. 22 | /// 23 | /// There is no formal guarantee that the undocumented behavior described above 24 | /// will be retained. If the implementation changes in a future Rust version, 25 | /// the function may return a false negative, that is, it may return `false`, 26 | /// even though `T` implements the trait. However, the implementation guarantees 27 | /// that a false positive result is impossible, i.e., the function will never 28 | /// return true if `T` does not implement the trait in any future Rust version. 29 | /// 30 | /// Details: 31 | /// - , 32 | /// - , 33 | /// - , 34 | /// - . 35 | pub trait WeakSpecialization: Sized { 36 | /// Checks the types `T1` and `T2` for equality and returns the 37 | /// specialization provider if types implement `LifetimeFree` and the types 38 | /// are equal. 39 | /// 40 | /// The [`LifetimeFree`] trait is **not** automatically derived for all 41 | /// lifetime-free types. The library only implements it for standard library 42 | /// types that do not have any lifetime parameters. This function uses 43 | /// `impls_lifetime_free` to check wherever the unconstrained type 44 | /// implements `LifetimeFree` trait or not. 45 | /// 46 | /// [`LifetimeFree`]: crate::LifetimeFree 47 | #[must_use] 48 | fn try_new_if_lifetime_free_weak() -> Option; 49 | } 50 | 51 | #[cfg(feature = "alloc")] 52 | impl WeakSpecialization for Specialization 53 | where 54 | T1: ?Sized, 55 | T2: ?Sized, 56 | { 57 | #[inline] 58 | fn try_new_if_lifetime_free_weak() -> Option { 59 | (impls_lifetime_free_weak::() && type_eq_ignore_lifetimes::()) 60 | // SAFETY: `T1` can be specialized to `T2` if the types are equal with 61 | // erased lifetime and any of it implements `NoLifetime` trait. 62 | .then_some(unsafe { Self::new_unchecked() }) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "try-specialize" 3 | version = "0.1.2" 4 | authors = [ "Andrey Zheleznov " ] 5 | edition = "2021" 6 | rust-version = "1.82.0" 7 | description = "Zero-cost specialization in generic context on stable Rust" 8 | documentation = "https://docs.rs/try-specialize" 9 | readme = "README.md" 10 | repository = "https://github.com/zheland/try-specialize" 11 | license = "MIT OR Apache-2.0" 12 | keywords = [ 13 | "specialization", 14 | "specialize", 15 | "cast", 16 | "no-std", 17 | "stable", 18 | ] 19 | categories = [ 20 | "rust-patterns", 21 | "no-std", 22 | "no-std::no-alloc", 23 | ] 24 | 25 | [features] 26 | default = [ "alloc", "std" ] 27 | alloc = [] 28 | std = [ "alloc" ] 29 | 30 | # Enables API that depends on Rust library undocumented behavior. 31 | unreliable = [] 32 | 33 | # Internal private feature. 34 | # Enables function merging check tests for generated extern test fns. 35 | # Requires `profile.opt-level >= 2` when used with `cargo test`. 36 | __test_extern_fn_merging = [] 37 | 38 | # Internal private feature. 39 | # Enables dead code elimination check tests for generated extern test fns. 40 | # Requires `profile.opt-level >= 2` when used with `cargo test`. 41 | __test_extern_fn_dce = [] 42 | 43 | # Internal private feature. 44 | # Enables tests that require nightly rust toolchain. 45 | # Requires `nightly` toolchain when used with `cargo test`. 46 | __test_nightly = [] 47 | 48 | [dependencies] 49 | 50 | [dev-dependencies] 51 | castaway = "0.2.3" 52 | coe-rs = "0.1.2" 53 | downcast-rs = "2.0.1" 54 | hashbrown = "0.15.4" 55 | paste = "1.0.15" 56 | rustversion = "1.0.21" 57 | specialize = "0.0.3" 58 | specialized-dispatch = "0.2.1" 59 | syllogism = "0.1.3" 60 | syllogism-macro = "0.1.1" 61 | version-sync = "0.9.5" 62 | 63 | [package.metadata.docs.rs] 64 | all-features = true 65 | rustdoc-args = ["--cfg", "docsrs"] 66 | 67 | [lints.rust] 68 | rust_2018_idioms.level = "warn" 69 | rust_2018_idioms.priority = -1 70 | future_incompatible = "warn" 71 | keyword_idents = "warn" 72 | let_underscore = "warn" 73 | meta_variable_misuse = "warn" 74 | missing_abi = "warn" 75 | missing_copy_implementations = "warn" 76 | missing_debug_implementations = "warn" 77 | missing_docs = "warn" 78 | non_ascii_idents = "warn" 79 | refining_impl_trait = "warn" 80 | single_use_lifetimes = "warn" 81 | trivial_casts = "warn" 82 | trivial_numeric_casts = "warn" 83 | unused_crate_dependencies = "warn" 84 | unused_extern_crates = "warn" 85 | unused_import_braces = "warn" 86 | unused_lifetimes = "warn" 87 | unused_qualifications = "warn" 88 | unused_results = "warn" 89 | variant_size_differences = "warn" 90 | 91 | [lints.clippy] 92 | all.level = "warn" 93 | all.priority = -1 94 | pedantic.level = "warn" 95 | pedantic.priority = -1 96 | alloc_instead_of_core = "warn" 97 | allow_attributes = "warn" 98 | allow_attributes_without_reason = "warn" 99 | arithmetic_side_effects = "warn" 100 | as_conversions = "warn" 101 | branches_sharing_code = "warn" 102 | clone_on_ref_ptr = "warn" 103 | dbg_macro = "warn" 104 | debug_assert_with_mut_call = "warn" 105 | decimal_literal_representation = "warn" 106 | default_trait_access = "warn" 107 | empty_line_after_outer_attr = "warn" 108 | empty_structs_with_brackets = "warn" 109 | error_impl_error = "warn" 110 | exit = "warn" 111 | fallible_impl_from = "warn" 112 | filetype_is_file = "warn" 113 | float_cmp_const = "warn" 114 | future_not_send = "warn" 115 | get_unwrap = "warn" 116 | if_then_some_else_none = "warn" 117 | missing_const_for_fn = "warn" 118 | missing_inline_in_public_items = "warn" 119 | modulo_arithmetic = "warn" 120 | multiple_inherent_impl = "warn" 121 | mut_mut = "warn" 122 | nonstandard_macro_braces = "warn" 123 | option_if_let_else = "warn" 124 | panic = "warn" 125 | print_stderr = "warn" 126 | rc_buffer = "warn" 127 | redundant_pub_crate = "warn" 128 | std_instead_of_core = "warn" 129 | string_lit_as_bytes = "warn" 130 | suboptimal_flops = "warn" 131 | suspicious_operation_groupings = "warn" 132 | todo = "warn" 133 | trivial_regex = "warn" 134 | try_err = "warn" 135 | undocumented_unsafe_blocks = "warn" 136 | unimplemented = "warn" 137 | unwrap_used = "warn" 138 | use_self = "warn" 139 | useless_let_if_seq = "warn" 140 | verbose_file_reads = "warn" 141 | wildcard_enum_match_arm = "warn" 142 | module_name_repetitions = "allow" # Items are re-exported to the crate root. 143 | -------------------------------------------------------------------------------- /tests/zero_cost_optimized_results.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs, reason = "okay in tests")] 2 | #![expect(unused_crate_dependencies, reason = "okay in tests")] 3 | #![cfg(not(miri))] 4 | #![cfg(not(debug_assertions))] // Requires `profile.opt-level >= 1` 5 | 6 | use core::convert::identity; 7 | 8 | use try_specialize::TrySpecialize; 9 | 10 | mod common; 11 | 12 | #[rustfmt::skip] 13 | macro_rules! check_tested_fn { 14 | ( 15 | { 16 | { $lib1:tt $lt1:tt $size1:tt $name1:ident : $ty1:ty = $example1:expr } 17 | { $lib2:tt $lt2:tt $size2:tt $name2:ident : $ty2:ty = $example2:expr } 18 | { $kind:tt, $arg:ident => $try_specialize_expr:expr } 19 | { $ok_pat:pat, $fail_pat:pat, $ok:expr, $fail:expr } 20 | } { $counters:path } 21 | ) => {{ 22 | mif! { if (eq $lib1 core) { 23 | #[cfg(feature = "__test_extern_fn_dce")] 24 | extern "C" { 25 | // This function can not be linked. 26 | // Test checks that the code paths calling this function are optimized away. 27 | #[link_name = concat!( 28 | "should_be_unreachable_on_link_time\n", 29 | "ERROR: link time check failed for `", 30 | stringify!($kind), 31 | "_from_", 32 | stringify!($name1), 33 | "_to_", 34 | stringify!($name2), 35 | "_is_ok`\n", 36 | "This check is highly dependent on the optimizer success, ", 37 | "so an error in it does not necessarily mean an error in the ", 38 | "library code." 39 | )] 40 | fn should_be_unreachable_on_link_time() -> !; 41 | } 42 | }} 43 | 44 | if stringify!($name1) == stringify!($name2) { 45 | $counters.num_eq_names += 1; 46 | } else { 47 | $counters.num_ne_names += 1; 48 | } 49 | 50 | match identity::<$ty1>($example1) { 51 | $arg => { 52 | let result = $try_specialize_expr; 53 | 54 | #[allow(unreachable_code, reason = "Okay in tests.")] 55 | if stringify!($name1) == stringify!($name2) { 56 | if result != $ok { 57 | cold_unexpected_specialization_success_panic( 58 | stringify!($name1), 59 | stringify!($name2), 60 | ); 61 | mif! { if (eq $lib1 core) { 62 | // Core types can be checked at compile time. 63 | #[cfg(feature = "__test_extern_fn_dce")] 64 | unsafe { should_be_unreachable_on_link_time() } 65 | }} 66 | } 67 | } else { 68 | if result != $fail { 69 | cold_unexpected_specialization_failure_panic( 70 | stringify!($name1), 71 | stringify!($name2), 72 | ); 73 | mif! { if (eq $lib1 core) { 74 | // Core types can be checked at compile time. 75 | #[cfg(feature = "__test_extern_fn_dce")] 76 | unsafe { should_be_unreachable_on_link_time() } 77 | }} 78 | } 79 | } 80 | } 81 | } 82 | }}; 83 | } 84 | 85 | #[test] 86 | fn test_zero_cost_specialization_fn_merging_and_results() { 87 | struct Counters { 88 | num_eq_names: usize, 89 | num_ne_names: usize, 90 | } 91 | 92 | let mut counters = Counters { 93 | num_eq_names: 0, 94 | num_ne_names: 0, 95 | }; 96 | 97 | macro_rules! with_local_counters_var { 98 | ( $in:tt | $func:ident! $args:tt ) => { 99 | $func!( { $in { counters }} $args ) 100 | } 101 | } 102 | 103 | chain! { 104 | for_all_types! 105 | | for_each_cartesian_square_pair! 106 | | for_each_cartesian_pair_tested_fn! 107 | | with_local_counters_var! 108 | | check_tested_fn! 109 | } 110 | 111 | // Expected values depend on tested types amount. 112 | #[cfg(not(feature = "alloc"))] 113 | let expected = [105, 1198]; 114 | #[cfg(all(feature = "alloc", not(feature = "std")))] 115 | let expected = [119, 1680]; 116 | #[cfg(feature = "std")] 117 | let expected = [131, 2110]; 118 | 119 | assert_eq!(counters.num_eq_names, expected[0]); 120 | assert_eq!(counters.num_ne_names, expected[1]); 121 | } 122 | 123 | // Extracted to separate function to optimize test build times. 124 | #[cold] 125 | #[inline(never)] 126 | fn cold_unexpected_specialization_success_panic(ty1: &'static str, ty2: &'static str) -> ! { 127 | panic!("unexpected specialization success for types {ty1}, {ty2}"); 128 | } 129 | 130 | // Extracted to separate function to optimize test build times. 131 | #[cold] 132 | #[inline(never)] 133 | fn cold_unexpected_specialization_failure_panic(ty1: &'static str, ty2: &'static str) -> ! { 134 | panic!("unexpected specialization failure for types {ty1}, {ty2}"); 135 | } 136 | -------------------------------------------------------------------------------- /examples/encode.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs, reason = "okay in examples")] 2 | #![expect( 3 | clippy::missing_errors_doc, 4 | unused_crate_dependencies, 5 | reason = "okay in examples" 6 | )] 7 | 8 | //! This example implements custom data encoders and decoders with customizable 9 | //! per-type encoding and decoding errors and optimized byte array encoding and 10 | //! decoding. 11 | //! 12 | //! Both encoder and decoder tries to specialize `[T; N]` to `[u8; N]` 13 | //! in order to branch to better-optimized bytes slice reader and writers. 14 | //! 15 | //! Also it uses: 16 | //! - `Specialization::rev` to convert read `[u8; N]` back to `[T; N]`. 17 | //! - `Specialization::map` to convert read `<[u8; N]>::Error` to `<[T; 18 | //! N]>::Error`. 19 | 20 | use core::convert::Infallible; 21 | use core::{array, slice}; 22 | use std::io::{self, Read, Write}; 23 | 24 | use try_specialize::{Specialization, TypeFn}; 25 | 26 | pub trait Encode { 27 | type EncodeError; 28 | fn encode_to(&self, writer: &mut W) -> Result<(), Self::EncodeError> 29 | where 30 | W: ?Sized + Write; 31 | } 32 | 33 | pub trait Decode: Sized { 34 | type DecodeError; 35 | fn decode_from(reader: &mut R) -> Result 36 | where 37 | R: ?Sized + Read; 38 | } 39 | 40 | impl Encode for () { 41 | type EncodeError = Infallible; 42 | 43 | #[inline] 44 | fn encode_to(&self, _writer: &mut W) -> Result<(), Self::EncodeError> 45 | where 46 | W: ?Sized + Write, 47 | { 48 | Ok(()) 49 | } 50 | } 51 | 52 | impl Decode for () { 53 | type DecodeError = Infallible; 54 | 55 | #[inline] 56 | fn decode_from(_reader: &mut R) -> Result 57 | where 58 | R: ?Sized + Read, 59 | { 60 | Ok(()) 61 | } 62 | } 63 | 64 | impl Encode for Box 65 | where 66 | T: Encode, 67 | { 68 | type EncodeError = T::EncodeError; 69 | 70 | #[inline] 71 | fn encode_to(&self, writer: &mut W) -> Result<(), Self::EncodeError> 72 | where 73 | W: ?Sized + Write, 74 | { 75 | T::encode_to(self, writer) 76 | } 77 | } 78 | 79 | impl Decode for Box 80 | where 81 | T: Decode, 82 | { 83 | type DecodeError = T::DecodeError; 84 | 85 | #[inline] 86 | fn decode_from(reader: &mut R) -> Result 87 | where 88 | R: ?Sized + Read, 89 | { 90 | Ok(Self::new(T::decode_from(reader)?)) 91 | } 92 | } 93 | 94 | impl Encode for u8 { 95 | type EncodeError = io::Error; 96 | 97 | #[inline] 98 | fn encode_to(&self, writer: &mut W) -> Result<(), Self::EncodeError> 99 | where 100 | W: ?Sized + Write, 101 | { 102 | writer.write_all(&[*self])?; 103 | Ok(()) 104 | } 105 | } 106 | 107 | impl Decode for u8 { 108 | type DecodeError = io::Error; 109 | 110 | #[inline] 111 | fn decode_from(reader: &mut R) -> Result 112 | where 113 | R: ?Sized + Read, 114 | { 115 | let mut byte: Self = 0; 116 | reader.read_exact(slice::from_mut(&mut byte))?; 117 | Ok(byte) 118 | } 119 | } 120 | 121 | impl Encode for [T] 122 | where 123 | T: Encode, 124 | { 125 | type EncodeError = T::EncodeError; 126 | 127 | #[inline] 128 | fn encode_to(&self, writer: &mut W) -> Result<(), Self::EncodeError> 129 | where 130 | W: ?Sized + Write, 131 | { 132 | if let Some(spec) = Specialization::<[T], [u8]>::try_new() { 133 | // Specialize self from `[T; N]` to `[u32; N]` 134 | let bytes: &[u8] = spec.specialize_ref(self); 135 | // Map type specialization to its associated error specialization. 136 | let spec_err = spec.rev().map::(); 137 | writer 138 | .write_all(bytes) 139 | // Specialize error from `io::Error` to `Self::EncodeError`. 140 | .map_err(|err| spec_err.specialize(err))?; 141 | } else { 142 | for item in self { 143 | item.encode_to(writer)?; 144 | } 145 | } 146 | Ok(()) 147 | } 148 | } 149 | 150 | impl Encode for [T; N] 151 | where 152 | T: Encode, 153 | { 154 | type EncodeError = T::EncodeError; 155 | 156 | #[inline] 157 | fn encode_to(&self, writer: &mut W) -> Result<(), Self::EncodeError> 158 | where 159 | W: ?Sized + Write, 160 | { 161 | self.as_slice().encode_to(writer) 162 | } 163 | } 164 | 165 | impl Decode for [T; N] 166 | where 167 | T: Decode + Default, 168 | { 169 | type DecodeError = T::DecodeError; 170 | 171 | #[inline] 172 | fn decode_from(reader: &mut R) -> Result 173 | where 174 | R: ?Sized + Read, 175 | { 176 | let spec = Specialization::<[T; N], [u8; N]>::try_new(); 177 | 178 | if let Some(spec) = spec { 179 | let mut array = [0; N]; 180 | reader 181 | .read_exact(&mut array) 182 | // Specialize `<[u8; N]>::Error` to `<[T; N]>::Error` 183 | .map_err(|err| spec.rev().map::().specialize(err))?; 184 | // Specialize `[u8; N]` to `[T; N]` 185 | let array = spec.rev().specialize(array); 186 | Ok(array) 187 | } else { 188 | // In real code it can be done without `Default` bound. 189 | // But then the code would be unnecessarily complex for the example. 190 | let mut array = array::from_fn(|_| T::default()); 191 | for item in &mut array { 192 | *item = T::decode_from(reader)?; 193 | } 194 | Ok(array) 195 | } 196 | } 197 | } 198 | 199 | struct MapToEncodeError; 200 | 201 | impl TypeFn for MapToEncodeError 202 | where 203 | T: ?Sized + Encode, 204 | { 205 | type Output = T::EncodeError; 206 | } 207 | 208 | struct MapToDecodeError; 209 | impl TypeFn for MapToDecodeError 210 | where 211 | T: Decode, 212 | { 213 | type Output = T::DecodeError; 214 | } 215 | 216 | #[expect(clippy::unwrap_used, reason = "okay in examples")] 217 | fn main() { 218 | let mut array_buf = [0; 8]; 219 | let mut buf = &mut array_buf[..]; 220 | [1_u8, 2, 3].encode_to(&mut buf).unwrap(); 221 | 4_u8.encode_to(&mut buf).unwrap(); 222 | [(), (), (), ()].encode_to(&mut buf).unwrap(); 223 | [5_u8, 6, 7, 8].map(Box::new).encode_to(&mut buf).unwrap(); 224 | assert!(9_u8.encode_to(&mut buf).is_err()); 225 | assert!([9_u8, 10].encode_to(&mut buf).is_err()); 226 | ().encode_to(&mut buf).unwrap(); 227 | [(), (), ()].encode_to(&mut buf).unwrap(); 228 | assert!([9_u8, 10].map(Box::new).encode_to(&mut buf).is_err()); 229 | assert_eq!(array_buf, [1, 2, 3, 4, 5, 6, 7, 8]); 230 | 231 | let buf = &mut array_buf.as_slice(); 232 | assert_eq!(u8::decode_from(buf).unwrap(), 1); 233 | assert_eq!(<[u8; 4]>::decode_from(buf).unwrap(), [2, 3, 4, 5]); 234 | assert_eq!(<[(); 16]>::decode_from(buf).unwrap(), [(); 16]); 235 | assert_eq!(<[u8; 1]>::decode_from(buf).unwrap(), [6]); 236 | assert_eq!( 237 | <[Box; 2]>::decode_from(buf).unwrap(), 238 | [Box::new(7), Box::new(8)] 239 | ); 240 | assert!(u8::decode_from(buf).is_err()); 241 | assert!(<[u8; 1]>::decode_from(buf).is_err()); 242 | assert_eq!(<[(); 2]>::decode_from(buf).unwrap(), [(); 2]); 243 | assert!(<[Box; 2]>::decode_from(buf).is_err()); 244 | } 245 | -------------------------------------------------------------------------------- /src/type_eq.rs: -------------------------------------------------------------------------------- 1 | use core::any::TypeId; 2 | use core::marker::PhantomData; 3 | 4 | use crate::LifetimeFree; 5 | 6 | /// Returns `true` if the `T1` and `T2` types are equal. 7 | /// 8 | /// This method requires only `T2` to implement [`LifetimeFree`] trait. 9 | /// 10 | /// Library tests ensure that type comparisons are performed at compile time and 11 | /// are fully optimized with no runtime cost at `opt-level >= 1`. Note that the 12 | /// release profile uses `opt-level = 3` by default. 13 | /// 14 | /// [`LifetimeFree`]: crate::LifetimeFree 15 | /// 16 | /// # Examples 17 | /// 18 | /// ```rust 19 | /// use try_specialize::type_eq; 20 | /// 21 | /// assert!(type_eq::<(), ()>()); 22 | /// assert!(!type_eq::<(), u8>()); 23 | /// 24 | /// assert!(type_eq::()); 25 | /// assert!(!type_eq::()); 26 | /// 27 | /// assert!(type_eq::<[u8], [u8]>()); 28 | /// assert!(type_eq::<[u8; 8], [u8; 8]>()); 29 | /// assert!(!type_eq::<[u8; 8], [u8]>()); 30 | /// assert!(!type_eq::<[u8], [u8; 8]>()); 31 | /// assert!(!type_eq::<[u8; 8], [u8; 16]>()); 32 | /// 33 | /// assert!(type_eq::()); 34 | /// assert!(type_eq::<[u8], [u8]>()); 35 | /// assert!(!type_eq::()); 36 | /// assert!(!type_eq::<[u8], [u8; 4]>()); 37 | /// 38 | /// # #[cfg(feature = "alloc")] 39 | /// assert!(type_eq::()); 40 | /// ``` 41 | #[inline] 42 | #[must_use] 43 | pub fn type_eq() -> bool 44 | where 45 | T1: ?Sized, 46 | T2: ?Sized + LifetimeFree, 47 | { 48 | type_eq_ignore_lifetimes::() 49 | } 50 | 51 | /// Returns `true` if the `T1` and `T2` static types are equal. 52 | /// 53 | /// This method requires both `T1` and `T2` to be `'static`. 54 | /// 55 | /// Library tests ensure that type comparisons are performed at compile time and 56 | /// are fully optimized with no runtime cost at `opt-level >= 1`. Note that the 57 | /// release profile uses `opt-level = 3` by default. 58 | /// 59 | /// # Examples 60 | /// 61 | /// ```rust 62 | /// use try_specialize::static_type_eq; 63 | /// 64 | /// fn static_type_eq_of_vals(_: T1, _: T2) -> bool { 65 | /// static_type_eq::() 66 | /// } 67 | /// 68 | /// assert!(static_type_eq::<(), ()>()); 69 | /// assert!(!static_type_eq::<(), u8>()); 70 | /// 71 | /// assert!(static_type_eq::()); 72 | /// assert!(!static_type_eq::()); 73 | /// 74 | /// assert!(static_type_eq::<[u8], [u8]>()); 75 | /// assert!(static_type_eq::<[u8; 8], [u8; 8]>()); 76 | /// assert!(!static_type_eq::<[u8; 8], [u8]>()); 77 | /// assert!(!static_type_eq::<[u8], [u8; 8]>()); 78 | /// assert!(!static_type_eq::<[u8; 8], [u8; 16]>()); 79 | /// 80 | /// assert!(static_type_eq::<&'static str, &'static str>()); 81 | /// assert!(static_type_eq_of_vals("foo", "bar")); 82 | /// 83 | /// assert!(static_type_eq::()); 84 | /// assert!(static_type_eq::<[u8], [u8]>()); 85 | /// assert!(!static_type_eq::()); 86 | /// assert!(!static_type_eq::<[u8], [u8; 4]>()); 87 | /// 88 | /// # #[cfg(feature = "alloc")] 89 | /// assert!(static_type_eq::()); 90 | /// ``` 91 | #[inline] 92 | #[must_use] 93 | pub fn static_type_eq() -> bool 94 | where 95 | T1: ?Sized + 'static, 96 | T2: ?Sized + 'static, 97 | { 98 | TypeId::of::() == TypeId::of::() 99 | } 100 | 101 | /// Returns `true` if the `Self` and `T` types are equal ignoring their 102 | /// lifetimes. 103 | /// 104 | /// Note that all the lifetimes are erased and not accounted for. 105 | /// 106 | /// Library tests ensure that type comparisons are performed at compile time and 107 | /// are fully optimized with no runtime cost at `opt-level >= 1`. Note that the 108 | /// release profile uses `opt-level = 3` by default. 109 | /// 110 | /// # Examples 111 | /// 112 | /// ```rust 113 | /// use core::hint::black_box; 114 | /// 115 | /// use try_specialize::type_eq_ignore_lifetimes; 116 | /// 117 | /// const STATIC_STR: &'static str = "foo"; 118 | /// 119 | /// assert!(type_eq_ignore_lifetimes::<(), ()>()); 120 | /// assert!(!type_eq_ignore_lifetimes::<(), u8>()); 121 | /// 122 | /// assert!(type_eq_ignore_lifetimes::()); 123 | /// assert!(!type_eq_ignore_lifetimes::()); 124 | /// 125 | /// assert!(type_eq_ignore_lifetimes::<[u8], [u8]>()); 126 | /// assert!(type_eq_ignore_lifetimes::<[u8; 8], [u8; 8]>()); 127 | /// assert!(!type_eq_ignore_lifetimes::<[u8; 8], [u8]>()); 128 | /// assert!(!type_eq_ignore_lifetimes::<[u8], [u8; 8]>()); 129 | /// assert!(!type_eq_ignore_lifetimes::<[u8; 8], [u8; 16]>()); 130 | /// 131 | /// assert!(type_eq_ignore_lifetimes::()); 132 | /// assert!(type_eq_ignore_lifetimes::<[u8], [u8]>()); 133 | /// assert!(!type_eq_ignore_lifetimes::()); 134 | /// assert!(!type_eq_ignore_lifetimes::<[u8], [u8; 4]>()); 135 | /// 136 | /// assert!(type_eq_ignore_lifetimes::<&'static str, &'static str>()); 137 | /// assert!(type_eq_ignore_lifetimes_of_vals("foo", "bar")); 138 | /// assert!(type_eq_ignore_lifetimes_of_vals( 139 | /// STATIC_STR, 140 | /// format!("bar").as_str() 141 | /// )); 142 | /// 143 | /// # #[cfg(feature = "alloc")] 144 | /// scoped_test(); 145 | /// 146 | /// # #[cfg(feature = "alloc")] 147 | /// fn scoped_test() { 148 | /// let local_str = format!("{}{}", black_box("foo"), black_box("bar")); 149 | /// let local_str = local_str.as_str(); // Non-static str. 150 | /// assert!(type_eq_ignore_lifetimes_of_vals(STATIC_STR, local_str)); 151 | /// } 152 | /// 153 | /// fn type_eq_ignore_lifetimes_of_vals(_: T1, _: T2) -> bool { 154 | /// type_eq_ignore_lifetimes::() 155 | /// } 156 | /// ``` 157 | #[inline] 158 | #[must_use] 159 | pub fn type_eq_ignore_lifetimes() -> bool 160 | where 161 | T1: ?Sized, 162 | T2: ?Sized, 163 | { 164 | non_static_type_id::() == non_static_type_id::() 165 | } 166 | 167 | /// Returns the `TypeId` of the type this generic function has been instantiated 168 | /// with ignoring lifetimes. 169 | /// 170 | /// Based on original implementation written by @dtolnay: 171 | /// 172 | /// 173 | /// # Safety 174 | /// 175 | /// This function doesn't validate type lifetimes. Lifetimes must be validated 176 | /// separately. 177 | #[inline] 178 | #[must_use] 179 | fn non_static_type_id() -> TypeId 180 | where 181 | T: ?Sized, 182 | { 183 | trait NonStaticAny { 184 | fn get_type_id(&self) -> TypeId 185 | where 186 | Self: 'static; 187 | } 188 | 189 | impl NonStaticAny for PhantomData 190 | where 191 | T: ?Sized, 192 | { 193 | #[inline] 194 | fn get_type_id(&self) -> TypeId 195 | where 196 | Self: 'static, 197 | { 198 | TypeId::of::() 199 | } 200 | } 201 | 202 | // SAFETY: Types differs only by lifetimes. Lifetimes are the compile-time 203 | // feature and are completely erased before the code generation stage. 204 | // The transmuted type is only used to get its id and does not outlive the 205 | // `get_type_id` function. 206 | NonStaticAny::get_type_id(unsafe { 207 | core::mem::transmute::<&dyn NonStaticAny, &(dyn NonStaticAny + 'static)>(&PhantomData::) 208 | }) 209 | } 210 | 211 | #[cfg(test)] 212 | mod tests { 213 | #[cfg(feature = "alloc")] 214 | use alloc::string::String; 215 | use core::cell::Ref; 216 | use core::hint::black_box; 217 | 218 | use crate::type_eq_ignore_lifetimes; 219 | 220 | fn type_eq_ignore_lifetimes_of_vals(_: T1, _: T2) -> bool { 221 | type_eq_ignore_lifetimes::() 222 | } 223 | 224 | #[cfg(feature = "alloc")] 225 | #[inline(never)] 226 | fn make_dummy_string() -> String { 227 | #[expect( 228 | clippy::arithmetic_side_effects, 229 | reason = "false positive for string type" 230 | )] 231 | black_box(String::from("foo") + "bar") 232 | } 233 | 234 | #[test] 235 | fn test_type_eq_ignore_lifetimes_with_local() { 236 | test_type_eq_ignore_lifetimes_with_local_impl(&black_box(123)); 237 | } 238 | 239 | #[expect( 240 | clippy::trivially_copy_pass_by_ref, 241 | reason = "ref is necessary for the test" 242 | )] 243 | fn test_type_eq_ignore_lifetimes_with_local_impl<'a>(local1: &'a u32) { 244 | assert!(type_eq_ignore_lifetimes::<&'static str, &'a str>()); 245 | assert!(type_eq_ignore_lifetimes::<&'a str, &'static str>()); 246 | assert!(type_eq_ignore_lifetimes::<&'a str, &'a str>()); 247 | assert!(!type_eq_ignore_lifetimes::<&'a str, &'a u32>()); 248 | 249 | assert!(type_eq_ignore_lifetimes::, Ref<'a, u32>>()); 250 | assert!(type_eq_ignore_lifetimes::, Ref<'static, u32>>()); 251 | assert!(type_eq_ignore_lifetimes::, Ref<'a, u32>>()); 252 | assert!(!type_eq_ignore_lifetimes::, Ref<'a, u64>>()); 253 | 254 | let local2: &u32 = &black_box(234); 255 | let local3: &i32 = &black_box(234); 256 | assert!(type_eq_ignore_lifetimes_of_vals(local1, local2)); 257 | assert!(!type_eq_ignore_lifetimes_of_vals(local2, local3)); 258 | assert!(!type_eq_ignore_lifetimes_of_vals(local1, local3)); 259 | } 260 | 261 | #[cfg(feature = "alloc")] 262 | #[test] 263 | fn test_type_eq_ignore_lifetimes_alloc() { 264 | let string = make_dummy_string(); 265 | let non_static_str: &str = string.as_str(); 266 | assert!(type_eq_ignore_lifetimes_of_vals("foo", non_static_str)); 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /src/unreliable/try_specialize_weak.rs: -------------------------------------------------------------------------------- 1 | use crate::unreliable::WeakSpecialization; 2 | use crate::Specialization; 3 | 4 | /// A extension trait for [`TrySpecialize`] trait for specializing one 5 | /// completely unconstrained type to another completely unconstrained type. 6 | /// 7 | /// This trait uses [`Specialization`] helper struct and [`WeakSpecialization`] 8 | /// helper trait to perform all conversions. You can use [`Specialization`] and 9 | /// [`WeakSpecialization`] directly if you need to perform more complex 10 | /// specialization cases or to cache the specializable ability. 11 | /// 12 | /// # Reliability 13 | /// 14 | /// While it is unlikely, there is still a possibility that the methods of this 15 | /// trait may return false negatives in future Rust versions. 16 | /// 17 | /// The correctness of the results returned by the methods depends on the 18 | /// following: 19 | /// - Documented behavior that if `T` implements `Eq`, two `Rc`s that point to 20 | /// the same allocation are always equal: 21 | /// . 22 | /// - Undocumented behavior that the `Rc::partial_eq` implementation for `T: Eq` 23 | /// will not use `PartialEq::eq` if both `Rc`s point to the same memory 24 | /// location. 25 | /// - The assumption that the undocumented short-circuit behavior described 26 | /// above will be retained for optimization purposes. 27 | /// 28 | /// There is no formal guarantee that the undocumented behavior described above 29 | /// will be retained. If the implementation changes in a future Rust version, 30 | /// the function may return a false negative, that is, it may return `false`, 31 | /// even though `T` implements the trait. However, the implementation guarantees 32 | /// that a false positive result is impossible, i.e., the function will never 33 | /// return true if `T` does not implement the trait in any future Rust version. 34 | /// 35 | /// Details: 36 | /// - , 37 | /// - , 38 | /// - , 39 | /// - . 40 | /// 41 | /// [`TrySpecialize`]: crate::TrySpecialize 42 | pub trait TrySpecializeWeak { 43 | /// Attempts to specialize `Self` as `T` checking that underlying `Self` 44 | /// type implements [`LifetimeFree`]. 45 | /// 46 | /// Returns `T` as `Self` wrapped in `Ok` if `Self` and `T` types are 47 | /// identical and [`impls_lifetime_free_weak::()`] check succeed. 48 | /// Otherwise, it returns `T` wrapped in `Err`. 49 | /// 50 | /// The [`LifetimeFree`] trait is **not** automatically derived for all 51 | /// lifetime-free types. The library only implements it for standard library 52 | /// types that do not have any lifetime parameters. Prefer to specialize to 53 | /// specific [`LifetimeFree`] type if possible with 54 | /// [`TrySpecialize::try_specialize`]. 55 | /// 56 | /// [`LifetimeFree`]: crate::LifetimeFree 57 | /// [`TrySpecialize::try_specialize`]: crate::TrySpecialize::try_specialize 58 | /// [`impls_lifetime_free_weak::()`]: crate::unreliable::impls_lifetime_free_weak 59 | /// 60 | /// # Examples 61 | /// 62 | /// ```rust 63 | /// # #[cfg(all(feature = "alloc", feature = "unreliable"))] { 64 | /// use core::ops::Add; 65 | /// 66 | /// use try_specialize::unreliable::TrySpecializeWeak; 67 | /// 68 | /// fn add_if_same_ty_weak(left: T1, right: T2) -> Result 69 | /// where 70 | /// T1: Add, 71 | /// { 72 | /// match right.try_specialize_if_lifetime_free_weak() { 73 | /// Ok(right) => Ok(left + right), 74 | /// Err(right) => Err((left, right)), 75 | /// } 76 | /// } 77 | /// 78 | /// assert_eq!(add_if_same_ty_weak(42_u32, 123_u32), Ok(165)); 79 | /// assert_eq!( 80 | /// add_if_same_ty_weak(123.456_f64, 123_u32), 81 | /// Err((123.456_f64, 123_u32)) 82 | /// ); 83 | /// assert_eq!( 84 | /// add_if_same_ty_weak(123.456_f64, 1000.111_f64), 85 | /// Ok(1123.567_f64) 86 | /// ); 87 | /// # } 88 | #[expect(clippy::missing_errors_doc, reason = "already described")] 89 | #[inline] 90 | fn try_specialize_if_lifetime_free_weak(self) -> Result 91 | where 92 | Self: Sized, 93 | { 94 | if let Some(spec) = Specialization::try_new_if_lifetime_free_weak() { 95 | Ok(spec.specialize(self)) 96 | } else { 97 | Err(self) 98 | } 99 | } 100 | 101 | /// Attempts to specialize `&Self` as `&T` checking that underlying `Self` 102 | /// type implements [`LifetimeFree`]. 103 | /// 104 | /// The [`LifetimeFree`] trait is **not** automatically derived for all 105 | /// lifetime-free types. The library only implements it for standard library 106 | /// types that do not have any lifetime parameters. Prefer to specialize to 107 | /// specific [`LifetimeFree`] type if possible with 108 | /// [`TrySpecialize::try_specialize_ref`]. 109 | /// 110 | /// [`LifetimeFree`]: crate::LifetimeFree 111 | /// [`TrySpecialize::try_specialize_ref`]: crate::TrySpecialize::try_specialize_ref 112 | /// [`impls_lifetime_free_weak::()`]: crate::unreliable::impls_lifetime_free_weak 113 | /// 114 | /// # Examples 115 | /// 116 | /// ```rust 117 | /// # #[cfg(all(feature = "alloc", feature = "unreliable"))] { 118 | /// use try_specialize::unreliable::TrySpecializeWeak; 119 | /// 120 | /// fn eq_if_same_ty_weak(left: &T1, right: &T2) -> Option 121 | /// where 122 | /// T1: PartialEq, 123 | /// { 124 | /// right. 125 | /// try_specialize_ref_if_lifetime_free_weak(). 126 | /// map(|right| left == right) 127 | /// } 128 | /// 129 | /// assert_eq!(eq_if_same_ty_weak(&42_u32, &42_u32), Some(true)); 130 | /// assert_eq!(eq_if_same_ty_weak(&42_u32, &123_u32), Some(false)); 131 | /// assert_eq!(eq_if_same_ty_weak(&123.456_f64, &123_u32), None); 132 | /// # } 133 | #[inline] 134 | fn try_specialize_ref_if_lifetime_free_weak(&self) -> Option<&T> 135 | where 136 | T: ?Sized, 137 | { 138 | Specialization::try_new_if_lifetime_free_weak().map(|spec| spec.specialize_ref(self)) 139 | } 140 | 141 | /// Attempts to specialize `&mut Self` as `&mut T` checking that underlying 142 | /// `Self` type implements [`LifetimeFree`]. 143 | /// 144 | /// The [`LifetimeFree`] trait is **not** automatically derived for all 145 | /// lifetime-free types. The library only implements it for standard library 146 | /// types that do not have any lifetime parameters. Prefer to specialize to 147 | /// specific [`LifetimeFree`] type if possible with 148 | /// [`TrySpecialize::try_specialize_mut`]. 149 | /// 150 | /// [`LifetimeFree`]: crate::LifetimeFree 151 | /// [`TrySpecialize::try_specialize_mut`]: crate::TrySpecialize::try_specialize_mut 152 | /// [`impls_lifetime_free_weak::()`]: crate::unreliable::impls_lifetime_free_weak 153 | /// 154 | /// # Examples 155 | /// 156 | /// ```rust 157 | /// # #[cfg(all(feature = "alloc", feature = "unreliable"))] { 158 | /// use core::ops::AddAssign; 159 | /// 160 | /// use try_specialize::unreliable::TrySpecializeWeak; 161 | /// 162 | /// fn transfer_if_same_ty_weak(left: &mut T1, right: &mut T2) -> bool 163 | /// where 164 | /// T1: AddAssign + Default, 165 | /// { 166 | /// match right.try_specialize_mut_if_lifetime_free_weak() { 167 | /// Some(right) => { 168 | /// *left += core::mem::take(right); 169 | /// true 170 | /// }, 171 | /// None => false, 172 | /// } 173 | /// } 174 | /// 175 | /// let mut v1: u32 = 10; 176 | /// let mut v2: f64 = 20.0; 177 | /// let mut v3: u32 = 40; 178 | /// let mut v4: f64 = 80.0; 179 | /// 180 | /// assert_eq!(transfer_if_same_ty_weak(&mut v1, &mut v2), false); 181 | /// assert_eq!((v1, v2, v3, v4), (10, 20.0, 40, 80.0)); 182 | /// assert_eq!(transfer_if_same_ty_weak(&mut v1, &mut v3), true); 183 | /// assert_eq!((v1, v2, v3, v4), (50, 20.0, 0, 80.0)); 184 | /// assert_eq!(transfer_if_same_ty_weak(&mut v1, &mut v4), false); 185 | /// assert_eq!((v1, v2, v3, v4), (50, 20.0, 0, 80.0)); 186 | /// assert_eq!(transfer_if_same_ty_weak(&mut v2, &mut v3), false); 187 | /// assert_eq!((v1, v2, v3, v4), (50, 20.0, 0, 80.0)); 188 | /// assert_eq!(transfer_if_same_ty_weak(&mut v2, &mut v4), true); 189 | /// assert_eq!((v1, v2, v3, v4), (50, 100.0, 0, 0.0)); 190 | /// # } 191 | #[inline] 192 | fn try_specialize_mut_if_lifetime_free_weak(&mut self) -> Option<&mut T> 193 | where 194 | T: ?Sized, 195 | { 196 | Specialization::try_new_if_lifetime_free_weak().map(|spec| spec.specialize_mut(self)) 197 | } 198 | } 199 | 200 | impl TrySpecializeWeak for T where T: ?Sized {} 201 | 202 | #[cfg(test)] 203 | mod tests { 204 | #[cfg(feature = "alloc")] 205 | use crate::unreliable::TrySpecializeWeak; 206 | 207 | #[cfg(feature = "alloc")] 208 | #[test] 209 | fn test_try_specialize_if_lifetime_free() { 210 | fn try_spec_erased(value: T1) -> Result { 211 | value.try_specialize_if_lifetime_free_weak() 212 | } 213 | 214 | assert_eq!(try_spec_erased::<_, u32>(123_u32), Ok(123_u32)); 215 | assert_eq!(try_spec_erased::<_, i32>(123_u32), Err(123_u32)); 216 | 217 | assert_eq!(try_spec_erased::<_, u32>("abc"), Err("abc")); 218 | // '&'static str' is not [`LifetimeFree`] so the specialization failed 219 | // as expected even if the types are equal. 220 | assert_eq!(try_spec_erased::<_, &'static str>("abc"), Err("abc")); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /src/type_fn.rs: -------------------------------------------------------------------------------- 1 | /// A trait that defines a mapping between an input type and an output type. 2 | /// 3 | /// This trait is used to map specialization types to other wrapped or 4 | /// associated specialization types. If generic types `T1` and `T2` are proven 5 | /// to be equivalent, then types `>::Output` and 6 | /// `>::Output` are also equivalent. 7 | /// 8 | /// This trait can also be used to specialize 9 | /// generics of the third-party library types that do not implement 10 | /// [`LifetimeFree`]. 11 | /// 12 | /// [`LifetimeFree`]: crate::LifetimeFree 13 | /// 14 | /// # Examples 15 | /// 16 | /// Custom data encoders and decoders with customizable per-type encoding 17 | /// and decoding errors and optimized byte array encoding and decoding. 18 | /// Full example code is available at 19 | /// [`examples/encode.rs`](https://github.com/zheland/try-specialize/blob/v0.1.2/examples/encode.rs). 20 | /// ```rust 21 | /// # use core::convert::Infallible; 22 | /// # use core::{array, slice}; 23 | /// # use std::io::{self, Read, Write}; 24 | /// # 25 | /// # use try_specialize::{Specialization, TypeFn}; 26 | /// # 27 | /// # pub trait Encode { 28 | /// # type EncodeError; 29 | /// # fn encode_to(&self, writer: &mut W) -> Result<(), Self::EncodeError> 30 | /// # where 31 | /// # W: ?Sized + Write; 32 | /// # } 33 | /// # 34 | /// # pub trait Decode: Sized { 35 | /// # type DecodeError; 36 | /// # fn decode_from(reader: &mut R) -> Result 37 | /// # where 38 | /// # R: ?Sized + Read; 39 | /// # } 40 | /// # 41 | /// # impl Encode for () { 42 | /// # type EncodeError = Infallible; 43 | /// # 44 | /// # #[inline] 45 | /// # fn encode_to(&self, _writer: &mut W) -> Result<(), Self::EncodeError> 46 | /// # where 47 | /// # W: ?Sized + Write, 48 | /// # { 49 | /// # Ok(()) 50 | /// # } 51 | /// # } 52 | /// # 53 | /// # impl Decode for () { 54 | /// # type DecodeError = Infallible; 55 | /// # 56 | /// # #[inline] 57 | /// # fn decode_from(_reader: &mut R) -> Result 58 | /// # where 59 | /// # R: ?Sized + Read, 60 | /// # { 61 | /// # Ok(()) 62 | /// # } 63 | /// # } 64 | /// # 65 | /// # impl Encode for Box 66 | /// # where 67 | /// # T: Encode, 68 | /// # { 69 | /// # type EncodeError = T::EncodeError; 70 | /// # 71 | /// # #[inline] 72 | /// # fn encode_to(&self, writer: &mut W) -> Result<(), Self::EncodeError> 73 | /// # where 74 | /// # W: ?Sized + Write, 75 | /// # { 76 | /// # T::encode_to(self, writer) 77 | /// # } 78 | /// # } 79 | /// # 80 | /// # impl Decode for Box 81 | /// # where 82 | /// # T: Decode, 83 | /// # { 84 | /// # type DecodeError = T::DecodeError; 85 | /// # 86 | /// # #[inline] 87 | /// # fn decode_from(reader: &mut R) -> Result 88 | /// # where 89 | /// # R: ?Sized + Read, 90 | /// # { 91 | /// # Ok(Self::new(T::decode_from(reader)?)) 92 | /// # } 93 | /// # } 94 | /// # 95 | /// # impl Encode for u8 { 96 | /// # type EncodeError = io::Error; 97 | /// # 98 | /// # #[inline] 99 | /// # fn encode_to(&self, writer: &mut W) -> Result<(), Self::EncodeError> 100 | /// # where 101 | /// # W: ?Sized + Write, 102 | /// # { 103 | /// # writer.write_all(&[*self])?; 104 | /// # Ok(()) 105 | /// # } 106 | /// # } 107 | /// # 108 | /// # impl Decode for u8 { 109 | /// # type DecodeError = io::Error; 110 | /// # 111 | /// # #[inline] 112 | /// # fn decode_from(reader: &mut R) -> Result 113 | /// # where 114 | /// # R: ?Sized + Read, 115 | /// # { 116 | /// # let mut byte: Self = 0; 117 | /// # reader.read_exact(slice::from_mut(&mut byte))?; 118 | /// # Ok(byte) 119 | /// # } 120 | /// # } 121 | /// // ... 122 | /// 123 | /// impl Encode for [T] 124 | /// where 125 | /// T: Encode, 126 | /// { 127 | /// type EncodeError = T::EncodeError; 128 | /// 129 | /// #[inline] 130 | /// fn encode_to(&self, writer: &mut W) -> Result<(), Self::EncodeError> 131 | /// where 132 | /// W: ?Sized + Write, 133 | /// { 134 | /// if let Some(spec) = Specialization::<[T], [u8]>::try_new() { 135 | /// // Specialize self from `[T; N]` to `[u32; N]` 136 | /// let bytes: &[u8] = spec.specialize_ref(self); 137 | /// // Map type specialization to its associated error specialization. 138 | /// let spec_err = spec.rev().map::(); 139 | /// writer 140 | /// .write_all(bytes) 141 | /// // Specialize error from `io::Error` to `Self::EncodeError`. 142 | /// .map_err(|err| spec_err.specialize(err))?; 143 | /// } else { 144 | /// for item in self { 145 | /// item.encode_to(writer)?; 146 | /// } 147 | /// } 148 | /// Ok(()) 149 | /// } 150 | /// } 151 | /// 152 | /// // ... 153 | /// # impl Encode for [T; N] 154 | /// # where 155 | /// # T: Encode, 156 | /// # { 157 | /// # type EncodeError = T::EncodeError; 158 | /// # 159 | /// # #[inline] 160 | /// # fn encode_to(&self, writer: &mut W) -> Result<(), Self::EncodeError> 161 | /// # where 162 | /// # W: ?Sized + Write, 163 | /// # { 164 | /// # self.as_slice().encode_to(writer) 165 | /// # } 166 | /// # } 167 | /// # 168 | /// # impl Decode for [T; N] 169 | /// # where 170 | /// # T: Decode + Default, 171 | /// # { 172 | /// # type DecodeError = T::DecodeError; 173 | /// # 174 | /// # #[inline] 175 | /// # fn decode_from(reader: &mut R) -> Result 176 | /// # where 177 | /// # R: ?Sized + Read, 178 | /// # { 179 | /// # let spec = Specialization::<[T; N], [u8; N]>::try_new(); 180 | /// # 181 | /// # if let Some(spec) = spec { 182 | /// # let mut array = [0; N]; 183 | /// # reader 184 | /// # .read_exact(&mut array) 185 | /// # // Specialize `<[u8; N]>::Error` to `<[T; N]>::Error` 186 | /// # .map_err(|err| spec.rev().map::().specialize(err))?; 187 | /// # // Specialize `[u8; N]` to `[T; N]` 188 | /// # let array = spec.rev().specialize(array); 189 | /// # Ok(array) 190 | /// # } else { 191 | /// # // In real code it can be done without `Default` bound. 192 | /// # // But then the code would be unnecessarily complex for the example. 193 | /// # let mut array = array::from_fn(|_| T::default()); 194 | /// # for item in &mut array { 195 | /// # *item = T::decode_from(reader)?; 196 | /// # } 197 | /// # Ok(array) 198 | /// # } 199 | /// # } 200 | /// # } 201 | /// # 202 | /// # struct MapToEncodeError; 203 | /// # 204 | /// # impl TypeFn for MapToEncodeError 205 | /// # where 206 | /// # T: ?Sized + Encode, 207 | /// # { 208 | /// # type Output = T::EncodeError; 209 | /// # } 210 | /// # 211 | /// # struct MapToDecodeError; 212 | /// # impl TypeFn for MapToDecodeError 213 | /// # where 214 | /// # T: Decode, 215 | /// # { 216 | /// # type Output = T::DecodeError; 217 | /// # } 218 | /// # 219 | /// # let mut array_buf = [0; 8]; 220 | /// # let mut buf = &mut array_buf[..]; 221 | /// # [1_u8, 2, 3].encode_to(&mut buf).unwrap(); 222 | /// # 4_u8.encode_to(&mut buf).unwrap(); 223 | /// # [(), (), (), ()].encode_to(&mut buf).unwrap(); 224 | /// # [5_u8, 6, 7, 8].map(Box::new).encode_to(&mut buf).unwrap(); 225 | /// # assert!(9_u8.encode_to(&mut buf).is_err()); 226 | /// # assert!([9_u8, 10].encode_to(&mut buf).is_err()); 227 | /// # ().encode_to(&mut buf).unwrap(); 228 | /// # [(), (), ()].encode_to(&mut buf).unwrap(); 229 | /// # assert!([9_u8, 10].map(Box::new).encode_to(&mut buf).is_err()); 230 | /// # assert_eq!(array_buf, [1, 2, 3, 4, 5, 6, 7, 8]); 231 | /// # 232 | /// # let buf = &mut array_buf.as_slice(); 233 | /// # assert_eq!(u8::decode_from(buf).unwrap(), 1); 234 | /// # assert_eq!(<[u8; 4]>::decode_from(buf).unwrap(), [2, 3, 4, 5]); 235 | /// # assert_eq!(<[(); 16]>::decode_from(buf).unwrap(), [(); 16]); 236 | /// # assert_eq!(<[u8; 1]>::decode_from(buf).unwrap(), [6]); 237 | /// # assert_eq!( 238 | /// # <[Box; 2]>::decode_from(buf).unwrap(), 239 | /// # [Box::new(7), Box::new(8)] 240 | /// # ); 241 | /// # assert!(u8::decode_from(buf).is_err()); 242 | /// # assert!(<[u8; 1]>::decode_from(buf).is_err()); 243 | /// # assert_eq!(<[(); 2]>::decode_from(buf).unwrap(), [(); 2]); 244 | /// # assert!(<[Box; 2]>::decode_from(buf).is_err()); 245 | /// ``` 246 | /// 247 | /// We can't use `reader.read_exact(&mut array)?;` in the example above because 248 | /// its error variant is `io::Error` while the function error variant is 249 | /// `T::Error`. But we can use the same specialization, but reversed and mapped: 250 | /// - `[T; N] => [u8; N]`, 251 | /// - with `.rev()`: `[u8; N] => [T; N]`, 252 | /// - with `.map::()`: `<[u8; N] as Decode>::Error => <[T; N] as 253 | /// Decode>::Error`, 254 | /// - and for the compiler `<[u8; N] as Decode>::Error` and `io::Error` are 255 | /// equal types, so we can specialize the error as well: `io::Error => <[T; N] 256 | /// as Decode>::Error`. 257 | /// 258 | /// Truncated synthetic example with multiple generics specialization for a 259 | /// third-party type: 260 | /// ```rust 261 | /// # #[cfg(feature = "std")] { 262 | /// use try_specialize::{Specialization, TypeFn}; 263 | /// 264 | /// fn some_generic_fn(value: hashbrown::HashMap) -> &'static str { 265 | /// struct MapIntoHashMap; 266 | /// impl TypeFn<(K, V)> for MapIntoHashMap { 267 | /// type Output = hashbrown::HashMap; 268 | /// } 269 | /// 270 | /// if let Some(spec) = Specialization::<(K, V), (u32, char)>::try_new() { 271 | /// let spec = spec.map::(); 272 | /// let value = spec.specialize(value); 273 | /// specialized_impl(value) 274 | /// } else { 275 | /// default_impl(value) 276 | /// } 277 | /// } 278 | /// 279 | /// fn default_impl(value: hashbrown::HashMap) -> &'static str { 280 | /// // ... 281 | /// # "default impl" 282 | /// } 283 | /// 284 | /// fn specialized_impl(value: hashbrown::HashMap) -> &'static str { 285 | /// // ... 286 | /// # "specialized impl" 287 | /// } 288 | /// # 289 | /// # assert_eq!( 290 | /// # some_generic_fn([(0_i32, 'a'), (1, 'b'), (2, 'c')].into_iter().collect()), 291 | /// # "default impl" 292 | /// # ); 293 | /// # 294 | /// # assert_eq!( 295 | /// # some_generic_fn( 296 | /// # [(0_u32, "zero"), (1, "one"), (2, "two")] 297 | /// # .into_iter() 298 | /// # .collect() 299 | /// # ), 300 | /// # "default impl" 301 | /// # ); 302 | /// # 303 | /// # assert_eq!( 304 | /// # some_generic_fn([(0_u32, 'a'), (1, 'b'), (2, 'c')].into_iter().collect()), 305 | /// # "specialized impl" 306 | /// # ); 307 | /// # } 308 | /// ``` 309 | pub trait TypeFn 310 | where 311 | T: ?Sized, 312 | { 313 | /// The returned type. 314 | type Output: ?Sized; 315 | } 316 | -------------------------------------------------------------------------------- /tests/zero_cost_optimized_fn_ptrs.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs, reason = "okay in tests")] 2 | #![expect(unused_crate_dependencies, reason = "okay in tests")] 3 | #![cfg(not(miri))] 4 | #![cfg(not(debug_assertions))] // Requires `profile.opt-level >= 1` 5 | 6 | use std::collections::{HashMap, HashSet}; 7 | 8 | use paste::paste; 9 | use try_specialize::TrySpecialize; 10 | 11 | mod common; 12 | 13 | #[cfg(feature = "__test_extern_fn_merging")] 14 | #[rustversion::all(before(2024-08-13), before(1.82))] 15 | const RUST_VERSION_GE_1_82: bool = false; 16 | 17 | #[cfg(feature = "__test_extern_fn_merging")] 18 | #[rustversion::any(since(2024-08-13), since(1.82))] 19 | const RUST_VERSION_GE_1_82: bool = true; 20 | 21 | #[test] 22 | fn test_zero_cost_specialization_fn_ptrs() { 23 | #[derive(Clone, Default, Debug)] 24 | struct Stats { 25 | matched: usize, 26 | alternative_fn_ptrs: HashSet<*const u8>, 27 | total: usize, 28 | } 29 | 30 | let reference_fn_ptrs = collect_reference_fn_ptrs(); 31 | let tested_fn_ptrs = collect_tested_fn_ptrs(); 32 | 33 | let mut stats: HashMap<_, _> = [ 34 | ("core", Stats::default()), 35 | ("alloc", Stats::default()), 36 | ("std", Stats::default()), 37 | ] 38 | .into_iter() 39 | .collect(); 40 | 41 | for ((lib1, tested_fn_kind, ty1, ty2), tested_fn_ptr) in tested_fn_ptrs { 42 | let reference_fn_kind = match tested_fn_kind { 43 | "to_ltfree" | "from_ltfree" | "static" => "value", 44 | "to_ltfree_ref" | "from_ltfree_ref" | "static_ref" => "ref", 45 | "to_ltfree_mut" | "from_ltfree_mut" | "static_mut" => "mut", 46 | _ => panic!("unexpected function kind"), 47 | }; 48 | 49 | let reference_fn_ptr = reference_fn_ptrs[&(reference_fn_kind, ty1, ty1 == ty2)]; 50 | let stats = stats.get_mut(lib1).unwrap(); 51 | if tested_fn_ptr == reference_fn_ptr { 52 | stats.matched += 1; 53 | } else { 54 | let _: bool = stats.alternative_fn_ptrs.insert(tested_fn_ptr); 55 | 56 | if lib1 == "core" || reference_fn_kind != "value" { 57 | #[cfg(feature = "__test_extern_fn_merging")] 58 | assert!( 59 | (lib1 != "core" && reference_fn_kind == "value") || !RUST_VERSION_GE_1_82, 60 | "Since Rust 1.82 it is expected that function merging might fail only for \ 61 | non-core types passed by value. Function data: ({}, {}, {}, {})", 62 | lib1, 63 | tested_fn_kind, 64 | ty1, 65 | ty2 66 | ); 67 | 68 | #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] 69 | { 70 | let valid_expected = if ty1 == ty2 { 71 | valid_ret_true_fn_asm() 72 | } else { 73 | valid_ret_false_fn_asm() 74 | }; 75 | 76 | assert!( 77 | valid_expected 78 | .iter() 79 | .any(|expected| cmp_fn_asm(tested_fn_ptr, expected)), 80 | "Generated function is not matched to any expected one" 81 | ); 82 | } 83 | } 84 | } 85 | stats.total += 1; 86 | } 87 | 88 | // Expected values depend on tested types amount. 89 | #[cfg(not(feature = "alloc"))] 90 | let expected = [1303, 0, 0]; 91 | #[cfg(all(feature = "alloc", not(feature = "std")))] 92 | let expected = [1532, 267, 0]; 93 | #[cfg(feature = "std")] 94 | let expected = [1711, 297, 233]; 95 | 96 | assert_eq!(stats["core"].total, expected[0]); 97 | assert_eq!(stats["alloc"].total, expected[1]); 98 | assert_eq!(stats["std"].total, expected[2]); 99 | 100 | #[cfg(feature = "__test_extern_fn_merging")] 101 | { 102 | if RUST_VERSION_GE_1_82 { 103 | // 100% of core fns are expected to have same asm since Rust 1.82. 104 | assert_eq!(stats["core"].matched, stats["core"].total); 105 | } else { 106 | // More than 99% of fns are expected to have same asm before Rust 1.82. 107 | assert!(stats["core"].matched >= stats["core"].total * 99 / 100); 108 | } 109 | 110 | // Thresholds below are approximate and may be adjusted in the future. 111 | assert!(stats["alloc"].matched >= stats["alloc"].total * 2 / 3); 112 | assert!(stats["std"].matched >= stats["std"].total * 2 / 3); 113 | assert!(stats["alloc"].alternative_fn_ptrs.len() < 8); 114 | assert!(stats["std"].alternative_fn_ptrs.len() < 8); 115 | } 116 | } 117 | 118 | fn cmp_fn_asm(fn_ptr: *const u8, expected: &[u8]) -> bool { 119 | for (offset, &expected) in expected.iter().enumerate() { 120 | // SAFETY: Locally defined extern functions should be readable. 121 | let byte = unsafe { fn_ptr.byte_add(offset).read() }; 122 | if byte != expected { 123 | return false; 124 | } 125 | } 126 | true 127 | } 128 | 129 | #[rustversion::all(before(2024-08-13), before(1.82))] 130 | fn valid_ret_false_fn_asm() -> &'static [&'static [u8]] { 131 | &[ 132 | &[ 133 | 0x31, 0xC0, // xorl %eax, %eax 134 | 0xC3, // retq 135 | ], 136 | // Generates suboptimal, but valid bytecode for `Option`s. 137 | // The `Option` tag (passed in %edi) can never be equal to 2, so it 138 | // will never return 1. 139 | // 140 | // Improved in: 141 | // - , 142 | // - . 143 | &[ 144 | 0x83, 0xFF, 0x02, // cmpl $2, %edi 145 | 0x0F, 0x94, 0xC0, // sete %al 146 | 0xC3, // retq 147 | ], 148 | ] 149 | } 150 | 151 | #[rustversion::any(since(2024-08-13), since(1.82))] 152 | fn valid_ret_false_fn_asm() -> &'static [&'static [u8]] { 153 | &[&[ 154 | 0x31, 0xC0, // xorl %eax, %eax 155 | 0xC3, // retq 156 | ]] 157 | } 158 | 159 | fn valid_ret_true_fn_asm() -> &'static [&'static [u8]] { 160 | &[&[ 161 | 0xB0, 0x01, // movb $1, %al 162 | 0xC3, // retq 163 | ]] 164 | } 165 | 166 | /// Defines the reference function that have a signature similar to the 167 | /// functions being checked, but unlike them return the specified constant. 168 | macro_rules! define_reference_fn { 169 | { 170 | { $lib:tt $name:ident: $ty:ty } 171 | { $kind:tt, $const:literal } 172 | } => { 173 | paste! { 174 | #[allow(clippy::missing_const_for_fn)] 175 | #[no_mangle] 176 | extern "Rust" fn [< $kind _from_ $name _to_ $const >](_: $ty) -> bool { 177 | $const 178 | } 179 | } 180 | }; 181 | } 182 | 183 | // For all compatible ($kind, $ty, $const) defines reference fns like: 184 | // - `fn ${kind}_from_${ty}_to_${const}(_: ${ty}) -> bool { ${const} }`. 185 | chain! { 186 | for_all_types! 187 | | for_each! 188 | | for_each_reference_fn! 189 | | define_reference_fn! 190 | } 191 | 192 | /// Inserts checked to a specified map. 193 | macro_rules! insert_reference_fn_to_map { 194 | ({ { $lib:tt $name:ident : $ty:ty } { $kind:tt, $const:literal } } { $map:path }) => { 195 | if $map 196 | .insert( 197 | (stringify!($kind), stringify!($name), $const), 198 | common::fn_raw_ptr(paste! { [< $kind _from_ $name _to_ $const >] }), 199 | ) 200 | .is_some() 201 | { 202 | cold_unexpected_duplicate_reference_fn(stringify!($kind), stringify!($name), $const); 203 | } 204 | }; 205 | } 206 | 207 | fn collect_reference_fn_ptrs() -> HashMap<(&'static str, &'static str, bool), *const u8> { 208 | let mut map = HashMap::new(); 209 | 210 | macro_rules! with_local_map_var { 211 | ( $in:tt | $func:ident! $args:tt ) => { 212 | $func!( { $in { map }} $args ) 213 | } 214 | } 215 | 216 | chain! { 217 | for_all_types! 218 | | for_each! 219 | | for_each_reference_fn! 220 | | with_local_map_var! 221 | | insert_reference_fn_to_map! 222 | } 223 | 224 | map 225 | } 226 | 227 | /// Defines tested function. 228 | macro_rules! define_tested_fn { 229 | ( 230 | { $lib1:tt $lt1:tt $size1:tt $name1:ident : $ty1:ty = $example1:expr } { $lib2:tt $lt2:tt $size2:tt $name2:ident : $ty2:ty = $example2:expr } { $kind:tt, $arg:ident => $try_specialize_expr:expr } { $ok_pat:pat, $fail_pat:pat, $ok:expr, $fail:expr } 231 | ) => { 232 | paste! { 233 | #[no_mangle] 234 | extern "Rust" fn [< 235 | $kind _from_ $name1 _to_ $name2 _is_ok 236 | >]( $arg: $ty1 ) -> bool { 237 | match $try_specialize_expr { 238 | $ok_pat => true, 239 | $fail_pat => false, 240 | } 241 | } 242 | } 243 | }; 244 | } 245 | 246 | // For all compatible ($kind, $ty1, $ty2) defines reference fns like: 247 | // - `fn {kind}_from_${ty1}_to_${ty2}(_: ${ty1}) -> bool { ... }`. 248 | chain! { 249 | for_all_types! 250 | | for_each_cartesian_square_pair! 251 | | for_each_cartesian_pair_tested_fn! 252 | | define_tested_fn! 253 | } 254 | 255 | /// Inserts checked to a specified map. 256 | macro_rules! insert_tested_fn_to_map { 257 | ( 258 | { 259 | { $lib1:tt $lt1:tt $size1:tt $name1:ident : $ty1:ty = $example1:expr } { $lib2:tt $lt2:tt $size2:tt $name2:ident : $ty2:ty = $example2:expr } { $kind:tt, $arg:ident => $try_specialize_expr:expr } { $ok_pat:pat, $fail_pat:pat, $ok:expr, $fail:expr } 260 | } { $map:path } 261 | ) => { 262 | if $map 263 | .insert( 264 | ( 265 | stringify!($lib1), 266 | stringify!($kind), 267 | stringify!($name1), 268 | stringify!($name2), 269 | ), 270 | common::fn_raw_ptr(paste! { [< $kind _from_ $name1 _to_ $name2 _is_ok >] }), 271 | ) 272 | .is_some() 273 | { 274 | cold_unexpected_duplicate_tested_fn( 275 | stringify!($kind), 276 | stringify!($name1), 277 | stringify!($name2), 278 | ); 279 | } 280 | }; 281 | } 282 | 283 | fn collect_tested_fn_ptrs( 284 | ) -> HashMap<(&'static str, &'static str, &'static str, &'static str), *const u8> { 285 | let mut map = HashMap::new(); 286 | 287 | macro_rules! with_local_map_var { 288 | ( $in:tt | $func:ident! $args:tt ) => { 289 | $func!( { $in { map }} $args ) 290 | } 291 | } 292 | 293 | chain! { 294 | for_all_types! 295 | | for_each_cartesian_square_pair! 296 | | for_each_cartesian_pair_tested_fn! 297 | | with_local_map_var! 298 | | insert_tested_fn_to_map! 299 | } 300 | 301 | map 302 | } 303 | 304 | // Extracted to separate function to optimize test build times. 305 | #[cold] 306 | #[inline(never)] 307 | fn cold_unexpected_duplicate_reference_fn(kind: &'static str, ty: &'static str, value: bool) -> ! { 308 | panic!("unexpected duplicate reference function found for {kind}, {ty}, {value}"); 309 | } 310 | 311 | // Extracted to separate function to optimize test build times. 312 | #[cold] 313 | #[inline(never)] 314 | fn cold_unexpected_duplicate_tested_fn( 315 | kind: &'static str, 316 | ty1: &'static str, 317 | ty2: &'static str, 318 | ) -> ! { 319 | panic!("unexpected duplicate tested function found for {kind}, {ty1}, {ty2}"); 320 | } 321 | -------------------------------------------------------------------------------- /check.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # ================================================================ 4 | # Crate-specific settings 5 | # ================================================================ 6 | 7 | toolchains_param_set=( 8 | "stable|" 9 | "beta|" 10 | "nightly|--features __test_nightly" 11 | "1.82.0|" 12 | ) 13 | all_features=( "" "alloc" "std" "unreliable" ) 14 | test_opt_level1_args="--release --features alloc,std,unreliable" 15 | test_opt_level2_args="--release --features alloc,std,unreliable" 16 | test_opt_level2_args+=",__test_extern_fn_merging,__test_extern_fn_dce" 17 | cross_targets=( 18 | "i686-unknown-linux-gnu" 19 | "x86_64-unknown-linux-gnu" 20 | "aarch64-unknown-linux-gnu" 21 | "i686-pc-windows-gnu" 22 | "wasm32-unknown-emscripten" 23 | ) 24 | cross_args="--features alloc,std,unreliable" 25 | min_covered_functions_percent=95 26 | min_covered_lines_percent=90 27 | min_covered_regions_percent=90 28 | crate_docs_link="https://docs.rs/try-specialize/latest/try_specialize" 29 | crate_docs_ignored_lines_regex=$'^(L|T) \[`transmute`\]: .*$' 30 | 31 | # ================================================================ 32 | # Common check script code 33 | # ================================================================ 34 | 35 | set -Eeuo pipefail 36 | 37 | tmp_dir="" 38 | bold_red=$'\033[1;31m' 39 | bold_green=$'\033[1;32m' 40 | no_color=$'\033[0m' 41 | 42 | cleanup() { 43 | local return_code=$? 44 | if [ -n "$tmp_dir" ]; then 45 | rm -rf -- "$tmp_dir" 46 | fi 47 | exit $return_code 48 | } 49 | 50 | trap cleanup EXIT 51 | 52 | tmp_dir="$( mktemp -d )" 53 | 54 | exec() { "$@"; } 55 | passthru() { tee; } 56 | comment() { tee; } 57 | ok() { echo "${bold_green}OK${no_color}: $@" 1>&2; } 58 | fail() { echo "${bold_red}ERROR${no_color}: $@" 1>&2; exit 1; } 59 | echo_and_run() { echo "$ ${*@Q}"; "$@"; } 60 | 61 | echo_and_try_run() { 62 | set +eo pipefail 63 | echo "$ ${*@Q}" 64 | "$@" 2> >( tee "$tmp_dir/error.txt" ) 65 | echo $? > "$tmp_dir/return_code.txt" 66 | set -eo pipefail 67 | } 68 | 69 | expect_failure() { 70 | if [ "$(cat "$tmp_dir/return_code.txt")" -ne "0" ]; then 71 | ok "Command failed as expected." 72 | if ! cat "$tmp_dir/error.txt" | grep -q "$@"; then 73 | fail "Unexpected error message, expected regex: ${*@Q}." 74 | fi 75 | else 76 | fail "Command did not fail as expected." 77 | fi 78 | } 79 | 80 | # Apply some transformations to lib.rs and README.md, to get the documentation 81 | # in a single intermediate format, which is expected to be the same for both: 82 | # lib.rs and README.md. 83 | normalize_docs() { 84 | source=$1 85 | local libdoc=false 86 | local readme=false 87 | if [[ $source == "lib.rs" ]]; then 88 | libdoc=exec 89 | readme=passthru 90 | elif [[ $source == "README.md" ]]; then 91 | libdoc=passthru 92 | readme=exec 93 | else 94 | fail "Internal script error: invalid docs source." 95 | fi 96 | 97 | set +eo pipefail 98 | 99 | cat \ 100 | | comment "Remove title and badges from README.md" \ 101 | | $readme awk ' 102 | BEGIN { is_content=0 } 103 | is_content==1 { print; next } 104 | /^(# .*||!\[.*|\[!\[.*)$/ { next } # Ignore title, empty line and images. 105 | is_content==0 { is_content=1; print; next } 106 | ' \ 107 | | comment "Remove '//!' lines from lib.rs" \ 108 | | $libdoc rg '^//!' \ 109 | | $libdoc rg '^//! ?' -r '' \ 110 | | comment "Add "C " prefix for markdown code lines." \ 111 | | comment "Add "T " prefix for other markdown lines." \ 112 | | awk ' 113 | BEGIN { is_code=0 } 114 | /^```.*$/ { print "C " $0; is_code=!is_code; next } 115 | is_code==0 { print "T " $0; next } 116 | is_code==1 { print "C " $0; next } 117 | ' \ 118 | | comment "Remove hidden portions from lib.rs doc examples." \ 119 | | $libdoc rg -v $'^C #( |$)' \ 120 | | comment "Decrease all headers level in README.md." \ 121 | | $readme rg --passthru $'^T #(#+) (.*)' -r $'T $1 $2' \ 122 | | comment "Convert rust stdlib links to rust paths in README.md." \ 123 | | comment "Use "L " prefix for markdown lines with link references." \ 124 | | $readme rg --passthru \ 125 | $'^T \[(.*)\]: https://doc\.rust-lang\.org/(std|core)/(?:(?:(?:([^/]*)/)?([^/]*)/)?([^/]*)/)?(?:struct|enum|trait|fn|primitive|macro)\.([^/]*)\.html(#| |$)' \ 126 | -r $'L [$1]: $2::$3::$4::$5::$6$7' \ 127 | | comment "Convert rust stdlib module links to rust paths in README.md." \ 128 | | $readme rg --passthru \ 129 | $'^T \[(.*)\]: https://doc\.rust-lang\.org/(std|core)/(?:(?:(?:([^/]*)/)?([^/]*)/)?([^/]*)/)?index\.html(#| |$)' \ 130 | -r $'L [$1]: $2::$3::$4::$5$6' \ 131 | | comment "Convert crate links to rust paths in README.md." \ 132 | | $readme rg --passthru \ 133 | $'^T \[(.*)\]: '"$crate_docs_link"$'/(?:(?:(?:([^/]*)/)?([^/]*)/)?([^/]*)/)?(?:struct|enum|trait|fn|primitive|macro)\.([^/]*)\.html(#| |$)' \ 134 | -r $'L [$1]: $2::$3::$4::$5$6' \ 135 | | comment "Convert crate module links to rust paths in README.md." \ 136 | | $readme rg --passthru \ 137 | $'^T \[(.*)\]: '"$crate_docs_link"$'/(?:(?:(?:([^/]*)/)?([^/]*)/)?([^/]*)/)?index\.html(#| |$)' \ 138 | -r $'L [$1]: $2::$3::$4$5' \ 139 | | comment "Remove artifact "::::" and starting "::" in paths after the previous link convertions." \ 140 | | $readme rg --passthru \ 141 | $'^L \[(.*)\]: ([^:]*)::(?:::)+' -r $'L [$1]: $2::' \ 142 | | $readme rg --passthru \ 143 | $'^L \[(.*)\]: ::' -r $'L [$1]: ' \ 144 | | comment "Convert link methods to rust paths in README.md." \ 145 | | $readme rg --passthru \ 146 | $'^L \[(.*)\]: (.*)#method\.' -r $'L [$1]: $2$3::' \ 147 | | comment "Remove link titles that now duplicates rust path in README.md." \ 148 | | $readme rg --pcre2 --passthru \ 149 | $'^L \[(.*)\]: ([^ ]*) "\\2"' -r $'L [$1]: $2' \ 150 | | comment "Remove link references that now duplicates rust path in README.md" \ 151 | | $readme rg --pcre2 -v $'^L \[`(.*)`\]: \\1$' \ 152 | | $readme rg -U --pcre2 --passthru $'^(L .*)\nT \n(?:T \n)+(L .*)$' -r $'$1\n\n$2' \ 153 | | comment "Ignore specified lines" \ 154 | | rg -v "$crate_docs_ignored_lines_regex" \ 155 | | comment "Remove temporary prefixes." \ 156 | | rg --passthru $'T ' -r '' \ 157 | | rg --passthru $'C ' -r '' \ 158 | | rg --passthru $'L ' -r '' \ 159 | | tee 160 | 161 | set -eo pipefail 162 | } 163 | 164 | echo_and_run cargo +nightly fmt --all -- --check 165 | echo_and_run cargo outdated --exit-code 1 166 | 167 | readme="$( cat README.md | normalize_docs "README.md" )" 168 | libdoc="$( cat src/lib.rs | normalize_docs "lib.rs" )" 169 | echo_and_run git diff <(echo "$readme") <(echo "$libdoc") 170 | 171 | # Each value is a set of `|`-separated values: 172 | # - comma separated features, 173 | # - expected status on non-nightly toolchains, 174 | # - expected status on nightly toolchain, 175 | # - expected error message regex" 176 | valid_no_alloc_and_no_std_param_sets=( 177 | "|fail|fail|panic_handler.* function required" 178 | "panic-handler|ok|fail|undefined symbol: rust_eh_personality" 179 | "alloc,panic-handler|fail|fail|no global memory allocator found" 180 | "alloc,panic-handler,global-allocator|ok|fail|undefined symbol: rust_eh_personality" 181 | "alloc,std,panic-handler,global-allocator|fail|fail|found duplicate lang item .*panic_impl" 182 | "alloc,std,global-allocator|ok|ok|" 183 | ) 184 | for toolchain_params in "${toolchains_param_set[@]}"; do 185 | toolchain=$(echo "$toolchain_params" | cut -sd"|" -f1) 186 | ( 187 | echo_and_run export CARGO_TARGET_DIR="target/check-no-alloc-and-no-std-$toolchain" 188 | for param_set in "${valid_no_alloc_and_no_std_param_sets[@]}"; do 189 | features=$(echo "$param_set" | cut -sd"|" -f1) 190 | expected_default=$(echo "$param_set" | cut -sd"|" -f2) 191 | expected_nightly=$(echo "$param_set" | cut -sd"|" -f3) 192 | expected_error_regex=$(echo "$param_set" | cut -sd"|" -f4-) 193 | if [ "$toolchain" = "nightly" ]; then 194 | expected="$expected_nightly" 195 | else 196 | expected="$expected_default" 197 | fi 198 | args="--config build.rustflags=[\"-C\",\"link-arg=-nostartfiles\"]" 199 | args+=" --manifest-path tests/no-alloc-and-no-std/Cargo.toml" 200 | args+=" --no-default-features" 201 | [ -n "$features" ] && args+=" --features $features" 202 | if [[ "$expected" == "ok" ]]; then 203 | echo_and_run cargo "+$toolchain" clippy $args -- -D warnings 204 | echo_and_run cargo "+$toolchain" build $args 205 | elif [[ "$expected" == "fail" ]]; then 206 | echo_and_try_run cargo "+$toolchain" build $args 207 | expect_failure "$expected_error_regex" 208 | else 209 | fail "Internal script error: invalid expected result." 210 | fi 211 | done 212 | ) 213 | done 214 | 215 | num_features=${#all_features[@]} 216 | num_combinations=$(echo "2^$num_features" | bc) 217 | feature_sets=() 218 | 219 | # Iterate over all `2^num_features` features combinations if required 220 | # `combination_idx` is used as a bitmask of the enabled features. 221 | for ((combination_idx = 0; combination_idx < num_combinations; combination_idx++)); do 222 | features_set=() 223 | for ((feature_idx = 0; feature_idx < num_features; feature_idx++)); do 224 | mask=$(echo "2^$feature_idx" | bc) # The mask of `feature_idx`-th feature. 225 | 226 | if (( combination_idx & mask )); then 227 | features_set+=(${all_features[$feature_idx]}) 228 | fi 229 | done 230 | features=$(echo "${features_set[@]}" | tr " " ",") 231 | feature_sets+=("$features") 232 | done 233 | 234 | 235 | for toolchain_params in "${toolchains_param_set[@]}"; do 236 | toolchain=$(echo "$toolchain_params" | cut -sd"|" -f1) 237 | toolchain_flags=$(echo "$toolchain_params" | cut -sd"|" -f2) 238 | ( 239 | echo_and_run export CARGO_TARGET_DIR="target/check-$toolchain" 240 | for features in "${feature_sets[@]}"; do 241 | cargo="cargo +$toolchain" 242 | if [ -n "$features" ]; then 243 | args="--no-default-features --features $features" 244 | else 245 | args="--no-default-features" 246 | fi 247 | [ -n "$toolchain_flags" ] && args+=" $toolchain_flags" 248 | echo_and_run $cargo clippy --all-targets $args -- -D warnings 249 | echo_and_run $cargo build --all-targets $args 250 | echo_and_run $cargo test --all-targets $args 251 | echo_and_run $cargo test --release --all-targets $args 252 | echo_and_run $cargo test --doc $args 253 | echo_and_run $cargo test --doc --release $args 254 | if [[ "$toolchain" == "nightly" ]]; then 255 | echo_and_run $cargo miri test --all-targets $args 256 | fi 257 | echo_and_run $cargo bench --no-run --all-targets $args 258 | done 259 | ) 260 | ( 261 | args="$test_opt_level1_args" 262 | echo_and_run export CARGO_PROFILE_RELEASE_OPT_LEVEL=1 263 | echo_and_run cargo test --all-targets $args 264 | ) 265 | ( 266 | args="$test_opt_level2_args" 267 | echo_and_run export CARGO_PROFILE_RELEASE_OPT_LEVEL=2 268 | echo_and_run cargo test --all-targets $args 269 | ) 270 | for target in "${cross_targets[@]}"; do 271 | args="--target $target $cross_args" 272 | echo_and_run cross clippy $args -- -D warnings 273 | echo_and_run cross build $args 274 | echo_and_run cross test $args 275 | done 276 | done 277 | 278 | for features in "${feature_sets[@]}"; do 279 | args=() 280 | features=$( echo "$features" | tr "," " " ) 281 | for feature in ${features}; do 282 | args+=( --features ) 283 | args+=( $feature ) 284 | done 285 | echo_and_run cargo semver-checks --only-explicit-features "${args[@]}" 286 | done 287 | 288 | echo_and_run cargo deny --workspace --all-features check 289 | 290 | echo_and_run cargo +nightly llvm-cov --doctests --all-features --html \ 291 | --fail-under-functions $min_covered_functions_percent \ 292 | --fail-under-lines $min_covered_lines_percent \ 293 | --fail-under-regions $min_covered_regions_percent 294 | 295 | ok "All checks succeeded." 1>&2 296 | -------------------------------------------------------------------------------- /tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | #[cfg(feature = "alloc")] 4 | use std::borrow::Cow; 5 | #[cfg(feature = "std")] 6 | use std::collections::HashMap; 7 | #[cfg(feature = "alloc")] 8 | use std::collections::{BTreeMap, BTreeSet}; 9 | #[cfg(feature = "std")] 10 | use std::path::Path; 11 | #[cfg(feature = "std")] 12 | use std::sync::Arc; 13 | 14 | /// Allows to use the results of one macro as input for another macro in easy 15 | /// way. Requires support for special `pipe`-like syntax from any macro in the 16 | /// chain except the last one. 17 | /// 18 | /// An example of the recursive macro expansion steps: 19 | /// - `chain! { generator! | mapper! | filter! | flattener! | sink! }`, 20 | /// - `generator! { chain! { | mapper! | filter! | flattener! | sink! }}`, 21 | /// - `chain! { GENERATED_DATA | mapper! | filter! | flattener! | sink! }`, 22 | /// - `mapper! { GENERATED_DATA | chain! { | filter! | flattener! | sink! }}`, 23 | /// - `chain! { MAPPED_DATA | filter! | flattener! | sink! }`, 24 | /// - `filter! { MAPPED_DATA | chain! { | flattener! | sink! }}`, 25 | /// - `chain! { FILTERED_DATA | flattener! | sink! }`, 26 | /// - `flattener! { FILTERED_DATA | chain! { | sink! }}`, 27 | /// - `chain! { FLATTENED_DATA | sink! }`, 28 | /// - `sink! FLATTENED_DATA`. 29 | #[macro_export] 30 | macro_rules! chain { 31 | { $in:tt { $( $tail:tt )* } } => { 32 | chain! { $in $( $tail )* } 33 | }; 34 | { $in:tt | $func:ident! | $( $tail:tt )* } => { 35 | $func! { $in | chain! { | $( $tail )* } } 36 | }; 37 | { $in:tt | $func:ident! } => { 38 | $func! $in 39 | }; 40 | { $func:ident! | $( $tail:tt )* } => { 41 | $func! { chain! { | $( $tail )* } } 42 | }; 43 | } 44 | 45 | /// Calls the callback macro for each item in a list. 46 | #[macro_export] 47 | macro_rules! for_each { 48 | { [ $( $tt:tt )* ] | $func:ident! $args:tt } => { 49 | $( $func! { $tt $args } )* 50 | }; 51 | } 52 | 53 | /// Calls the callback macro for each pair of list cartesian square set. 54 | #[macro_export] 55 | macro_rules! for_each_cartesian_square_pair { 56 | { [ $( $tt:tt )* ] | $func:ident! $args:tt } => { 57 | for_each_cartesian_product_pair! { 58 | ( [ $( $tt )* ] x [ $( $tt )* ] ) | $func! $args 59 | } 60 | }; 61 | } 62 | 63 | /// Calls the callback macro for each pair of two lists cartesian product set. 64 | #[macro_export] 65 | macro_rules! for_each_cartesian_product_pair { 66 | { ( [] x [ $( $right:tt )* ] ) | $func:ident! $args:tt } => {}; 67 | { 68 | ( [ $left_next:tt $( $left_rest:tt )* ] x [ $( $right_all:tt )* ] ) 69 | | $func:ident! $args:tt 70 | } => { 71 | $( $func! { { $left_next $right_all } $args } )* 72 | for_each_cartesian_product_pair! { 73 | ( [ $( $left_rest )* ] x [ $( $right_all )* ] ) | $func! $args 74 | } 75 | }; 76 | } 77 | 78 | /// Computes if-then-else-like expressions. 79 | /// 80 | /// Keep in mind that in Rust the order of macro expansion is undefined, 81 | /// which forces to use recursion more often in the auxiliary macros 82 | /// implementations. 83 | #[macro_export] 84 | macro_rules! mif { 85 | { if true { $( $then:tt )* } $( else { $( $else:tt )* } )? } => { 86 | $( $then )* 87 | }; 88 | { if false { $( $then:tt )* } $( else { $( $else:tt )* } )? } => { 89 | $( $( $else )* )? 90 | }; 91 | 92 | { if core $( $tt:tt )* } => { mif! { if true $( $tt )* } }; 93 | { if alloc $( $tt:tt )* } => { mif_alloc! { $( $tt )* } }; 94 | { if std $( $tt:tt )* } => { mif_std! { $( $tt )* } }; 95 | 96 | { if ( eq $left:tt $right:tt ) $( $tt:tt )* } => { 97 | mif_eq! { ($left, $right) $( $tt )* } 98 | }; 99 | { if ( not $cond:tt ) $then:tt } => { 100 | mif! { if $cond {} else $then } 101 | }; 102 | { if ( not $cond:tt ) $then:tt else $else:tt } => { 103 | mif! { if $cond $else else $then } 104 | }; 105 | { if ( all ) $( $tt:tt )* } => { 106 | mif! { if true $( $tt )* } 107 | }; 108 | { if ( all $first:tt $( $rest:tt )* ) $( $tt:tt )* } => { 109 | mif! { 110 | if $first { 111 | mif! { if ( all $( $rest )* ) $( $tt )* } 112 | } else { 113 | mif! { if false $( $tt )* } 114 | } 115 | } 116 | }; 117 | } 118 | 119 | /// An auxiliary feature-dependent macro for a `mif` macro. 120 | #[macro_export] 121 | #[cfg(feature = "alloc")] 122 | macro_rules! mif_alloc { 123 | { $( $tt:tt )* } => { mif! { if true $( $tt )* } } 124 | } 125 | 126 | /// An auxiliary feature-dependent macro for a `mif` macro. 127 | #[macro_export] 128 | #[cfg(not(feature = "alloc"))] 129 | macro_rules! mif_alloc { 130 | { $( $tt:tt )* } => { mif! { if false $( $tt )* } } 131 | } 132 | 133 | /// An auxiliary feature-dependent macro for a `mif` macro. 134 | #[macro_export] 135 | #[cfg(feature = "std")] 136 | macro_rules! mif_std { 137 | { $( $tt:tt )* } => { mif! { if true $( $tt )* } } 138 | } 139 | 140 | /// An auxiliary feature-dependent macro for a `mif` macro. 141 | #[macro_export] 142 | #[cfg(not(feature = "std"))] 143 | macro_rules! mif_std { 144 | { $( $tt:tt )* } => { mif! { if false $( $tt )* } } 145 | } 146 | 147 | /// An auxiliary equality check macro for a `mif` macro. 148 | #[macro_export] 149 | macro_rules! mif_eq { 150 | { ( core, core ) $( $tt:tt )* } => { mif! { if true $( $tt )* } }; 151 | { ( $_:tt, core ) $( $tt:tt )* } => { mif! { if false $( $tt )* } }; 152 | { ( ltfree, ltfree ) $( $tt:tt )* } => { mif! { if true $( $tt )* } }; 153 | { ( $_:tt, ltfree ) $( $tt:tt )* } => { mif! { if false $( $tt )* } }; 154 | { ( sized, sized ) $( $tt:tt )* } => { mif! { if true $( $tt )* } }; 155 | { ( $_:tt, sized ) $( $tt:tt )* } => { mif! { if false $( $tt )* } }; 156 | } 157 | 158 | /// For a given checked type, determines the set of required reference 159 | /// functions, and calls the callback macro for each such function. 160 | #[macro_export] 161 | macro_rules! for_each_reference_fn { 162 | { 163 | { $lib:tt $lt:tt $size:tt $name:ident: $ty:ty = $example:expr } 164 | | $func:ident! $args:tt 165 | } => { 166 | mif! { if $lib { 167 | mif! { if (eq $size sized) { 168 | $func! { { { $lib $name: $ty } { value, false } } $args } 169 | $func! { { { $lib $name: $ty } { value, true } } $args } 170 | }} 171 | $func! { { { $lib $name: &$ty } { ref, false } } $args } 172 | $func! { { { $lib $name: &$ty } { ref, true } } $args } 173 | $func! { { { $lib $name: &mut $ty } { mut, false } } $args } 174 | $func! { { { $lib $name: &mut $ty } { mut, true } } $args } 175 | }} 176 | }; 177 | } 178 | 179 | /// For a given pair of checked types, determines the set of required checked 180 | /// functions, and calls the callback macro for each such function. 181 | #[macro_export] 182 | macro_rules! for_each_cartesian_pair_tested_fn { 183 | { 184 | { 185 | { $lib1:tt $lt1:tt $size1:tt $name1:ident: $ty1:ty = $example1:expr } 186 | { $lib2:tt $lt2:tt $size2:tt $name2:ident: $ty2:ty = $example2:expr } 187 | } | $func:ident! $args:tt 188 | } => { 189 | mif! { if (all $lib1 $lib2) { 190 | mif! { if (eq $lt2 ltfree) { 191 | mif! { if (all (eq $size1 sized) (eq $size2 sized)) { 192 | $func! { 193 | { 194 | { $lib1 $lt1 $size1 $name1: $ty1 = $example1 } 195 | { $lib2 $lt2 $size2 $name2: $ty2 = $example2 } 196 | { to_ltfree, arg => arg.try_specialize::<$ty2>() } 197 | { Ok(_ok), Err(_err), Ok($example2), Err($example1) } 198 | } 199 | $args 200 | } 201 | }} 202 | $func! { 203 | { 204 | { $lib1 $lt1 $size1 $name1: &$ty1 = &$example1 } 205 | { $lib2 $lt2 $size2 $name2: &$ty2 = &$example2 } 206 | { to_ltfree_ref, arg => arg.try_specialize_ref::<$ty2>() } 207 | { Some(_some), None, Some(&$example2), None } 208 | } 209 | $args 210 | } 211 | $func! { 212 | { 213 | { $lib1 $lt1 $size1 $name1: &mut $ty1 = &mut $example1 } 214 | { $lib2 $lt2 $size2 $name2: &mut $ty2 = &mut $example2 } 215 | { to_ltfree_mut, arg => arg.try_specialize_mut::<$ty2>() } 216 | { Some(_some), None, Some(&mut $example2), None } 217 | } 218 | $args 219 | } 220 | }} 221 | mif! { if (eq $lt1 ltfree) { 222 | mif! { if (all (eq $size1 sized) (eq $size2 sized)) { 223 | $func! { 224 | { 225 | { $lib1 $lt1 $size1 $name1: $ty1 = $example1 } 226 | { $lib2 $lt2 $size2 $name2: $ty2 = $example2 } 227 | { from_ltfree, arg => <$ty2>::try_specialize_from(arg) } 228 | { Ok(_ok), Err(_err), Ok($example2), Err($example1) } 229 | } 230 | $args 231 | } 232 | }} 233 | $func! { 234 | { 235 | { $lib1 $lt1 $size1 $name1: &$ty1 = &$example1 } 236 | { $lib2 $lt2 $size2 $name2: &$ty2 = &$example2 } 237 | { from_ltfree_ref, arg => <$ty2>::try_specialize_from_ref(arg) } 238 | { Some(_some), None, Some(&$example2), None } 239 | } 240 | $args 241 | } 242 | $func! { 243 | { 244 | { $lib1 $lt1 $size1 $name1: &mut $ty1 = &mut $example1 } 245 | { $lib2 $lt2 $size2 $name2: &mut $ty2 = &mut $example2 } 246 | { from_ltfree_mut, arg => <$ty2>::try_specialize_from_mut(arg) } 247 | { Some(_some), None, Some(&mut $example2), None } 248 | } 249 | $args 250 | } 251 | }} 252 | mif! { if (all (eq $size1 sized) (eq $size2 sized)) { 253 | $func! { 254 | { 255 | { $lib1 $lt1 $size1 $name1: $ty1 = $example1 } 256 | { $lib2 $lt2 $size2 $name2: $ty2 = $example2 } 257 | { static, arg => arg.try_specialize_static::<$ty2>() } 258 | { Ok(_ok), Err(_err), Ok($example2), Err($example1) } 259 | } 260 | $args 261 | } 262 | }} 263 | $func! { 264 | { 265 | { $lib1 $lt1 $size1 $name1: &$ty1 = &$example1 } 266 | { $lib2 $lt2 $size2 $name2: &$ty2 = &$example2 } 267 | { static_ref, arg => arg.try_specialize_ref_static::<$ty2>() } 268 | { Some(_some), None, Some(&$example2), None } 269 | } 270 | $args 271 | } 272 | $func! { 273 | { 274 | { $lib1 $lt1 $size1 $name1: &mut $ty1 = &mut $example1 } 275 | { $lib2 $lt2 $size2 $name2: &mut $ty2 = &mut $example2 } 276 | { static_mut, arg => arg.try_specialize_mut_static::<$ty2>() } 277 | { Some(_some), None, Some(&mut $example2), None } 278 | } 279 | $args 280 | } 281 | }} 282 | } 283 | } 284 | 285 | /// Calls the callback macro will all checked types. 286 | /// 287 | /// Adding each additional type non-linearly increases compilation time. 288 | #[macro_export] 289 | macro_rules! for_all_types { 290 | ($func:ident ! $args:tt) => { 291 | $func! { 292 | [ 293 | { core ltfree sized unit: () = () } 294 | { core ltfree sized bool: bool = true } 295 | { core ltfree sized u32: u32 = 123_456_789 } 296 | { core ltfree sized i32: i32 = -123_456_789 } 297 | { core ltfree sized f64: f64 = 123.456 } 298 | { core ltfree sized u128: u128 = u128::MAX - 123_456_789 } 299 | { core ltfree sized array_u8_8: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8] } 300 | { core ltfree sized tuple_u32_f64_u8: (u32, f64, u8) = (987_654_321, 987.654, 123) } 301 | { core ltfree sized option_u32: Option = Some(123_123_123) } 302 | { 303 | core ltfree sized result_unit_i8: 304 | Result<(), ::core::num::NonZero> = 305 | Err(::core::num::NonZero::new(1).unwrap()) 306 | } 307 | { 308 | core ltfree unsized str: str = 309 | *unsafe { ::core::str::from_utf8_unchecked_mut(&mut { *b"bar" }) } 310 | // expression above is used to make string literal mutable if needed 311 | } 312 | { 313 | core ltfree unsized slice_option_u16: [Option] = [ 314 | Some(12_345), None, Some(-123), None, Some(1) 315 | ] 316 | } 317 | { core static sized str_static_ref: &'static str = "foo" } 318 | { 319 | alloc ltfree sized btreeset_string_vec_u8: 320 | ::std::collections::BTreeSet = 321 | $crate::common::btreeset_string_vec_u8_example() 322 | } 323 | { 324 | alloc static sized btreemap_cow_str_string: 325 | ::std::collections::BTreeMap< 326 | ::std::borrow::Cow<'static, str>, 327 | String 328 | > = $crate::common::btreemap_cow_str_string_example() 329 | } 330 | { 331 | alloc static unsized slice_cow_static_u32: 332 | [::std::borrow::Cow<'static, u8>] = [ 333 | ::std::borrow::Cow::Owned(12), 334 | ::std::borrow::Cow::Borrowed(&23), 335 | ::std::borrow::Cow::Owned(34), 336 | ::std::borrow::Cow::Borrowed(&45) 337 | ] 338 | } 339 | { 340 | std ltfree sized hashmap_i32_box_str: 341 | ::std::collections::HashMap> = 342 | $crate::common::hashmap_i32_box_str_example() 343 | } 344 | { 345 | std static sized hashmap_arc_path_static_str: 346 | ::std::collections::HashMap< 347 | ::std::sync::Arc<::std::path::Path>, 348 | &'static [u8] 349 | > = $crate::common::hashmap_arc_path_static_str_example() 350 | } 351 | ] 352 | $args 353 | } 354 | }; 355 | } 356 | 357 | #[cfg(feature = "alloc")] 358 | #[must_use] 359 | pub fn btreeset_string_vec_u8_example() -> BTreeSet { 360 | ["a", "b", "c"] 361 | .map(ToString::to_string) 362 | .into_iter() 363 | .collect() 364 | } 365 | 366 | #[cfg(feature = "alloc")] 367 | #[must_use] 368 | pub fn btreemap_cow_str_string_example() -> BTreeMap, String> { 369 | [ 370 | (Cow::Owned("a".to_string() + "bc"), String::from("qwe")), 371 | (Cow::Borrowed("efg"), String::from("asd")), 372 | (Cow::Borrowed("ijk"), String::from("zxc")), 373 | ] 374 | .into_iter() 375 | .collect() 376 | } 377 | 378 | #[cfg(feature = "std")] 379 | #[must_use] 380 | pub fn hashmap_i32_box_str_example() -> HashMap> { 381 | [ 382 | (1, Box::from("foo")), 383 | (2, Box::from("bar")), 384 | (3, Box::from("qwe")), 385 | ] 386 | .into_iter() 387 | .collect() 388 | } 389 | 390 | #[cfg(feature = "std")] 391 | #[must_use] 392 | pub fn hashmap_arc_path_static_str_example() -> HashMap, &'static [u8]> { 393 | [ 394 | (Arc::from(Path::new("here")), &b"first"[..]), 395 | (Arc::from(Path::new("there")), &[1, 2, 3]), 396 | (Arc::from(Path::new("nowhere")), &[123; 12]), 397 | ] 398 | .into_iter() 399 | .collect() 400 | } 401 | 402 | // When called, it implicitly coerce function to function pointer. 403 | #[expect(clippy::as_conversions, reason = "okay in tests")] 404 | pub const fn fn_raw_ptr(func: extern "Rust" fn(T) -> R) -> *const u8 { 405 | (func as *const fn(T) -> R).cast::() 406 | } 407 | -------------------------------------------------------------------------------- /src/unreliable/impls_trait.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::{Debug, Display, Write as FmtWrite}; 2 | use core::future::{Future, IntoFuture}; 3 | use core::hash::Hash; 4 | use core::ops::Deref; 5 | use core::panic::{RefUnwindSafe, UnwindSafe}; 6 | use core::str::FromStr; 7 | #[cfg(feature = "std")] 8 | use std::io::{Read as IoRead, Write as IoWrite}; 9 | 10 | use crate::LifetimeFree; 11 | 12 | /// Generates a function which returns `true` if the given type implements 13 | /// specified trait. Note that all the lifetimes are erased and not accounted 14 | /// for. 15 | /// 16 | /// Library tests ensure that the `impls_trait` checks are performed at compile 17 | /// time and are fully optimized with no runtime cost at `opt-level >= 1`. Note 18 | /// that the release profile uses `opt-level = 3` by default. 19 | /// 20 | /// Custom attributes: 21 | /// - `#[auto_doc]` attribute enables automatic documentation generation for the 22 | /// generated function including the `Reliability` documentation section. 23 | /// - `#[+reliability_doc]` attribute enables automatic generation of 24 | /// `Reliability` documentation section for the generated function. 25 | /// 26 | /// # Reliability 27 | /// 28 | /// While it is unlikely, there is still a possibility that the functions 29 | /// generated by this macro may return false negatives in future Rust versions. 30 | /// 31 | /// The correctness of the results returned by the functions depends on 32 | /// the following: 33 | /// - Documented behavior that if `T` implements `Eq`, two `Rc`s that point to 34 | /// the same allocation are always equal: 35 | /// . 36 | /// - Undocumented behavior that the `Rc::partial_eq` implementation for `T: Eq` 37 | /// will not use `PartialEq::eq` if both `Rc`s point to the same memory 38 | /// location. 39 | /// - The assumption that the undocumented short-circuit behavior described 40 | /// above will be retained for optimization purposes. 41 | /// 42 | /// There is no formal guarantee that the undocumented behavior described above 43 | /// will be retained. If the implementation changes in a future Rust version, 44 | /// the function may return a false negative, that is, it may return `false`, 45 | /// even though `T` implements the trait. However, the implementation guarantees 46 | /// that a false positive result is impossible, i.e., the function will never 47 | /// return true if `T` does not implement the trait in any future Rust version. 48 | /// 49 | /// Details: 50 | /// - , 51 | /// - , 52 | /// - , 53 | /// - . 54 | /// 55 | /// # Examples 56 | /// 57 | /// ```rust 58 | /// # #[cfg(all(feature = "alloc", feature = "unreliable"))] { 59 | /// use try_specialize::define_impls_trait_ignore_lt_fn; 60 | /// 61 | /// define_impls_trait_ignore_lt_fn!( 62 | /// #[auto_doc] pub impls_into_iterator_u32: IntoIterator 63 | /// ); 64 | /// assert!(impls_into_iterator_u32::<[u32; 4]>()); 65 | /// assert!(impls_into_iterator_u32::>()); 66 | /// assert!(!impls_into_iterator_u32::>()); 67 | /// # } 68 | /// ``` 69 | /// 70 | /// ```rust 71 | /// # #[cfg(all(feature = "alloc", feature = "unreliable"))] { 72 | /// use try_specialize::define_impls_trait_ignore_lt_fn; 73 | /// 74 | /// define_impls_trait_ignore_lt_fn!( 75 | /// pub impls_copy_eq_ord: Copy + Eq + Ord 76 | /// ); 77 | /// assert!(impls_copy_eq_ord::()); 78 | /// assert!(impls_copy_eq_ord::<[u32; 4]>()); 79 | /// assert!(!impls_copy_eq_ord::>()); 80 | /// # } 81 | /// ``` 82 | /// 83 | /// ```rust 84 | /// # #[cfg(all(feature = "alloc", feature = "unreliable"))] { 85 | /// use try_specialize::define_impls_trait_ignore_lt_fn; 86 | /// 87 | /// pub trait IsRef {} 88 | /// impl IsRef for &T where T: ?Sized {} 89 | /// 90 | /// pub trait IsMutRef {} 91 | /// impl IsMutRef for &mut T where T: ?Sized {} 92 | /// 93 | /// pub trait IsBox {} 94 | /// impl IsBox for Box where T: ?Sized {} 95 | /// 96 | /// define_impls_trait_ignore_lt_fn!( 97 | /// #[+reliability_doc] 98 | /// /// Returns `true` if the given type is a const reference like `&T`. 99 | /// pub is_ref: IsRef 100 | /// ); 101 | /// 102 | /// define_impls_trait_ignore_lt_fn!( 103 | /// #[+reliability_doc] 104 | /// /// Returns `true` if the given type is a mutable reference like 105 | /// /// `&mut T`. 106 | /// pub is_mut_ref: IsMutRef 107 | /// ); 108 | /// 109 | /// define_impls_trait_ignore_lt_fn!( 110 | /// #[+reliability_doc] 111 | /// /// Returns `true` if the given type is a `Box`-type. 112 | /// pub is_box: IsBox 113 | /// ); 114 | /// 115 | /// assert!(!is_ref::()); 116 | /// assert!(!is_ref::<[u32; 4]>()); 117 | /// assert!(!is_ref::>()); 118 | /// assert!(is_ref::<&u32>()); 119 | /// assert!(is_ref::<&[u32]>()); 120 | /// assert!(!is_ref::<&mut u32>()); 121 | /// 122 | /// assert!(!is_mut_ref::()); 123 | /// assert!(!is_mut_ref::<&u32>()); 124 | /// assert!(is_mut_ref::<&mut u32>()); 125 | /// 126 | /// assert!(!is_box::()); 127 | /// assert!(!is_box::<&u32>()); 128 | /// assert!(!is_box::<&mut u32>()); 129 | /// assert!(is_box::>()); 130 | /// assert!(is_box::>()); 131 | /// # } 132 | /// ``` 133 | #[macro_export] 134 | macro_rules! define_impls_trait_ignore_lt_fn { 135 | ( #[auto_doc] $( #[$meta:meta] )* $vis:vis $fn_name:ident: $( $bounds:tt )+ ) => { 136 | define_impls_trait_ignore_lt_fn! { 137 | #[doc = "Returns `true` if the given type implements `"] 138 | #[doc = stringify!( $( $bounds )+ )] 139 | #[doc = "`."] 140 | /// 141 | /// Use [`define_impls_trait_ignore_lt_fn`] macro to generate other 142 | /// trait implementation check functions. 143 | /// 144 | /// Library tests ensure that the `impls_trait` checks are performed 145 | /// at compile time and fully optimized with no runtime cost at 146 | /// `opt-level >= 1`. Note that the release profile uses 147 | /// `opt-level = 3` by default. 148 | /// 149 | /// [`define_impls_trait_ignore_lt_fn`]: https://docs.rs/try-specialize/latest/try_specialize/macro.define_impls_trait_ignore_lt_fn.html 150 | /// 151 | #[+reliability_doc] 152 | $( #[$meta] )* 153 | $vis $fn_name: $( $bounds )+ 154 | } 155 | }; 156 | ( 157 | $( #[$meta1:meta] )* #[+reliability_doc] $( #[$meta2:meta] )* 158 | $vis:vis $fn_name:ident: $( $bounds:tt )+ 159 | ) => { 160 | define_impls_trait_ignore_lt_fn! { 161 | $( #[$meta1] )* 162 | /// 163 | /// # Reliability 164 | /// 165 | /// While it is unlikely, there is still a possibility that this 166 | /// function may return false negatives in future Rust versions. 167 | /// 168 | /// The correctness of the results returned by the functions depends 169 | /// on the following: 170 | /// - Documented behavior that if `T` implements `Eq`, two `Rc`s 171 | /// that point to the same allocation are always equal: 172 | /// . 173 | /// - Undocumented behavior that the `Rc::partial_eq` implementation 174 | /// for `T: Eq` will not use `PartialEq::eq` if both `Rc`s point 175 | /// to the same memory location. 176 | /// - The assumption that the undocumented short-circuit behavior 177 | /// described above will be retained for optimization purposes. 178 | /// 179 | /// There is no formal guarantee that the undocumented behavior 180 | /// described above will be retained. If the implementation changes 181 | /// in a future Rust version, the function may return a false 182 | /// negative, that is, it may return `false`, even though `T` 183 | /// implements the trait. However, the implementation guarantees 184 | /// that a false positive result is impossible, i.e., the function 185 | /// will never return true if `T` does not implement the trait in 186 | /// any future Rust version. 187 | /// 188 | /// Details: 189 | /// - , 190 | /// - , 191 | /// - , 192 | /// - . 193 | /// 194 | $( #[$meta2] )* 195 | $vis $fn_name: $( $bounds )+ 196 | } 197 | }; 198 | ( $( #[$meta:meta] )* $vis:vis $fn_name:ident: $( $bounds:tt )+ ) => { 199 | $( #[$meta] )* 200 | #[inline] 201 | #[must_use] 202 | $vis fn $fn_name() -> bool 203 | where 204 | T: ?Sized 205 | { 206 | struct Impl<'a, T>(&'a ::core::cell::Cell, ::core::marker::PhantomData) 207 | where 208 | T: ?Sized; 209 | 210 | impl PartialEq for Impl<'_, T> 211 | where 212 | T: ?Sized, 213 | { 214 | fn eq(&self, _other: &Self) -> bool { 215 | let _ = self.0.set(true); 216 | true 217 | } 218 | } 219 | 220 | impl Eq for Impl<'_, T> where T: ?Sized + $( $bounds )+ {} 221 | 222 | let not_impls_trait = ::core::cell::Cell::new(false); 223 | let rc = $crate::macro_deps::Rc::new(Impl( 224 | ¬_impls_trait, 225 | ::core::marker::PhantomData:: 226 | )); 227 | let _ = rc == rc; 228 | !not_impls_trait.get() 229 | } 230 | }; 231 | } 232 | 233 | define_impls_trait_ignore_lt_fn!(#[auto_doc] pub impls_sized_weak: Sized); 234 | define_impls_trait_ignore_lt_fn!(#[auto_doc] pub impls_send_weak: Send); 235 | define_impls_trait_ignore_lt_fn!(#[auto_doc] pub impls_sync_weak: Sync); 236 | define_impls_trait_ignore_lt_fn!(#[auto_doc] pub impls_unpin_weak: Unpin); 237 | define_impls_trait_ignore_lt_fn!(#[auto_doc] pub impls_unwind_safe_weak: UnwindSafe); 238 | define_impls_trait_ignore_lt_fn!(#[auto_doc] pub impls_ref_unwind_safe_weak: RefUnwindSafe); 239 | 240 | define_impls_trait_ignore_lt_fn!(#[auto_doc] pub impls_deref_weak: Deref); 241 | define_impls_trait_ignore_lt_fn!( 242 | #[auto_doc] 243 | /// # Examples 244 | /// 245 | /// ```rust 246 | /// # #[cfg(all(feature = "alloc", feature = "unreliable"))] { 247 | /// use core::sync::atomic::{AtomicU32, Ordering as AtomicOrdering}; 248 | /// 249 | /// use try_specialize::unreliable::impls_copy_weak; 250 | /// 251 | /// #[derive(Eq, PartialEq, Debug)] 252 | /// pub struct ArrayLike { 253 | /// # inner: [T; N] 254 | /// } 255 | /// 256 | /// impl From<[T; N]> for ArrayLike { 257 | /// #[inline] 258 | /// fn from(value: [T; N]) -> Self { 259 | /// // ... 260 | /// # Self { inner: value } 261 | /// } 262 | /// } 263 | /// 264 | /// impl AsRef<[T; N]> for ArrayLike { 265 | /// #[inline] 266 | /// fn as_ref(&self) -> &[T; N] { 267 | /// // ... 268 | /// # &self.inner 269 | /// } 270 | /// } 271 | /// 272 | /// static DEBUG: AtomicU32 = AtomicU32::new(0); 273 | /// 274 | /// impl Clone for ArrayLike 275 | /// where 276 | /// T: Clone 277 | /// { 278 | /// #[inline] 279 | /// fn clone(&self) -> Self { 280 | /// if impls_copy_weak::() { 281 | /// DEBUG.store(101, AtomicOrdering::Relaxed); 282 | /// // Fast path for `T: Copy`. 283 | /// unsafe { std::mem::transmute_copy(self) } 284 | /// } else { 285 | /// DEBUG.store(202, AtomicOrdering::Relaxed); 286 | /// Self::from(self.as_ref().clone()) 287 | /// } 288 | /// } 289 | /// } 290 | /// 291 | /// #[derive(Clone, Eq, PartialEq, Debug)] 292 | /// struct NonCopiable(pub T); 293 | /// 294 | /// assert_eq!( 295 | /// ArrayLike::from([1, 2, 3]).clone(), 296 | /// ArrayLike::from([1, 2, 3]) 297 | /// ); 298 | /// assert_eq!(DEBUG.load(AtomicOrdering::Relaxed), 101); 299 | /// 300 | /// assert_eq!( 301 | /// ArrayLike::from([NonCopiable(1), NonCopiable(2)]).clone(), 302 | /// ArrayLike::from([NonCopiable(1), NonCopiable(2)]) 303 | /// ); 304 | /// assert_eq!(DEBUG.load(AtomicOrdering::Relaxed), 202); 305 | /// # } 306 | /// ``` 307 | pub impls_copy_weak: Copy 308 | ); 309 | define_impls_trait_ignore_lt_fn!(#[auto_doc] pub impls_clone_weak: Clone); 310 | define_impls_trait_ignore_lt_fn!( 311 | #[auto_doc] 312 | /// # Examples 313 | /// 314 | /// ```rust 315 | /// # #[cfg(all(feature = "alloc", feature = "unreliable"))] { 316 | /// 317 | /// use core::sync::atomic::{AtomicU32, Ordering as AtomicOrdering}; 318 | /// # use std::sync::Arc; 319 | /// 320 | /// use try_specialize::unreliable::impls_eq_weak; 321 | /// 322 | /// #[derive(Clone, Debug)] 323 | /// pub struct ArcLike { 324 | /// // ... 325 | /// # inner: Arc, 326 | /// } 327 | /// 328 | /// impl ArcLike { 329 | /// #[inline] 330 | /// fn new(value: T) -> Self { 331 | /// // ... 332 | /// # Self { 333 | /// # inner: Arc::new(value), 334 | /// # } 335 | /// } 336 | /// 337 | /// #[inline] 338 | /// fn as_ptr(&self) -> *const T { 339 | /// // ... 340 | /// # Arc::as_ptr(&self.inner) 341 | /// } 342 | /// } 343 | /// 344 | /// impl AsRef for ArcLike { 345 | /// #[inline] 346 | /// fn as_ref(&self) -> &T { 347 | /// // ... 348 | /// # &*self.inner 349 | /// } 350 | /// } 351 | /// 352 | /// impl PartialEq for ArcLike 353 | /// where 354 | /// T: PartialEq, 355 | /// { 356 | /// #[inline] 357 | /// fn eq(&self, other: &Self) -> bool { 358 | /// // Fast path for `T: Eq`. 359 | /// if impls_eq_weak::() && self.as_ptr() == other.as_ptr() { 360 | /// // Fast path for `T: Eq` if pointers are equal. 361 | /// return true; 362 | /// } 363 | /// self.as_ref() == other.as_ref() 364 | /// } 365 | /// } 366 | /// 367 | /// #[derive(Copy, Clone, Eq, Debug)] 368 | /// struct Wrapper(pub T); 369 | /// 370 | /// static COUNTER: AtomicU32 = AtomicU32::new(0); 371 | /// 372 | /// impl PartialEq for Wrapper 373 | /// where 374 | /// T: PartialEq, 375 | /// { 376 | /// #[inline] 377 | /// fn eq(&self, other: &Self) -> bool { 378 | /// let _ = COUNTER.fetch_add(1, AtomicOrdering::Relaxed); 379 | /// self.0 == other.0 380 | /// } 381 | /// } 382 | /// 383 | /// let arc_like1 = ArcLike::new(Wrapper(42_u32)); 384 | /// let arc_like2 = arc_like1.clone(); 385 | /// assert_eq!(arc_like1, arc_like2); 386 | /// // `u32` implements Eq. Fast path used. Counter not incremented. 387 | /// assert_eq!(COUNTER.load(AtomicOrdering::Relaxed), 0); 388 | /// 389 | /// let arc_like1 = ArcLike::new(Wrapper(123.456_f64)); 390 | /// let arc_like2 = arc_like1.clone(); 391 | /// assert_eq!(arc_like1, arc_like2); 392 | /// // `f64` doesn't implement Eq. Fast path is not used. 393 | /// // Counter incremented. 394 | /// assert_eq!(COUNTER.load(AtomicOrdering::Relaxed), 1); 395 | /// # } 396 | /// ``` 397 | pub impls_eq_weak: Eq 398 | ); 399 | define_impls_trait_ignore_lt_fn!(#[auto_doc] pub impls_partial_eq_weak: PartialEq); 400 | define_impls_trait_ignore_lt_fn!(#[auto_doc] pub impls_ord_weak: Ord); 401 | define_impls_trait_ignore_lt_fn!(#[auto_doc] pub impls_partial_ord_weak: PartialOrd); 402 | define_impls_trait_ignore_lt_fn!(#[auto_doc] pub impls_hash_weak: Hash); 403 | define_impls_trait_ignore_lt_fn!(#[auto_doc] pub impls_default_weak: Default); 404 | define_impls_trait_ignore_lt_fn!(#[auto_doc] pub impls_debug_weak: Debug); 405 | define_impls_trait_ignore_lt_fn!(#[auto_doc] pub impls_display_weak: Display); 406 | define_impls_trait_ignore_lt_fn!(#[auto_doc] pub impls_from_str_weak: FromStr); 407 | define_impls_trait_ignore_lt_fn!(#[auto_doc] pub impls_iterator_weak: Iterator); 408 | define_impls_trait_ignore_lt_fn!(#[auto_doc] pub impls_into_iterator_weak: IntoIterator); 409 | define_impls_trait_ignore_lt_fn!(#[auto_doc] pub impls_future_weak: Future); 410 | define_impls_trait_ignore_lt_fn!(#[auto_doc] pub impls_into_future_weak: IntoFuture); 411 | define_impls_trait_ignore_lt_fn!(#[auto_doc] pub impls_fmt_write_weak: FmtWrite); 412 | #[cfg(feature = "std")] 413 | #[cfg_attr(docsrs, doc(cfg(feature = "std")))] 414 | define_impls_trait_ignore_lt_fn!(#[auto_doc] pub impls_io_read_weak: IoRead); 415 | #[cfg(feature = "std")] 416 | #[cfg_attr(docsrs, doc(cfg(feature = "std")))] 417 | define_impls_trait_ignore_lt_fn!(#[auto_doc] pub impls_io_write_weak: IoWrite); 418 | 419 | define_impls_trait_ignore_lt_fn!(#[auto_doc] pub impls_lifetime_free_weak: LifetimeFree); 420 | 421 | #[cfg(test)] 422 | mod tests { 423 | #[cfg(feature = "alloc")] 424 | use alloc::string::String; 425 | #[cfg(feature = "alloc")] 426 | use alloc::vec::Vec; 427 | 428 | use crate::unreliable::{ 429 | impls_clone_weak, impls_copy_weak, impls_eq_weak, impls_lifetime_free_weak, 430 | impls_partial_eq_weak, 431 | }; 432 | 433 | #[test] 434 | fn test_impls_copy() { 435 | #[derive(Copy, Clone)] 436 | struct Copiable; 437 | #[derive(Clone)] 438 | struct Cloneable; 439 | struct NonCloneable; 440 | 441 | assert!(impls_copy_weak::<()>()); 442 | assert!(impls_copy_weak::()); 443 | assert!(impls_copy_weak::()); 444 | 445 | assert!(impls_copy_weak::()); 446 | assert!(!impls_copy_weak::()); 447 | assert!(!impls_copy_weak::()); 448 | } 449 | 450 | #[cfg(feature = "alloc")] 451 | #[test] 452 | fn test_impls_copy_alloc() { 453 | assert!(impls_copy_weak::<&String>()); 454 | assert!(impls_copy_weak::<&Vec>()); 455 | assert!(!impls_copy_weak::()); 456 | assert!(!impls_copy_weak::>()); 457 | assert!(!impls_copy_weak::<&mut String>()); 458 | assert!(!impls_copy_weak::<&mut Vec>()); 459 | } 460 | 461 | #[test] 462 | fn test_impls_clone() { 463 | #[derive(Copy, Clone)] 464 | struct Copiable; 465 | #[derive(Clone)] 466 | struct Cloneable; 467 | struct NonCloneable; 468 | 469 | assert!(impls_clone_weak::<()>()); 470 | assert!(impls_clone_weak::()); 471 | assert!(impls_clone_weak::()); 472 | 473 | assert!(impls_clone_weak::()); 474 | assert!(impls_clone_weak::()); 475 | assert!(!impls_clone_weak::()); 476 | } 477 | 478 | #[test] 479 | fn test_impls_eq() { 480 | assert!(impls_eq_weak::<()>()); 481 | assert!(impls_eq_weak::()); 482 | assert!(!impls_eq_weak::()); 483 | } 484 | 485 | #[cfg(feature = "alloc")] 486 | #[test] 487 | fn test_impls_eq_alloc() { 488 | assert!(impls_eq_weak::<&String>()); 489 | assert!(impls_eq_weak::<&Vec>()); 490 | assert!(impls_eq_weak::()); 491 | assert!(impls_eq_weak::>()); 492 | assert!(impls_eq_weak::<&mut String>()); 493 | assert!(impls_eq_weak::<&mut Vec>()); 494 | } 495 | 496 | #[test] 497 | fn test_impls_partial_eq() { 498 | assert!(impls_partial_eq_weak::<()>()); 499 | assert!(impls_partial_eq_weak::()); 500 | assert!(impls_partial_eq_weak::()); 501 | } 502 | 503 | #[cfg(feature = "alloc")] 504 | #[test] 505 | fn test_impls_partial_eq_alloc() { 506 | assert!(impls_partial_eq_weak::<&String>()); 507 | assert!(impls_partial_eq_weak::<&Vec>()); 508 | assert!(impls_partial_eq_weak::()); 509 | assert!(impls_partial_eq_weak::>()); 510 | assert!(impls_partial_eq_weak::<&mut String>()); 511 | assert!(impls_partial_eq_weak::<&mut Vec>()); 512 | } 513 | 514 | #[test] 515 | fn test_lifetime_free() { 516 | assert!(impls_lifetime_free_weak::<()>()); 517 | assert!(impls_lifetime_free_weak::()); 518 | assert!(impls_lifetime_free_weak::()); 519 | } 520 | 521 | #[cfg(feature = "alloc")] 522 | #[test] 523 | fn test_lifetime_free_alloc() { 524 | assert!(!impls_lifetime_free_weak::<&String>()); 525 | assert!(!impls_lifetime_free_weak::<&Vec>()); 526 | assert!(impls_lifetime_free_weak::()); 527 | assert!(impls_lifetime_free_weak::>()); 528 | assert!(!impls_lifetime_free_weak::<&mut String>()); 529 | assert!(!impls_lifetime_free_weak::<&mut Vec>()); 530 | } 531 | } 532 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # try-specialize 2 | 3 | [![Build Status](https://github.com/zheland/try-specialize/workflows/build/badge.svg)](https://github.com/zheland/try-specialize/actions) 4 | [![Latest Version](https://img.shields.io/crates/v/try-specialize.svg)](https://crates.io/crates/try-specialize) 5 | [![Documentation](https://docs.rs/try-specialize/badge.svg)](https://docs.rs/try-specialize) 6 | [![Codecov](https://codecov.io/gh/zheland/try-specialize/graph/badge.svg)](https://codecov.io/gh/zheland/try-specialize) 7 | [![Dependencies status](https://deps.rs/repo/github/zheland/try-specialize/status.svg)](https://deps.rs/repo/github/zheland/try-specialize) 8 | [![Downloads](https://img.shields.io/crates/d/try-specialize)](https://crates.io/crates/try-specialize) 9 | [![License](https://img.shields.io/crates/l/try-specialize)](https://github.com/zheland/try-specialize/#license) 10 | [![MSRV 1.82+](https://img.shields.io/badge/rustc-1.82+-blue.svg)](https://blog.rust-lang.org/2024/10/17/Rust-1.82.0/) 11 | 12 | The `try-specialize` crate provides limited, [zero-cost](#zero-cost) 13 | specialization in generic context on stable Rust. 14 | 15 | ```rust 16 | use try_specialize::TrySpecialize; 17 | 18 | fn example_specialize_by_value(value: T) -> Result { 19 | value.try_specialize() 20 | // Same as: `::try_specialize::(value)`. 21 | // `try_specialize::` specializes from `Self` to `T, where T: LifetimeFree`. 22 | } 23 | 24 | fn example_specialize_by_ref(value: &T) -> Option<&str> { 25 | value.try_specialize_ref() 26 | // Same as: `::try_specialize_ref::(value)`. 27 | // `try_specialize_ref::` specializes from `&Self` to `&T, where T: LifetimeFree`. 28 | } 29 | 30 | assert_eq!(example_specialize_by_value(123_u32), Ok(123)); 31 | assert_eq!(example_specialize_by_value(123_i32), Err(123)); 32 | assert_eq!(example_specialize_by_ref("foo"), Some("foo")); 33 | assert_eq!(example_specialize_by_ref(&123_u32), None); 34 | assert_eq!(example_specialize_by_ref(&[1, 2, 3]), None); 35 | ``` 36 | 37 | ## Introduction 38 | 39 | While specialization in Rust can be a tempting solution in many use cases, 40 | it is usually more idiomatic to use traits instead. Traits are the idiomatic 41 | way to achieve polymorphism in Rust, promoting better code clarity, 42 | reusability, and maintainability. 43 | 44 | However, specialization can be suitable when you need to optimize 45 | performance by providing specialized implementations for some types without 46 | altering the code logic. It's also useful in specific, type-level 47 | programming use cases like comparisons between types from different 48 | libraries. 49 | 50 | For a simple use cases, consider the [`castaway`] crate, which offers a much 51 | simpler API. On nightly Rust, consider using [`min_specialization`] feature 52 | instead. The Rust standard library already uses [`min_specialization`] for 53 | many optimizations. For a more detailed comparison, see the 54 | [Alternative crates](#alternative-crates) section below. 55 | 56 | ## About 57 | 58 | This crate offers a comprehensive API for addressing various specialization 59 | challenges, reducing the need for unsafe code. It provides specialization 60 | from unconstrained types, to unconstrained types, between 'static types, 61 | and between type references and mutable references, and more. 62 | 63 | Library tests ensure that specializations are 64 | performed at compile time and are fully optimized with no runtime cost at 65 | `opt-level >= 1`. Note that the [release] profile uses `opt-level = 3` 66 | by default. 67 | 68 | ## Usage 69 | 70 | Add this to your `Cargo.toml`: 71 | 72 | ```toml 73 | [dependencies] 74 | try-specialize = "0.1.2" 75 | ``` 76 | 77 | Then, you can use [`TrySpecialize`] trait methods like 78 | [`TrySpecialize::try_specialize`], [`TrySpecialize::try_specialize_ref`] and 79 | [`TrySpecialize::try_specialize_static`]. To check the possibility of 80 | specialization in advance and use it infallibly multiple times, including 81 | reversed or mapped specialization, use [`Specialization`] struct methods. 82 | 83 | Note that unlike casting, [subtyping], and [coercion], specialization does 84 | not alter the underlying type or data. It merely qualifies the underlying 85 | types of generics, succeeding only when the underlying types of `T1` and 86 | `T2` are equal. 87 | 88 | ## Examples 89 | 90 | Specialize type to any [`LifetimeFree`] type: 91 | ```rust 92 | use try_specialize::TrySpecialize; 93 | 94 | fn func(value: T) { 95 | match value.try_specialize::<(u32, String)>() { 96 | Ok(value) => specialized_impl(value), 97 | Err(value) => default_impl(value), 98 | } 99 | } 100 | 101 | fn specialized_impl(_value: (u32, String)) {} 102 | fn default_impl(_value: T) {} 103 | ``` 104 | 105 | Specialize `'static` type to any `'static` type: 106 | ```rust 107 | use try_specialize::TrySpecialize; 108 | 109 | fn func(value: T) 110 | where 111 | T: 'static, 112 | { 113 | match value.try_specialize_static::<(u32, &'static str)>() { 114 | Ok(value) => specialized_impl(value), 115 | Err(value) => default_impl(value), 116 | } 117 | } 118 | 119 | fn specialized_impl(_value: (u32, &'static str)) {} 120 | fn default_impl(_value: T) {} 121 | ``` 122 | 123 | Specialize `Sized` or `Unsized` type reference to any [`LifetimeFree`] type 124 | reference: 125 | ```rust 126 | use try_specialize::TrySpecialize; 127 | 128 | fn func(value: &T) 129 | where 130 | T: ?Sized, // Relax the implicit `Sized` bound. 131 | { 132 | match value.try_specialize_ref::() { 133 | Some(value) => specialized_impl(value), 134 | None => default_impl(value), 135 | } 136 | } 137 | 138 | fn specialized_impl(_value: &str) {} 139 | fn default_impl(_value: &T) {} 140 | ``` 141 | 142 | Specialize `Sized` or `Unsized` type mutable reference to any 143 | [`LifetimeFree`] type mutable reference: 144 | ```rust 145 | use try_specialize::TrySpecialize; 146 | 147 | fn func(value: &mut T) 148 | where 149 | T: ?Sized, // Relax the implicit `Sized` bound. 150 | { 151 | match value.try_specialize_mut::<[u8]>() { 152 | Some(value) => specialized_impl(value), 153 | None => default_impl(value), 154 | } 155 | } 156 | 157 | fn specialized_impl(_value: &mut [u8]) {} 158 | fn default_impl(_value: &mut T) {} 159 | ``` 160 | 161 | Specialize a third-party library container with generic types: 162 | ```rust 163 | use try_specialize::{Specialization, TypeFn}; 164 | 165 | fn func(value: hashbrown::HashMap) { 166 | struct MapIntoHashMap; 167 | impl TypeFn<(K, V)> for MapIntoHashMap { 168 | type Output = hashbrown::HashMap; 169 | } 170 | 171 | if let Some(spec) = Specialization::<(K, V), (u32, char)>::try_new() { 172 | let spec = spec.map::(); 173 | let value: hashbrown::HashMap = spec.specialize(value); 174 | specialized_impl(value); 175 | } else { 176 | default_impl(value); 177 | } 178 | } 179 | 180 | fn default_impl(_value: hashbrown::HashMap) {} 181 | fn specialized_impl(_value: hashbrown::HashMap) {} 182 | ``` 183 | 184 | For a more comprehensive example, see the [`examples/encode.rs`], which 185 | implements custom data encoders and decoders with per-type encoding and 186 | decoding errors and optimized byte array encoding and decoding. 187 | The part of this example related to the `Encode` implementation for a slice: 188 | ```rust 189 | // ... 190 | 191 | impl Encode for [T] 192 | where 193 | T: Encode, 194 | { 195 | type EncodeError = T::EncodeError; 196 | 197 | #[inline] 198 | fn encode_to(&self, writer: &mut W) -> Result<(), Self::EncodeError> 199 | where 200 | W: ?Sized + Write, 201 | { 202 | if let Some(spec) = Specialization::<[T], [u8]>::try_new() { 203 | // Specialize self from `[T; N]` to `[u32; N]` 204 | let bytes: &[u8] = spec.specialize_ref(self); 205 | // Map type specialization to its associated error specialization. 206 | let spec_err = spec.rev().map::(); 207 | writer 208 | .write_all(bytes) 209 | // Specialize error from `io::Error` to `Self::EncodeError`. 210 | .map_err(|err| spec_err.specialize(err))?; 211 | } else { 212 | for item in self { 213 | item.encode_to(writer)?; 214 | } 215 | } 216 | Ok(()) 217 | } 218 | } 219 | 220 | // ... 221 | ``` 222 | 223 | Find values by type in generic composite types: 224 | ```rust 225 | use try_specialize::{LifetimeFree, TrySpecialize}; 226 | 227 | pub trait ConsListLookup { 228 | fn find(&self) -> Option<&T> 229 | where 230 | T: ?Sized + LifetimeFree; 231 | } 232 | 233 | impl ConsListLookup for () { 234 | #[inline] 235 | fn find(&self) -> Option<&T> 236 | where 237 | T: ?Sized + LifetimeFree, 238 | { 239 | None 240 | } 241 | } 242 | 243 | impl ConsListLookup for (T1, T2) 244 | where 245 | T2: ConsListLookup, 246 | { 247 | #[inline] 248 | fn find(&self) -> Option<&T> 249 | where 250 | T: ?Sized + LifetimeFree, 251 | { 252 | self.0.try_specialize_ref().or_else(|| self.1.find::()) 253 | } 254 | } 255 | 256 | #[derive(Eq, PartialEq, Debug)] 257 | struct StaticStr(&'static str); 258 | // SAFETY: It is safe to implement `LifetimeFree` for structs with no 259 | // parameters. 260 | unsafe impl LifetimeFree for StaticStr {} 261 | 262 | let input = ( 263 | 123_i32, 264 | ( 265 | [1_u32, 2, 3, 4], 266 | (1_i32, (StaticStr("foo"), (('a', false), ()))), 267 | ), 268 | ); 269 | 270 | assert_eq!(input.find::(), None); 271 | assert_eq!(input.find::(), Some(&123_i32)); 272 | assert_eq!(input.find::<[u32; 4]>(), Some(&[1, 2, 3, 4])); 273 | assert_eq!(input.find::<[u32]>(), None); 274 | assert_eq!(input.find::(), Some(&StaticStr("foo"))); 275 | assert_eq!(input.find::(), None); 276 | assert_eq!(input.find::<(char, bool)>(), Some(&('a', false))); 277 | ``` 278 | 279 | ## Documentation 280 | 281 | [API Documentation] 282 | 283 | ## Feature flags 284 | 285 | - `alloc` (implied by `std`, enabled by default): enables [`LifetimeFree`] 286 | implementations for `alloc` types, like `Box`, `Arc`, `String`, `Vec`, 287 | `BTreeMap` etc. 288 | - `std` (enabled by default): enables `alloc` feature and [`LifetimeFree`] 289 | implementations for `std` types, like `OsStr`, `Path`, `PathBuf`, 290 | `Instant`, `HashMap` etc. 291 | - `unreliable`: enables functions, methods and macros that rely on Rust 292 | standard library undocumented behavior. Refer to the [`unreliable`] module 293 | documentation for details. 294 | 295 | ## How it works 296 | 297 | - Type comparison between `'static` types compares their [`TypeId::of`]s. 298 | - Type comparison between unconstrained and [`LifetimeFree`] type treats 299 | them as `'static` and compares their [`TypeId::of`]s. 300 | - Specialization relies on type comparison and [`transmute_copy`] when the 301 | equality of types is established. 302 | - Unreliable trait implementation checks are performed using an expected, 303 | but undocumented behavior of the Rust stdlib [`PartialEq`] implementation 304 | for [`Arc`]. [`Arc::eq`] uses fast path comparing references before 305 | comparing data if `T` implements [`Eq`]. 306 | 307 | ## Alternative crates 308 | 309 | - [`castaway`]: A similar crate with a much simpler macro-based API. The 310 | macro uses [Autoref-Based Specialization] and automatically determines the 311 | appropriate type of specialization, making it much easier to use. However, 312 | if no specialization is applicable because of the same [Autoref-Based 313 | Specialization], the compiler generates completely unclear errors, which 314 | makes it difficult to use it in complex cases. Internally uses `unsafe` 315 | code for type comparison and specialization. 316 | - [`coe-rs`]: Smaller and simpler, but supports only static types and don't 317 | safely combine type equality check and specialization. Internally uses 318 | `unsafe` code for type specialization. 319 | - [`downcast-rs`]: Specialized on trait objects (`dyn`) downcasting. Can't 320 | be used to specialize unconstrained types. 321 | - [`syllogism`] and [`syllogism_macro`]: Requires to provide all possible 322 | types to macro that generate a lot of boilerplate code and can't be used 323 | to specialize stdlib types because of orphan rules. 324 | - [`specialize`](https://crates.io/crates/specialize): Requires nightly. 325 | Adds a simple macro to inline nightly [`min_specialization`] usage into 326 | simple `if let` expressions. 327 | - [`specialized-dispatch`]: Requires nightly. Adds a macro to inline nightly 328 | [`min_specialization`] usage into a `match`-like macro. 329 | - [`spez`]: Specializes expression types, using [Autoref-Based 330 | Specialization]. It won't works in generic context but can be used in the 331 | code generated by macros. 332 | - [`impls`]: Determine if a type implements a trait. Can't detect erased 333 | type bounds, so not applicable in generic context, but can be used in the 334 | code generated by macros. 335 | 336 | ### Comparison of libraries supporting specialization in generic context: 337 | 338 | | | crate
`try-specialize` | crate
[`castaway`] | crate
[`coe-rs`] | crate
[`downcast-rs`] | crate
[`syllogism`] | [`min_spec...`]
nightly feature | crate
[`specialize`](https://crates.io/crates/specialize) | crate
[`spec...ch`] 339 | | --: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | 340 | | Checked version | `0.1.2` | `0.2.3` | `0.1.2` | `1.2.1` | `0.1.3` | N/A | `0.0.3` | `0.2.1` | 341 | | Rust toolchain | **Stable** | **Stable** | **Stable** | **Stable** | **Stable** | Nightly | Nightly | Nightly | 342 | | API complexity | Complex | **Simple** | **Simple** | Moderate | **Simple** | **Simple** | **Simple** | **Simple** | 343 | | API difficulty | Difficult | **Easy** | **Easy** | Moderate | Moderate | **Easy** | **Easy** | Moderate | 344 | | Zero-cost (compile-time optimized) | **YES** | **YES** | **YES** | no | **YES** | **YES** | **YES** | **YES** | 345 | | Safely combines type eq check and specialization | **YES** | **YES** | no | **YES** | **YES** | **YES** |**YES** | **YES** | 346 | | Specialize value references | **YES** | **YES** | **YES** | N/A | **YES** | **YES** | **YES** | no | 347 | | Specialize values | **YES** | **YES** | no | N/A | **YES** | **YES** | **YES** | **YES** | 348 | | Specialize values without consume on failure | **YES** | **YES** | no | N/A | **YES** | **YES** | no | **YES** | 349 | | Limited non-static value specialization | **YES** | **YES** | no | N/A | **YES** | **YES** | **YES** | **YES** | 350 | | Full non-static value specialization | no | no | no | N/A | **YES** | no | no | no | 351 | | Specialize trait objects (`dyn`) | N/A | N/A | N/A | **YES** | N/A | N/A | N/A | N/A | 352 | | Compare types without instantiation | **YES** | no | **YES** | no | **YES** | **YES** | **YES** | no | 353 | | Support std types | **YES** | **YES** | **YES** | **YES** | no | **YES** | **YES** | **YES** | 354 | | Specialize from unconstrained type | **YES** | **YES** | no | no | no | **YES** | **YES** | **YES** | 355 | | Specialize to unconstrained type | **YES** | no | no | no | no | **YES** | **YES** | **YES** | 356 | | Check generic implements "erased" trait | **YES**, but [`unreliable`] | no | no | no | no | **YES** | **YES** | **YES** | 357 | | Specialize to generic with added bounds | no | no | no | no | no | **YES** | **YES** | **YES** | 358 | | API based on | Traits | Macros | Traits | Macros + Traits | Traits | Language | Macros | Macros | 359 | | Type comparison implementation based on | [`TypeId`]
+ [`transmute`] | [`TypeId`]
+ [`transmute`] |[`TypeId`] | N/A | Traits | Language | Nightly
feature | Nightly
feature | 360 | | Type casting implementation based on | [`transmute_copy`] | [`ptr::read`] | [`transmute`] | [`std::any::Any`] | Traits | Language | Nightly
feature | Nightly
feature | 361 | | Implementation free of `unsafe` | no | no | no | **YES** | **YES** | **YES** | **YES** | **YES** | 362 | 363 | ### Primitive example of the value specialization using different libraries 364 | 365 |
366 | 367 | crate `try_specialize`: 368 | 369 | ```rust 370 | use try_specialize::TrySpecialize; 371 | 372 | fn spec(value: T) -> Result { 373 | value.try_specialize() 374 | } 375 | 376 | assert_eq!(spec(42_u32), Ok(42)); 377 | assert_eq!(spec(42_i32), Err(42)); 378 | assert_eq!(spec("abc"), Err("abc")); 379 | ``` 380 | 381 | 382 | 383 | crate [`castaway`]: 384 | 385 | ```rust 386 | use castaway::cast; 387 | 388 | fn spec(value: T) -> Result { 389 | cast!(value, _) 390 | } 391 | 392 | assert_eq!(spec(42_u32), Ok(42)); 393 | assert_eq!(spec(42_i32), Err(42)); 394 | assert_eq!(spec("abc"), Err("abc")); 395 | ``` 396 | 397 |
398 | 399 | crate [`coe-rs`]: 400 | 401 | ```rust 402 | use coe::{is_same, Coerce}; 403 | 404 | // Library don't support non-reference. 405 | // specialization. Using reference. 406 | fn spec(value: &T) -> Option<&u32> 407 | where 408 | // Library don't support specialization of 409 | // unconstrained non-static types. 410 | T: 'static, 411 | { 412 | is_same::().then(|| value.coerce()) 413 | } 414 | 415 | fn main() { 416 | assert_eq!(spec(&42_u32), Some(&42)); 417 | assert_eq!(spec(&42_i32), None); 418 | assert_eq!(spec(&"abc"), None); 419 | } 420 | ``` 421 | 422 | 423 | 424 | crates [`downcast-rs`]: 425 | 426 | ```rust 427 | use downcast_rs::{impl_downcast, DowncastSync}; 428 | 429 | trait Base: DowncastSync {} 430 | impl_downcast!(sync Base); 431 | 432 | // Library requires all specializable 433 | // types to be defined in advance. 434 | impl Base for u32 {} 435 | impl Base for i32 {} 436 | impl Base for &'static str {} 437 | 438 | // Library support only trait objects (`dyn`). 439 | fn spec(value: &dyn Base) -> Option<&u32> { 440 | value.downcast_ref::() 441 | } 442 | 443 | fn main() { 444 | assert_eq!(spec(&42_u32), Some(&42)); 445 | assert_eq!(spec(&42_i32), None); 446 | assert_eq!(spec(&"abc"), None); 447 | } 448 | ``` 449 | 450 |
451 | 452 | crate [`specialize`](https://crates.io/crates/specialize): 453 | 454 | ```rust 455 | // Requires nightly. 456 | #![feature(min_specialization)] 457 | 458 | use specialize::constrain; 459 | 460 | // Library don't support non-consuming 461 | // value specialization. Using reference. 462 | fn spec(value: &T) -> Option<&u32> { 463 | constrain!(ref value as u32) 464 | } 465 | 466 | assert_eq!(spec(&42_u32), Some(&42)); 467 | assert_eq!(spec(&42_i32), None); 468 | assert_eq!(spec("abc"), None); 469 | ``` 470 | 471 | 472 | 473 | crate [`specialized-dispatch`]: 474 | ```rust 475 | // Requires nightly. 476 | #![feature(min_specialization)] 477 | 478 | use specialized_dispatch::specialized_dispatch; 479 | 480 | // The library don't support using generics. 481 | // from outer item. Using `Option`. 482 | fn spec(value: T) -> Option { 483 | specialized_dispatch! { 484 | T -> Option, 485 | fn (value: u32) => Some(value), 486 | default fn (_: T) => None, 487 | value, 488 | } 489 | } 490 | 491 | assert_eq!(spec(42_u32), Some(42)); 492 | assert_eq!(spec(42_i32), None); 493 | assert_eq!(spec("abc"), None); 494 | ``` 495 | 496 |
497 | 498 | crates [`syllogism`] and [`syllogism_macro`]: 499 | 500 | ```rust 501 | use syllogism::{Distinction, Specialize}; 502 | use syllogism_macro::impl_specialization; 503 | 504 | // Library specialization can not be 505 | // implemented for std types because of 506 | // orphan rules. Using custom local types. 507 | #[derive(Eq, PartialEq, Debug)] 508 | struct U32(u32); 509 | #[derive(Eq, PartialEq, Debug)] 510 | struct I32(i32); 511 | #[derive(Eq, PartialEq, Debug)] 512 | struct Str<'a>(&'a str); 513 | 514 | // Library requires all specializable 515 | // types to be defined in one place. 516 | impl_specialization!( 517 | type U32; 518 | type I32; 519 | type Str<'a>; 520 | ); 521 | 522 | fn spec(value: T) -> Result 523 | where 524 | T: Specialize, 525 | { 526 | match value.specialize() { 527 | Distinction::Special(value) => Ok(value), 528 | Distinction::Generic(value) => Err(value), 529 | } 530 | } 531 | 532 | assert_eq!(spec(U32(42)), Ok(U32(42))); 533 | assert_eq!(spec(I32(42_i32)), Err(I32(42))); 534 | assert_eq!(spec(Str("abc")), Err(Str("abc"))); 535 | ``` 536 | 537 | 538 | 539 | [`min_specialization`] nightly feature: 540 | 541 | ```rust 542 | // Requires nightly. 543 | #![feature(min_specialization)] 544 | 545 | // The artificial example looks a bit long. 546 | // More real-world use cases are usually 547 | // on the contrary more clear and understandable. 548 | pub trait Spec: Sized { 549 | fn spec(self) -> Result; 550 | } 551 | 552 | impl Spec for T { 553 | default fn spec(self) -> Result { 554 | Err(self) 555 | } 556 | } 557 | 558 | impl Spec for u32 { 559 | fn spec(self) -> Result { 560 | Ok(self) 561 | } 562 | } 563 | 564 | assert_eq!(Spec::spec(42_u32), Ok(42)); 565 | assert_eq!(Spec::spec(42_i32), Err(42)); 566 | assert_eq!(Spec::spec("abc"), Err("abc")); 567 | ``` 568 | 569 |
570 | 571 | ## License 572 | 573 | Licensed under either of 574 | 575 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or ) 576 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or ) 577 | 578 | at your option. 579 | 580 | ## Contribution 581 | 582 | Unless you explicitly state otherwise, any contribution intentionally 583 | submitted for inclusion in the work by you, as defined in the Apache-2.0 584 | license, shall be dual licensed as above, without any 585 | additional terms or conditions. 586 | 587 | [API Documentation]: https://docs.rs/try-specialize 588 | [subtyping]: https://doc.rust-lang.org/nomicon/subtyping.html 589 | [coercion]: https://doc.rust-lang.org/nomicon/coercions.html 590 | [release]: https://doc.rust-lang.org/cargo/reference/profiles.html#release 591 | [`min_specialization`]: https://github.com/rust-lang/rust/pull/68970 592 | [`min_spec...`]: https://github.com/rust-lang/rust/pull/68970 "min_specialization" 593 | 594 | [`examples/encode.rs`]: https://github.com/zheland/try-specialize/blob/v0.1.2/examples/encode.rs 595 | 596 | [`std::any::TypeId`]: https://doc.rust-lang.org/std/any/struct.TypeId.html 597 | [`TypeId`]: https://doc.rust-lang.org/std/any/struct.TypeId.html "std::any::TypeId" 598 | [`std::any::Any`]: https://doc.rust-lang.org/std/any/trait.Any.html 599 | [`TypeId::of`]: https://doc.rust-lang.org/std/any/struct.TypeId.html#method.of "std::any::TypeId::of" 600 | [`transmute`]: https://doc.rust-lang.org/std/mem/fn.transmute.html "std::mem::transmute" 601 | [`transmute_copy`]: https://doc.rust-lang.org/std/mem/fn.transmute_copy.html "std::mem::transmute_copy" 602 | [`ptr::read`]: https://doc.rust-lang.org/std/ptr/fn.read.html "std::ptr::read" 603 | [`PartialEq`]: https://doc.rust-lang.org/std/cmp/trait.PartialEq.html "std::cmp::PartialEq" 604 | [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html "std::cmp::Eq" 605 | [`Arc::eq`]: https://doc.rust-lang.org/std/sync/struct.Arc.html#method.eq "::eq" 606 | [`Arc`]: https://doc.rust-lang.org/std/sync/struct.Arc.html "std::sync::Arc" 607 | 608 | [`unreliable`]: https://docs.rs/try-specialize/latest/try_specialize/unreliable/index.html 609 | [`LifetimeFree`]: https://docs.rs/try-specialize/latest/try_specialize/trait.LifetimeFree.html 610 | 611 | [`Specialization`]: https://docs.rs/try-specialize/latest/try_specialize/struct.Specialization.html 612 | [`try_new`]: https://docs.rs/try-specialize/latest/try_specialize/struct.Specialization.html#method.try_new "Specialization::try_new" 613 | [`try_new_static`]: https://docs.rs/try-specialize/latest/try_specialize/struct.Specialization.html#method.try_new_static "Specialization::try_new_static" 614 | [`try_new_ignore_lifetimes`]: https://docs.rs/try-specialize/latest/try_specialize/struct.Specialization.html#method.try_new_ignore_lifetimes "Specialization::try_new_ignore_lifetimes" 615 | [`rev`]: https://docs.rs/try-specialize/latest/try_specialize/struct.Specialization.html#method.rev "Specialization::rev" 616 | [`map`]: https://docs.rs/try-specialize/latest/try_specialize/struct.Specialization.html#method.map "Specialization::map" 617 | [`specialize`]: https://docs.rs/try-specialize/latest/try_specialize/struct.Specialization.html#method.specialize "Specialization::specialize" 618 | [`specialize_ref`]: https://docs.rs/try-specialize/latest/try_specialize/struct.Specialization.html#method.specialize_ref "Specialization::specialize_ref" 619 | [`specialize_mut`]: https://docs.rs/try-specialize/latest/try_specialize/struct.Specialization.html#method.specialize_mut "Specialization::specialize_mut" 620 | 621 | [`WeakSpecialization`]: https://docs.rs/try-specialize/latest/try_specialize/unreliable/trait.WeakSpecialization.html 622 | [`try_new_if_lifetime_free_weak`]: https://docs.rs/try-specialize/latest/try_specialize/unreliable/trait.WeakSpecialization.html#method.try_new_if_lifetime_free_weak "unreliable::WeakSpecialization::try_new_if_lifetime_free_weak" 623 | 624 | [`TrySpecialize`]: https://docs.rs/try-specialize/latest/try_specialize/trait.TrySpecialize.html 625 | [`TrySpecialize::try_specialize`]: https://docs.rs/try-specialize/latest/try_specialize/trait.TrySpecialize.html#method.try_specialize 626 | [`try_specialize`]: https://docs.rs/try-specialize/latest/try_specialize/trait.TrySpecialize.html#method.try_specialize "TrySpecialize::try_specialize" 627 | [`TrySpecialize::try_specialize_ref`]: https://docs.rs/try-specialize/latest/try_specialize/trait.TrySpecialize.html#method.try_specialize_ref 628 | [`try_specialize_ref`]: https://docs.rs/try-specialize/latest/try_specialize/trait.TrySpecialize.html#method.try_specialize_ref "TrySpecialize::try_specialize_ref" 629 | [`try_specialize_from`]: https://docs.rs/try-specialize/latest/try_specialize/trait.TrySpecialize.html#method.try_specialize_from "TrySpecialize::try_specialize_from" 630 | [`try_specialize_from_ref`]: https://docs.rs/try-specialize/latest/try_specialize/trait.TrySpecialize.html#method.try_specialize_from_ref "TrySpecialize::try_specialize_from_ref" 631 | [`TrySpecialize::try_specialize_static`]: https://docs.rs/try-specialize/latest/try_specialize/trait.TrySpecialize.html#method.try_specialize_static 632 | [`try_specialize_static`]: https://docs.rs/try-specialize/latest/try_specialize/trait.TrySpecialize.html#method.try_specialize_static "TrySpecialize::try_specialize_static" 633 | [`try_specialize_ref_static`]: https://docs.rs/try-specialize/latest/try_specialize/trait.TrySpecialize.html#method.try_specialize_ref_static "TrySpecialize::try_specialize_ref_static" 634 | [`..._ignore_lifetimes`]: https://docs.rs/try-specialize/latest/try_specialize/trait.TrySpecialize.html#method.try_specialize_ignore_lifetimes "TrySpecialize::try_specialize_ignore_lifetimes" 635 | [`..._ref_ignore_lifetimes`]: https://docs.rs/try-specialize/latest/try_specialize/trait.TrySpecialize.html#method.try_specialize_ref_ignore_lifetimes "TrySpecialize::try_specialize_ref_ignore_lifetimes" 636 | [`..._mut_ignore_lifetimes`]: https://docs.rs/try-specialize/latest/try_specialize/trait.TrySpecialize.html#method.try_specialize_mut_ignore_lifetimes "TrySpecialize::try_specialize_mut_ignore_lifetimes" 637 | 638 | [`TrySpecializeWeak`]: https://docs.rs/try-specialize/latest/try_specialize/unreliable/trait.TrySpecializeWeak.html 639 | [`..._if_lifetime_free_weak`]: https://docs.rs/try-specialize/latest/try_specialize/unreliable/trait.TrySpecializeWeak.html#method.try_specialize_if_lifetime_free_weak "unreliable::TrySpecializeWeak::try_specialize_if_lifetime_free_weak" 640 | [`..._ref_if_lifetime_free_weak`]: https://docs.rs/try-specialize/latest/try_specialize/unreliable/trait.TrySpecializeWeak.html#method.try_specialize_ref_if_lifetime_free_weak "unreliable::TrySpecializeWeak::try_specialize_ref_if_lifetime_free_weak" 641 | [`..._mut_if_lifetime_free_weak`]: https://docs.rs/try-specialize/latest/try_specialize/unreliable/trait.TrySpecializeWeak.html#method.try_specialize_mut_if_lifetime_free_weak "unreliable::TrySpecializeWeak::try_specialize_mut_if_lifetime_free_weak" 642 | 643 | [`castaway`]: https://crates.io/crates/castaway 644 | [`syllogism`]: https://crates.io/crates/syllogism 645 | [`syllogism_macro`]: https://crates.io/crates/syllogism_macro 646 | [`specialized-dispatch`]: https://crates.io/crates/specialized-dispatch 647 | [`spec...ch`]: https://crates.io/crates/specialized-dispatch "specialized-dispatch" 648 | [`spez`]: https://crates.io/crates/spez 649 | [`coe-rs`]: https://crates.io/crates/coe-rs 650 | [`downcast-rs`]: https://crates.io/crates/downcast-rs 651 | [`impls`]: https://crates.io/crates/impls 652 | 653 | [Autoref-Based Specialization]: https://lukaskalbertodt.github.io/2019/12/05/generalized-autoref-based-specialization.html 654 | -------------------------------------------------------------------------------- /src/try_specialize.rs: -------------------------------------------------------------------------------- 1 | use crate::{LifetimeFree, Specialization}; 2 | 3 | /// A trait for specializing one type to another at runtime. 4 | /// 5 | /// This trait uses [`Specialization`] helper struct to perform all 6 | /// conversions. You can use [`Specialization`] directly if you need to perform 7 | /// more complex specialization cases or to cache the specializable ability. 8 | /// 9 | /// Library tests ensure that the specializations are performed at compile time 10 | /// and are fully optimized with no runtime cost at `opt-level >= 1`. Note that 11 | /// the release profile uses `opt-level = 3` by default. 12 | /// 13 | /// Methods cheat sheet: 14 | /// | Bounds\Operation | specialize `T1` to `T2` | specialize `&T1` to `&T2` | specialize `&mut T1` to `&mut T2` | 15 | /// |:--:|:--:|:--:|:--:| 16 | /// | `T1: 'static`
`+ LifetimeFree` | [`try_specialize`] | [`try_specialize_ref`] | [`try_specialize_mut`] | 17 | /// | `T2: 'static`
`+ LifetimeFree` | [`try_specialize_from`] | [`try_specialize_from_ref`] | [`try_specialize_from_mut`] | 18 | /// | `T1: 'static,`
`T2: 'static,` | [`try_specialize_static`] | [`try_specialize_ref_static`] | [`try_specialize_mut_static`] | 19 | /// | unconstrained
underlying type
maybe impls
`LifetimeFree` | [`..._if_lifetime_free_weak`] | [`..._ref_if_lifetime_free_weak`] | [`..._mut_if_lifetime_free_weak`] | 20 | /// | unconstrained
`unsafe` without
lifetimes check | [`..._ignore_lifetimes`] | [`..._ref_ignore_lifetimes`] | [`..._mut_ignore_lifetimes`] | 21 | /// 22 | /// [`try_specialize`]: TrySpecialize::try_specialize 23 | /// [`try_specialize_ref`]: TrySpecialize::try_specialize_ref 24 | /// [`try_specialize_mut`]: TrySpecialize::try_specialize_mut 25 | /// [`try_specialize_from`]: TrySpecialize::try_specialize_from 26 | /// [`try_specialize_from_ref`]: TrySpecialize::try_specialize_from_ref 27 | /// [`try_specialize_from_mut`]: TrySpecialize::try_specialize_from_mut 28 | /// [`try_specialize_static`]: TrySpecialize::try_specialize_static 29 | /// [`try_specialize_ref_static`]: TrySpecialize::try_specialize_ref_static 30 | /// [`try_specialize_mut_static`]: TrySpecialize::try_specialize_mut_static 31 | /// [`..._ignore_lifetimes`]: TrySpecialize::try_specialize_ignore_lifetimes 32 | /// [`..._ref_ignore_lifetimes`]: TrySpecialize::try_specialize_ref_ignore_lifetimes 33 | /// [`..._mut_ignore_lifetimes`]: TrySpecialize::try_specialize_mut_ignore_lifetimes 34 | /// [`..._if_lifetime_free_weak`]: crate::unreliable::TrySpecializeWeak::try_specialize_if_lifetime_free_weak 35 | /// [`..._ref_if_lifetime_free_weak`]: crate::unreliable::TrySpecializeWeak::try_specialize_ref_if_lifetime_free_weak 36 | /// [`..._mut_if_lifetime_free_weak`]: crate::unreliable::TrySpecializeWeak::try_specialize_mut_if_lifetime_free_weak 37 | pub trait TrySpecialize { 38 | /// Attempts to specialize `Self` as `T` for types without lifetimes. 39 | /// 40 | /// Returns `Self` as `T` wrapped in `Ok` if `Self` and `T` types are 41 | /// identical. Otherwise, it returns `Self` wrapped in `Err`. 42 | /// 43 | /// Note that this method requires target type to implement 44 | /// [`LifetimeFree`]. Use [`TrySpecialize::try_specialize_from`] if your 45 | /// target type doesn't have [`LifetimeFree`] bound but source type does. 46 | /// Use [`TrySpecialize::try_specialize_static`] if both source and target 47 | /// type have `'static` bounds. 48 | /// 49 | /// The [`LifetimeFree`] trait is **not** automatically derived for all 50 | /// lifetime-free types. The library only implements it for standard library 51 | /// types that do not have any lifetime parameters. 52 | /// 53 | /// [`LifetimeFree`]: crate::LifetimeFree 54 | /// 55 | /// # Examples 56 | /// 57 | /// Simple partial reimplementation of stdlib `ToString`: 58 | /// ```rust 59 | /// # #[cfg(feature = "std")] { 60 | /// use core::fmt::Display; 61 | /// 62 | /// use try_specialize::TrySpecialize; 63 | /// 64 | /// fn to_string(value: T) -> String 65 | /// where 66 | /// T: Display, 67 | /// { 68 | /// match value.try_specialize() { 69 | /// Ok(string) => string, 70 | /// Err(value) => format!("{value}"), 71 | /// } 72 | /// } 73 | /// 74 | /// assert_eq!(to_string("abc".to_owned()), "abc".to_owned()); // Specialized. 75 | /// assert_eq!(to_string(123), String::from("123")); // Default. 76 | /// # } 77 | /// ``` 78 | /// 79 | /// Note that many standard library types and traits, including `ToString`, 80 | /// already use specialization for optimization purposes using 81 | /// `min_specialization` nightly feature. 82 | #[expect(clippy::missing_errors_doc, reason = "already described")] 83 | #[inline] 84 | fn try_specialize(self) -> Result 85 | where 86 | Self: Sized, 87 | T: LifetimeFree, 88 | { 89 | if let Some(spec) = Specialization::try_new() { 90 | Ok(spec.specialize(self)) 91 | } else { 92 | Err(self) 93 | } 94 | } 95 | 96 | /// Attempts to specialize `T` as `Self` for types without lifetimes. 97 | /// 98 | /// Returns `T` as `Self` wrapped in `Ok` if `Self` and `T` types are 99 | /// identical. Otherwise, it returns `T` wrapped in `Err`. 100 | /// 101 | /// Note that this method requires source type to implement 102 | /// [`LifetimeFree`]. Use [`TrySpecialize::try_specialize_from`] if your 103 | /// source type doesn't have [`LifetimeFree`] bound but target type does. 104 | /// Use [`TrySpecialize::try_specialize_static`] if both target and source 105 | /// type have `'static` bounds. 106 | /// The [`LifetimeFree`] trait is **not** automatically derived for all 107 | /// lifetime-free types. The library only implements it for standard library 108 | /// types that do not have any lifetime parameters. 109 | /// 110 | /// [`LifetimeFree`]: crate::LifetimeFree 111 | /// 112 | /// # Examples 113 | /// 114 | /// Generate a placeholder with non-default value for some types: 115 | /// ```rust 116 | /// # #[cfg(feature = "alloc")] { 117 | /// use try_specialize::{Specialization, TrySpecialize}; 118 | /// 119 | /// fn placeholder() -> T 120 | /// where 121 | /// T: Default, 122 | /// { 123 | /// None.or_else(|| T::try_specialize_from(12_u8).ok()) 124 | /// .or_else(|| T::try_specialize_from(234_u16).ok()) 125 | /// .or_else(|| T::try_specialize_from(3456_u32).ok()) 126 | /// .or_else(|| T::try_specialize_from(45678_u64).ok()) 127 | /// .or_else(|| T::try_specialize_from(567_890_u128).ok()) 128 | /// .or_else(|| T::try_specialize_from(123_456_789_usize).ok()) 129 | /// .or_else(|| T::try_specialize_from(123.456_f32).ok()) 130 | /// .or_else(|| T::try_specialize_from(123.456_f64).ok()) 131 | /// .or_else(|| { 132 | /// // SAFETY: For any `'a` It is safe to specialize `&'static str` 133 | /// // as `&'a str`. 134 | /// unsafe { "dummy string".try_specialize_ignore_lifetimes() }.ok() 135 | /// }) 136 | /// .or_else(|| { 137 | /// let spec/*: Specialization*/ = Specialization::try_new()?; 138 | /// Some(spec.rev().specialize(String::from("foobar"))) 139 | /// }) 140 | /// .or_else(|| { 141 | /// let spec/*: Specialization>*/ = Specialization::try_new()?; 142 | /// Some(spec.rev().specialize(String::from("bazz").into_boxed_str())) 143 | /// }) 144 | /// .unwrap_or_default() 145 | /// } 146 | /// 147 | /// assert_eq!(placeholder::<(u8, u8)>(), (0, 0)); 148 | /// assert_eq!(placeholder::(), 3456); 149 | /// assert_eq!(placeholder::(), 123.456_f64); 150 | /// assert_eq!(placeholder::(), 567_890); 151 | /// assert_eq!(placeholder::(), 0); 152 | /// assert_eq!(placeholder::<&'static str>(), "dummy string"); 153 | /// assert_eq!(placeholder::(), String::from("foobar")); 154 | /// assert_eq!(placeholder::>(), Box::from("bazz")); 155 | /// # } 156 | /// ``` 157 | #[expect(clippy::missing_errors_doc, reason = "already described")] 158 | #[inline] 159 | fn try_specialize_from(other: T) -> Result 160 | where 161 | Self: Sized, 162 | T: LifetimeFree, 163 | { 164 | if let Some(spec) = Specialization::try_new() { 165 | Ok(spec.rev().specialize(other)) 166 | } else { 167 | Err(other) 168 | } 169 | } 170 | 171 | /// Attempts to specialize `Self` as `T` for static types. 172 | /// 173 | /// Returns `Self` as `T` wrapped in `Ok` if `Self` and `T` types are 174 | /// identical. Otherwise, it returns `Self` wrapped in `Err`. 175 | /// 176 | /// Note that this method requires both types to have `'static` lifetime, 177 | /// but don't require any type to implement [`LifetimeFree`]. If one of your 178 | /// types does not have a 'static bounds but the other type implements 179 | /// [`LifetimeFree`] use [`TrySpecialize::try_specialize`] or 180 | /// [`TrySpecialize::try_specialize_from`] instead. 181 | /// 182 | /// # Note 183 | /// 184 | /// This function requires both the source and destination types to 185 | /// implement `'static`. Although most `'static` types in Rust can be 186 | /// subtypes to a non-`'static` alternatives this is not always the case. 187 | /// For example `fn(&'static str)` and `fn(&'a str)` have the same `TypeId` 188 | /// however you can't subtype the first to the second, because, unlike 189 | /// anything else in the language, functions are contravariant over their 190 | /// arguments. See 191 | /// and for 192 | /// more details. 193 | /// 194 | /// # Examples 195 | /// 196 | /// Function with specialized implementation for 197 | /// [`std::collections::HashMap`] and `hashbrown::HashMap`: 198 | /// ```rust 199 | /// use try_specialize::TrySpecialize; 200 | /// 201 | /// fn process_static(value: T) 202 | /// where 203 | /// T: 'static, 204 | /// { 205 | /// match value.try_specialize_static::>() { 206 | /// Ok(hash_map @ std::collections::HashMap { .. }) => { 207 | /// drop(hash_map); 208 | /// // specialized impl for `std::collections::HashMap` 209 | /// } 210 | /// Err(value) => { 211 | /// match value.try_specialize_static::>() { 212 | /// Ok(hash_map @ hashbrown::HashMap { .. }) => { 213 | /// drop(hash_map); 214 | /// // specialized impl for `hashbrown::HashMap` 215 | /// } 216 | /// Err(default) => { 217 | /// drop(default); 218 | /// // default impl ... 219 | /// } 220 | /// } 221 | /// } 222 | /// } 223 | /// } 224 | /// # let input = [(123_u32, 'a'), (234_u32, 'b')]; 225 | /// # process_static(input.into_iter().collect::>()); 226 | /// # process_static(input.into_iter().collect::>()); 227 | /// # process_static(input.into_iter().collect::>()); 228 | /// ``` 229 | #[expect(clippy::missing_errors_doc, reason = "already described")] 230 | #[inline] 231 | fn try_specialize_static(self) -> Result 232 | where 233 | Self: 'static + Sized, 234 | T: 'static, 235 | { 236 | if let Some(spec) = Specialization::try_new_static() { 237 | Ok(spec.specialize(self)) 238 | } else { 239 | Err(self) 240 | } 241 | } 242 | 243 | /// Attempts to specialize `&Self` as `&T` for types without lifetimes. 244 | /// 245 | /// Note that this method requires target type to implement 246 | /// [`LifetimeFree`]. Use [`TrySpecialize::try_specialize_from_ref`] if your 247 | /// target type doesn't implement [`LifetimeFree`] but source type does. 248 | /// 249 | /// The [`LifetimeFree`] trait is **not** automatically derived for all 250 | /// lifetime-free types. The library only implements it for standard library 251 | /// types that do not have any lifetime parameters. 252 | /// 253 | /// [`LifetimeFree`]: crate::LifetimeFree 254 | /// 255 | /// # Examples 256 | /// 257 | /// Stringifiable type concatenation, that don't allocate memory if one of 258 | /// the arguments is empty `&str`: 259 | /// ```rust 260 | /// use core::fmt::Display; 261 | /// use std::borrow::Cow; 262 | /// use std::format; 263 | /// 264 | /// use try_specialize::TrySpecialize; 265 | /// 266 | /// fn concat<'a, T1, T2>(first: &'a T1, second: &'a T2) -> Cow<'a, str> 267 | /// where 268 | /// T1: ?Sized + Display, 269 | /// T2: ?Sized + Display, 270 | /// { 271 | /// match ( 272 | /// first.try_specialize_ref(), 273 | /// second.try_specialize_ref(), 274 | /// ) { 275 | /// (Some(first), Some("")) => Cow::Borrowed(first), 276 | /// (Some(""), Some(second)) => Cow::Borrowed(second), 277 | /// (_, _) => Cow::Owned(format!("{first}{second}")), 278 | /// } 279 | /// } 280 | /// 281 | /// assert!(matches!(concat("foo", "bar"), Cow::Owned(v) if v == "foobar")); 282 | /// assert!(matches!(concat("foo", ""), Cow::Borrowed("foo"))); 283 | /// assert!(matches!(concat("", "bar"), Cow::Borrowed("bar"))); 284 | /// let foo = String::from("foo"); 285 | /// let bar = String::from("bar"); 286 | /// assert!(matches!(concat(&foo, &bar), Cow::Owned(v) if v == "foobar")); 287 | /// assert!(matches!(concat("foo", &456), Cow::Owned(v) if v == "foo456")); 288 | /// assert!(matches!(concat(&123, &456), Cow::Owned(v) if v == "123456")); 289 | /// ``` 290 | #[inline] 291 | fn try_specialize_ref(&self) -> Option<&T> 292 | where 293 | T: ?Sized + LifetimeFree, 294 | { 295 | Specialization::try_new().map(|spec| spec.specialize_ref(self)) 296 | } 297 | 298 | /// Attempts to specialize `&T` as `&Self` for types without lifetimes. 299 | /// 300 | /// Note that this method requires source type to implement 301 | /// [`LifetimeFree`]. Use [`TrySpecialize::try_specialize_ref`] if your 302 | /// source type doesn't implement [`LifetimeFree`] but target type does. 303 | /// The [`LifetimeFree`] trait is **not** automatically derived for all 304 | /// lifetime-free types. The library only implements it for standard library 305 | /// types that do not have any lifetime parameters. 306 | /// 307 | /// [`LifetimeFree`]: crate::LifetimeFree 308 | #[inline] 309 | fn try_specialize_from_ref(other: &T) -> Option<&Self> 310 | where 311 | T: ?Sized + LifetimeFree, 312 | { 313 | Specialization::try_new().map(|spec| spec.rev().specialize_ref(other)) 314 | } 315 | 316 | /// Attempts to specialize `&Self` as `&T` for static types. 317 | /// 318 | /// Note that this method requires both types to have `'static` lifetime, 319 | /// but don't require any type to implement [`LifetimeFree`]. 320 | #[inline] 321 | fn try_specialize_ref_static(&self) -> Option<&T> 322 | where 323 | Self: 'static, 324 | T: ?Sized + 'static, 325 | { 326 | Specialization::try_new_static().map(|spec| spec.specialize_ref(self)) 327 | } 328 | 329 | /// Attempts to specialize `&mut Self` as `&mut T` for types without 330 | /// lifetimes. 331 | /// 332 | /// Note that this method requires target type to implement 333 | /// [`LifetimeFree`]. Use [`TrySpecialize::try_specialize_from_mut`] if your 334 | /// target type doesn't implement [`LifetimeFree`] but source type does. 335 | /// 336 | /// The [`LifetimeFree`] trait is **not** automatically derived for all 337 | /// lifetime-free types. The library only implements it for standard library 338 | /// types that do not have any lifetime parameters. 339 | /// 340 | /// [`LifetimeFree`]: crate::LifetimeFree 341 | #[inline] 342 | fn try_specialize_mut(&mut self) -> Option<&mut T> 343 | where 344 | T: ?Sized + LifetimeFree, 345 | { 346 | Specialization::try_new().map(|spec| spec.specialize_mut(self)) 347 | } 348 | 349 | /// Attempts to specialize `&mut T` as `&mut Self` for types without 350 | /// lifetimes. 351 | /// 352 | /// Note that this method requires source type to implement 353 | /// [`LifetimeFree`]. Use [`TrySpecialize::try_specialize_mut`] if your 354 | /// source type doesn't implement [`LifetimeFree`] but target type does. 355 | /// The [`LifetimeFree`] trait is **not** automatically derived for all 356 | /// lifetime-free types. The library only implements it for standard library 357 | /// types that do not have any lifetime parameters. 358 | /// 359 | /// [`LifetimeFree`]: crate::LifetimeFree 360 | #[inline] 361 | fn try_specialize_from_mut(other: &mut T) -> Option<&mut Self> 362 | where 363 | T: ?Sized + LifetimeFree, 364 | { 365 | Specialization::try_new().map(|spec| spec.rev().specialize_mut(other)) 366 | } 367 | 368 | /// Attempts to specialize `&mut Self` as `&mut T` for static types. 369 | /// 370 | /// Note that this method requires both types to have `'static` lifetime, 371 | /// but don't require any type to implement [`LifetimeFree`]. 372 | #[inline] 373 | fn try_specialize_mut_static(&mut self) -> Option<&mut T> 374 | where 375 | Self: 'static, 376 | T: ?Sized + 'static, 377 | { 378 | Specialization::try_new_static().map(|spec| spec.specialize_mut(self)) 379 | } 380 | 381 | /// Attempts to specialize `Self` as `T` ignoring lifetimes. 382 | /// 383 | /// Returns `T` as `Self` wrapped in `Ok` if `Self` and `T` types are 384 | /// identical. Otherwise, it returns `T` wrapped in `Err`. 385 | /// 386 | /// # Safety 387 | /// 388 | /// This method doesn't validate type lifetimes. Lifetimes equality should 389 | /// be validated separately. 390 | /// 391 | /// Calling this method for types with any differences in lifetimes between 392 | /// `Self` and `T` types is *[undefined behavior]*. 393 | /// 394 | /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html 395 | /// 396 | /// # Examples 397 | /// 398 | /// Generate a placeholder with non-default value for some types with 399 | /// several `&'static T` types: 400 | /// ```rust 401 | /// use try_specialize::{Specialization, TrySpecialize}; 402 | /// 403 | /// fn placeholder() -> T 404 | /// where 405 | /// T: Default, 406 | /// { 407 | /// None.or_else(|| T::try_specialize_from(12_u8).ok()) 408 | /// .or_else(|| T::try_specialize_from(234_u16).ok()) 409 | /// .or_else(|| T::try_specialize_from(3456_u32).ok()) 410 | /// .or_else(|| { 411 | /// // SAFETY: For any `'a` It is safe to specialize `&'static str` 412 | /// // as `&'a str`. 413 | /// unsafe { "dummy string".try_specialize_ignore_lifetimes() }.ok() 414 | /// }) 415 | /// .or_else(|| { 416 | /// // Ensure that the slice is static. 417 | /// const DEFAULT: &'static [u8] = &[1, 2, 3, 4, 5]; 418 | /// // SAFETY: For any `'a` It is safe to specialize `&'static [u8]` 419 | /// // as `&'a [u8]`. 420 | /// unsafe { DEFAULT.try_specialize_ignore_lifetimes() }.ok() 421 | /// }) 422 | /// .unwrap_or_default() 423 | /// } 424 | /// 425 | /// assert_eq!(placeholder::<(u8, u8)>(), (0, 0)); 426 | /// assert_eq!(placeholder::(), 3456); 427 | /// assert_eq!(placeholder::(), 0.0); 428 | /// assert_eq!(placeholder::<&'static str>(), "dummy string"); 429 | /// assert_eq!(placeholder::<&'static [u8]>(), &[1, 2, 3, 4, 5]); 430 | /// ``` 431 | #[expect(clippy::missing_errors_doc, reason = "already described")] 432 | #[inline] 433 | unsafe fn try_specialize_ignore_lifetimes(self) -> Result 434 | where 435 | Self: Sized, 436 | { 437 | if let Some(spec) = Specialization::try_new_ignore_lifetimes() { 438 | Ok(spec.specialize(self)) 439 | } else { 440 | Err(self) 441 | } 442 | } 443 | 444 | /// Attempts to specialize `&Self` as `&T` ignoring lifetimes. 445 | /// 446 | /// # Safety 447 | /// 448 | /// This method doesn't validate type lifetimes. Lifetimes equality should 449 | /// be validated separately. 450 | /// 451 | /// Calling this method for types with any differences in lifetimes between 452 | /// `Self` and `T` types is *[undefined behavior]*. 453 | /// 454 | /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html 455 | #[inline] 456 | unsafe fn try_specialize_ref_ignore_lifetimes(&self) -> Option<&T> 457 | where 458 | T: ?Sized, 459 | { 460 | Specialization::try_new_ignore_lifetimes().map(|spec| spec.specialize_ref(self)) 461 | } 462 | 463 | /// Attempts to specialize `&mut Self` as `&mut T` ignoring lifetimes. 464 | /// 465 | /// # Safety 466 | /// 467 | /// This method doesn't validate type lifetimes. Lifetimes equality should 468 | /// be validated separately. 469 | /// 470 | /// Calling this method for types with any differences in lifetimes between 471 | /// `Self` and `T` types is *[undefined behavior]*. 472 | /// 473 | /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html 474 | #[inline] 475 | unsafe fn try_specialize_mut_ignore_lifetimes(&mut self) -> Option<&mut T> 476 | where 477 | T: ?Sized, 478 | { 479 | Specialization::try_new_ignore_lifetimes().map(|spec| spec.specialize_mut(self)) 480 | } 481 | } 482 | 483 | impl TrySpecialize for T where T: ?Sized {} 484 | 485 | #[cfg(test)] 486 | mod tests { 487 | #[cfg(feature = "alloc")] 488 | use alloc::borrow::{Cow, ToOwned}; 489 | #[cfg(feature = "alloc")] 490 | use alloc::boxed::Box; 491 | #[cfg(feature = "alloc")] 492 | use alloc::format; 493 | #[cfg(feature = "alloc")] 494 | use alloc::string::String; 495 | #[cfg(feature = "alloc")] 496 | use core::fmt::Display; 497 | 498 | #[cfg(feature = "alloc")] 499 | use crate::LifetimeFree; 500 | use crate::{type_eq_ignore_lifetimes, TrySpecialize}; 501 | 502 | #[cfg(feature = "alloc")] 503 | fn specialized_to_string(value: T) -> String 504 | where 505 | T: LifetimeFree + Display, 506 | { 507 | match value.try_specialize::() { 508 | Ok(value) => format!("{value}: i32"), 509 | Err(value) => match value.try_specialize::() { 510 | Ok(value) => format!("{value}: u32"), 511 | Err(value) => format!("{value}: ???"), 512 | }, 513 | } 514 | } 515 | 516 | #[test] 517 | fn test_try_specialize() { 518 | assert_eq!((123_i32).try_specialize::(), Ok(123_i32)); 519 | assert_eq!((123_u32).try_specialize::(), Ok(123_u32)); 520 | assert_eq!((123_i32).try_specialize::(), Err(123_i32)); 521 | assert_eq!("123".try_specialize::(), Err("123")); 522 | 523 | assert_eq!(::try_specialize_from(123_u32), Ok(123_u32)); 524 | assert_eq!(<&'static str>::try_specialize_from(123), Err(123)); 525 | } 526 | 527 | #[test] 528 | fn test_try_specialize_ref() { 529 | let value = &[1_u32, 2, 3][..]; 530 | assert_eq!(value.try_specialize_ref::<[u32]>(), Some(value)); 531 | assert_eq!(value.try_specialize_ref::<[i32]>(), None); 532 | assert_eq!(value.try_specialize_ref::(), None); 533 | assert_eq!(value.try_specialize_ref::(), None); 534 | 535 | assert_eq!(<[u32]>::try_specialize_from_ref(value), Some(value)); 536 | assert_eq!(<&'static str>::try_specialize_from_ref(value), None); 537 | } 538 | 539 | #[test] 540 | fn test_try_specialize_static() { 541 | let value: &'static [&'static u32] = &[&1, &2, &3]; 542 | assert_eq!(value.try_specialize_static::<&[&u32]>(), Ok(value)); 543 | assert_eq!(value.try_specialize_static::<&[&i32]>(), Err(value)); 544 | 545 | let value: [&'static u32; 3] = [&1, &2, &3]; 546 | let value: &[&'static u32] = &value; // Reference itself it not 'static. 547 | assert_eq!(value.try_specialize_ref_static::<[&u32]>(), Some(value)); 548 | assert_eq!(value.try_specialize_ref_static::<[&i32]>(), None); 549 | 550 | let mut value: [&'static u32; 3] = [&1, &2, &3]; 551 | let value: &mut [&'static u32] = &mut value; // Reference itself it not 'static. 552 | assert_eq!( 553 | value.try_specialize_mut_static::<[&u32]>(), 554 | Some(&mut [&1, &2, &3][..]) 555 | ); 556 | assert_eq!(value.try_specialize_mut_static::<[&i32]>(), None); 557 | } 558 | 559 | #[cfg(feature = "alloc")] 560 | #[test] 561 | fn test_try_specialize_ignore_lifetimes() { 562 | // SAFETY: In real code the developer should ensure that `T1` and `T2` 563 | // types have same lifetimes. 564 | unsafe fn try_spec_erased(value: T1) -> Result { 565 | value.try_specialize_ignore_lifetimes() 566 | } 567 | 568 | fn is_foobar_cow<'a, T>(value: Cow<'a, T>) -> bool 569 | where 570 | T: ?Sized + ToOwned, 571 | { 572 | // SAFETY: Specialization from `Cow<'a, T>` to `Cow<'a, str>` 573 | // will always have equal lifetimes because `str` is [`LifetimeFree`]. 574 | unsafe { 575 | try_spec_erased::<_, Cow<'a, str>>(value).is_ok_and(|value| value == "foobar") 576 | } 577 | } 578 | 579 | let value = String::from("foo") + "bar"; 580 | let value = Cow::Borrowed(value.as_str()); 581 | assert!(is_foobar_cow(value)); 582 | assert!(!is_foobar_cow(Cow::Borrowed("foo"))); 583 | assert!(!is_foobar_cow(Cow::Borrowed(&123))); 584 | } 585 | 586 | #[cfg(feature = "alloc")] 587 | #[test] 588 | fn test_try_specialize_ref_ignore_lifetimes() { 589 | #[expect( 590 | clippy::redundant_allocation, 591 | reason = "`Box` type is passed on purpose." 592 | )] 593 | fn with_non_static_box<'a>(mut value: Box<&'a u32>) { 594 | let mut expected = value.clone(); 595 | assert_eq!( 596 | // SAFETY: Okay in test. 597 | unsafe { 598 | value 599 | .clone() 600 | .try_specialize_ignore_lifetimes::>() 601 | }, 602 | Ok(expected.clone()) 603 | ); 604 | 605 | assert_eq!( 606 | // SAFETY: Okay in test. 607 | unsafe { value.try_specialize_ref_ignore_lifetimes::>() }, 608 | Some(&expected) 609 | ); 610 | 611 | assert_eq!( 612 | // SAFETY: Okay in test. 613 | unsafe { value.try_specialize_mut_ignore_lifetimes::>() }, 614 | Some(&mut expected) 615 | ); 616 | } 617 | 618 | let mut value = 12; 619 | value += 23; 620 | let value: Box<&u32> = Box::new(&value); 621 | with_non_static_box(value); 622 | } 623 | 624 | #[test] 625 | fn test_try_specialize_mut() { 626 | let value1 = &mut [1_u32, 2, 3][..]; 627 | let value2 = &mut [1_u32, 2, 3][..]; 628 | assert_eq!(value1.try_specialize_mut::<[u32]>(), Some(value2)); 629 | assert_eq!(value1.try_specialize_mut::<[i32]>(), None); 630 | assert_eq!(value1.try_specialize_mut::(), None); 631 | assert_eq!(value1.try_specialize_mut::(), None); 632 | 633 | let value2 = &mut [1_u32, 2, 3][..]; 634 | assert_eq!(<[u32]>::try_specialize_from_mut(value1), Some(value2)); 635 | assert_eq!(<&'static str>::try_specialize_from_mut(value1), None); 636 | } 637 | 638 | #[cfg(feature = "alloc")] 639 | #[test] 640 | fn test_alloc_try_specialize() { 641 | assert_eq!(specialized_to_string(123_i32), "123: i32"); 642 | assert_eq!(specialized_to_string(234_u32), "234: u32"); 643 | assert_eq!(specialized_to_string(345_i16), "345: ???"); 644 | assert_eq!(specialized_to_string(456_u16), "456: ???"); 645 | } 646 | 647 | #[test] 648 | fn test_should_not_impl_try_specialize_static_with_non_static_target() { 649 | #[expect(clippy::trivially_copy_pass_by_ref, reason = "intentionally")] 650 | fn scoped<'a>(_: &'a u32) { 651 | type LocalFn<'a> = fn(&'a str) -> u32; 652 | type StaticFn = LocalFn<'static>; 653 | 654 | // Types `TypeId` are equal. 655 | assert!(type_eq_ignore_lifetimes::>()); 656 | 657 | // But you can convert non-`'static` fn to `'static` one. 658 | let func: Option> = None; 659 | #[expect(clippy::unwrap_used, reason = "okay in tests")] 660 | let _func: Option = func.try_specialize_static().unwrap(); 661 | 662 | // The reverse will fail to compile. 663 | // let func: Option = None; 664 | // let func: Option> = 665 | // func.try_specialize_static().unwrap(); 666 | } 667 | 668 | let value = 123; 669 | scoped(&value); 670 | } 671 | } 672 | --------------------------------------------------------------------------------