const_struct
is a macro that allows you to pass structs, f32, and other types in a manner similar to const generics. This is a different feature from generic_const_expr
, and it is unclear whether there will be a standard replacement.
rust-lang/rust#76560
https://hackmd.io/OZG_XiLFRs2Xmw5s39jRzA?view
Official Const-related features are proposed here:
https://github.com/rust-lang/const-eval
It can be used in a no_std
environment.
If additional features are needed, or if you find any bugs, please open an issue.
It is currently under development, and the main
branch may break.
It can be made public, but there might be no compatibility between major versions.
If compatibility is broken, the major version will be increased. However, at this point, there are no plans to increase the major version.
※This is Translated with ChatGPT The original README.md is written in Japanese.
So this document may be out of date.
When receiving a struct, add Ty
to the end of its name.
There are exceptions, but the macro and trait are designed so that nothing else needs to be imported.
The data is stored in __DATA
.
For structs, you can directly access members by converting their names to camelCase.
Additionally, the data extracted via ::__DATA
is type-inferred by the compiler.
For more detailed code, please refer to /crates/test_code/
.
*The test code is complex to verify if it works even with unusual references.
If you use macros generated and exported by this package, you need to call this function in the crate root. Therefore, if the library to be used exists, this needs to be communicated.
const_struct::init!();
Here, it may be referred to as the inside macro.
When using primitive types, use the primitive
module.
When passing them, use the macro defined with a camelCase name.
For primitive types, you can also access them using VALUE
.
use const_struct::{primitive::F32Ty, F32};
pub fn tester<A: F32Ty>() {
println!("a: {:?}", A::__DATA);
}
fn main() {
tester::<F32!(0.5)>();
}
There are no restrictions on the values that can be defined.
For structs, you can receive them by using derive(ConstStruct).
To pass a value as a const, define it using the const_struct macro.
The const_struct macro defines the name by converting it to camelCase and appending Ty.
It is recommended to manage these collectively using files like setting.rs.
use const_struct::{const_struct, ConstStruct};
#[derive(ConstStruct, Debug)]
pub struct TestSetting {
pub a: Option<u32>,
abc_def: &'static str,
}
pub fn tester<A: TestSettingTy>() {
println!("a: {:?}", A::__DATA);
}
#[const_struct]
const WINDOW_SETTING: TestSetting = {
let mut c = TestSetting {
a: Some(0),
abc_def: "abc_def",
};
c.a = Some(5);
c.abc_def = "hello world";
c
};
fn main() {
tester::<WindowSettingTy>();
}
It's possible.
use const_struct::{const_struct, primitive::OptionTy};
pub fn tester<A: OptionTy<f64>>() {
println!("a: {:?}", A::__DATA);
}
#[const_struct]
const PI: Option<f64> = Some(3.14159265358979);
fn main() {
tester::<PiTy>();
}
Note: Sometimes referred to as the "inside macro." When the derive macro is applied, a macro with the same name is generated. When using this macro, it is necessary to import the corresponding struct at the same time. The member variables of the struct to which this macro is applied must implement the Copy trait. (As indicated by the third error to watch out for.) By using #[const_struct(macro_export)], the macro can be made publicly available.
#[allow(unused)]
#[const_struct::const_struct(macro_export)]
#[derive(const_struct::ConstStruct, Debug)]
pub struct TestSetting {
a: Option<u32>,
abc_def: &'static str,
}
pub fn tester<A: TestSettingTy>() {
println!("a: {:?}", A::__DATA);
}
pub const fn default() -> TestSetting {
TestSetting {
a: None,
abc_def: "hello world",
}
}
fn main() {
tester::<macros::TestSetting!(default())>();
}
In addition to the conditions mentioned, the Copy trait is automatically added to trait bounds.
When generics are present, you must specify types in the order they are defined when invoking the struct macro.
For const generics, if the value can be inferred from the provided arguments, you may use _ as a placeholder.
If all const generics can be omitted, you don't need to write them.
By using the call_with_generics! macro, you can omit const generics when they can be inferred.
Since types are expanded in the order they are defined, you must specify the types in the same order when using the call_with_generics! macro.
Non-const generic types cannot be omitted.
When you use #[const_struct] with a type like ???Ty, you can omit non-const generic types as well.
Member variables need not implement the derive macro.
The following is an example:
use const_struct::{call_with_generics, const_struct, ConstStruct};
#[derive(ConstStruct, Debug)]
pub struct TestSetting<const N: usize>;
pub fn tester<const N: usize, A: TestSettingTy<N>>() {
println!("a: {:?}", A::__DATA);
}
#[const_struct]
const B: TestSetting<5> = TestSetting;
fn main() {
tester::<5, macros::TestSetting!(5, TestSetting::<5>)>();
tester::<5, macros::TestSetting!(_, TestSetting::<5>)>();
tester::<4, macros::TestSetting!(4, TestSetting)>();
tester::<9, macros::TestSetting!(TestSetting::<9>)>();
tester::<5, macros::TestSetting!(B)>();
tester::<5, BTy>();
call_with_generics!(tester::<macros::TestSetting!(B)>());
call_with_generics!(tester::<5, BTy>());
call_with_generics!(tester::<macros::TestSetting!(_, BTy)>());
call_with_generics!(tester::<macros::TestSetting!(BTy)>());
}
When receiving composite types, only the outermost type should have Ty appended. Support for tuples is postponed and not yet available.
use const_struct::{primitive::OptionTy, F32, Some};
pub fn tester<A: OptionTy<Option<f32>>>() {
println!("a: {:?}", A::__DATA);
}
fn main() {
tester::<Some!(Some!(F32!(0.5)))>();
}
When receiving a composite type, wrap it with TupleTy
.
The maximum number of elements is 10.
use const_struct::{primitive::TupleTy, F32, F64, U32};
pub fn tester<A: TupleTy<(f32, f64, u32)>>() {
println!("a: {:?}", A::__DATA);
}
fn main() {
tester::<(F32!(0.5), F64!(0.5), U32!(0))>();
}
You can pass values defined externally into a trait. Options, tuples, and other types can be passed as they are.
use const_struct::{const_struct, primitive::F64Ty};
pub fn tester<A: F64Ty>() {
println!("a: {:?}", A::__DATA);
}
#[const_struct]
const PI: f64 = 3.14159265358979;
fn main() {
tester::<PiTy>();
}
It is possible to accept structs generated via derive.
For instance, consider the following example:
use const_struct::{primitive::TupleTy, ConstStruct, F32};
#[derive(ConstStruct, Debug)]
pub struct TestSetting;
pub fn tester<A: TupleTy<(f32, TestSetting)>>() {
println!("a: {:?}", A::__DATA);
}
fn main() {
tester::<(F32!(0.5), macros::TestSetting!(TestSetting))>();
}
It is possible to accept structs with generics that are generated via derive. Here's an example:
use const_struct::{call_with_generics, const_struct, primitive::TupleTy, ConstStruct, F32};
#[derive(ConstStruct, Debug)]
pub struct TestSetting<const N: usize>;
pub fn tester<const N: usize, A: TupleTy<(f32, TestSetting<N>)>>() {
println!("a: {:?}", A::__DATA);
}
#[const_struct]
const B: TestSetting<0> = TestSetting;
fn main() {
tester::<0, (F32!(0.5), BTy)>();
call_with_generics!(tester::<(F32!(0.5), macros::TestSetting!(BTy))>());
}
Structs that do not have the derive macro applied have considerable limitations.
Firstly, it cannot be used with structs that employ const generics. This is because the type cannot be determined.
Additionally, it is not possible to perform inner declaration consts using macros.
By using PrimitiveTraits, you can directly receive the type.
Since the member variables do not need to implement derive, you can create a simple wrapper that wraps existing structs, allowing them to be used without any restrictions.
use const_struct::{const_struct, primitive::TupleTy, PrimitiveTraits};
#[derive(Debug)]
pub struct TestSetting;
pub fn tester<A: TupleTy<(TestSetting,)>>() {
println!("a: {:?}", A::__DATA);
}
pub fn tester_alt<A: PrimitiveTraits<DATATYPE = TestSetting>>() {
println!("a: {:?}", A::__DATA);
}
#[const_struct]
const B: TestSetting = TestSetting;
fn main() {
tester::<(BTy,)>();
tester_alt::<BTy>();
}
Also, generics that are not const generics can be used.
However, you cannot use the call_with_generics! macro.
This is because there is no information on which generics to expand.
use const_struct::{const_struct, primitive::TupleTy};
pub trait Float {}
impl Float for f32 {}
#[derive(Debug)]
pub struct TestSetting<F: Float> {
a: F,
}
pub fn tester<F: Float + core::fmt::Debug + Copy, A: TupleTy<(TestSetting<F>,)>>() {
println!("a: {:?}", A::__DATA);
}
#[const_struct]
const B: TestSetting<f32> = TestSetting { a: 0.5 };
fn main() {
tester::<f32, (BTy,)>();
}
With const_struct, you can specify paths as shown below.
Using this technique, you can specify the absolute path of a struct, which eliminates the need to import the struct when using a macro for the struct name from another module or an external library.
Moreover, you don't need to specify the path for traits.
Since this doesn't rewrite the internal values that are passed, when creating a value or passing a type as generics, you need to specify the path using super, as shown below.
use const_struct::{const_struct, ConstStruct};
use core::fmt::Debug;
#[derive(Debug, Copy, Clone)]
pub struct Float32;
pub trait Float {}
impl Float for Float32 {}
#[derive(ConstStruct, Debug)]
#[const_struct(
TestSettingC: crate::test15::TestSettingC,
)]
pub struct TestSettingC<const N: usize, F: Float> {
_a: F,
}
pub fn tester<const N: usize, F: Float + Copy + Debug, A: TestSettingCTy<N, F>>() {
println!("a: {:?}", A::__DATA);
}
pub mod module {
fn main() {
const_struct::call_with_generics!(super::tester::<
super::macros::TestSettingC!(
super::Float32,
super::TestSettingC::<7, super::Float32> { _a: super::Float32 }
),
>());
}
}
This is an attribute macro that changes normal functions to receive generics based on a cfg flag.
It works by rewriting the internals, so it operates as is.
In the first argument, specify the variable.
When the cfg flag is present, it calls the original function, so when the cfg flag is absent, it calls the original function.
By adding another cfg flag, you can add cfg flags to the corresponding code.
Currently, only regular functions are supported.
use const_struct::ConstCompat;
#[const_compat(test_setting, #[cfg(not(feature = "dynamic"))])]
pub fn tester(test_setting: TestSetting) {
let t = test_setting.abc_def;
println!("{:?}", t);
tester_inner(test_setting.a.unwrap());
let test_setting = TestSetting::default();
println!("{:?}", test_setting);
}
- ConstStruct for enums, including outside macros, inside macros, etc., has not yet been tested.
- The current number of dependencies is 15. Efforts should be made to minimize this number.
pointers cannot be cast to integers during const eval
at compile-time, pointers do not have an integer value
avoiding this restriction via `transmute`, `union`, or raw pointers leads to compile-time undefined behavior
constructing invalid value: encountered a dangling reference (0x48[noalloc] has no provenance)
destructor of `generics::TestStructWithFloatGenerics<T, S>` cannot be evaluated at compile-time
error: reached the recursion limit while instantiating `...`