Skip to content

JohnScience/unconst_trait_impl

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

45 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Function-like macro that "unconsts" trait implementations

unconst_trait_impl::unconst_trait_impl turns the Nightly syntax for constant trait implementations into analogous non-const syntax that is accepted on stable toolchain.

The list of features taken into account:

In a vaccum, unconst_trait_impl procedural function-like macro is fairly useless because its call on constant trait implementation yields the same result as writing the non-const implementation in the first place.

However, with cfg_attr and remove_macro_call attributes, unconst_trait_impl macro allows one to conditionally remove the macro call thus providing support for stable toolchain while also providing functionality relying on Nightly features.

Example

#![cfg_attr(feature = "const_trait_impl", feature(const_trait_impl))]
#![cfg_attr(feature = "const_default_impls", feature(const_default_impls))]
#![cfg_attr(feature = "const_fn_trait_bound", feature(const_fn_trait_bound))]

#[cfg(not(all(
    feature = "const_trait_impl",
    feature = "const_default_impls",
    feature = "const_fn_trait_bound"
)))]
use unconst_trait_impl::unconst_trait_impl;
use core::{default::Default, marker::PhantomData};
#[cfg(all(
    feature = "const_trait_impl",
    feature = "const_default_impls",
    feature = "const_fn_trait_bound"
))]
use remove_macro_call::remove_macro_call;

// Since ZST is both Eq and and PartialEq, it has structural match
// https://github.com/rust-lang/rust/issues/63438
#[derive(Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd, Copy)]
pub struct ZST<T: ?Sized>(PhantomData<T>);

pub trait TraitName {}

#[cfg_attr(
    all(
        feature = "const_trait_impl",
        feature = "const_default_impls",
        feature = "const_fn_trait_bound"
    ),
    remove_macro_call
)]
unconst_trait_impl! {
    impl<T: ?Sized> const TraitName for ZST<T> {}
}

// With `cargo build --features const_trait_impl, const_default_impls, const_fn_trait_bound`
// or with `cargo build --all-features, the code below is expanded as is. Otherwise,
// it gets "unconsted" to be supported by stable toolchain.
#[cfg_attr(
    all(
        feature = "const_trait_impl",
        feature = "const_default_impls",
        feature = "const_fn_trait_bound"
    ),
    remove_macro_call
)]
unconst_trait_impl! {
    impl<T: ~const TraitName + ?Sized> const Default for ZST<T> {
        fn default() -> Self {
            ZST(Default::default())
        }
    }
}

Note: In the real code, the example above could be replaced with a simpler version relying on cfg_aliases crate.

You can learn more about remove_macro_call here:

Why is it so ugly?

From the standpoint of stable Rust, nightly Rust syntax is not Rust. Therefore, using an attribute would not suffice.

According to the reference, attributes may be applied to many things in the language:

  • All item declarations accept outer attributes while external blocks, functions, implementations, and modules accept inner attributes.
  • Most statements accept outer attributes (see Expression Attributes for limitations on expression statements).
  • Block expressions accept outer and inner attributes, but only when they are the outer expression of an expression statement or the final expression of another block expression.
  • Enum variants and struct and union fields accept outer attributes.
  • Match expression arms accept outer attributes.
  • Generic lifetime or type parameter accept outer attributes.
  • Expressions accept outer attributes in limited situations, see Expression Attributes for details.
  • Function, closure and function pointer parameters accept outer attributes. This includes attributes on variadic parameters denoted with ... in function pointers and external blocks.

Notice that "arbitrary Rust-like syntax" is not one of them. So the nightly syntax has to be wrapped in a function-like macro, which does permit arbitrary Rust-like syntax. It is also necessary to evaluate the configuration predicates before expansion of the function-like macro, which is problematic if at all possible in the context of a function-like macro.

Known limitations

Currently, type parameters (like T in T: ~const TraitName + ?Sized) get "unconsted" only when

  1. they belong to the trait implementation, i.e. when they are in <..> or in the where clause of the trait implementation;
  2. they belong to the signatures of methods and associated functions;

Other Items in the trait implementation currently don't get "unconsted".

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

About

Function-like macro that "unconsts" trait impls. Best with conditional removal.

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

Watchers

Forks

Languages