polymorphic_constant/
lib.rs

1// Copyright 2020 Louis Garczynski
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8#![no_std]
9
10/*!
11A macro to generate numerical constants in multiple types at once.
12
13You can have the number Pi be available in f64 or f32
14
15It was designed with three goals in mind:
16
17* Catch all overflow errors on compile-time
18* Minimize the code footprint
19* Be readable and easy to use
20
21This is meant as a temporary fix to the year-long debate over [Rust Pre-RFC #1337](https://internals.rust-lang.org/t/pre-rfc-untyped-constants/1337/)
22
23# Syntax
24
25```rust
26    use polymorphic_constant::polymorphic_constant;
27
28    polymorphic_constant! {
29        const PI: f32 | f64 = 3.141592653589793;
30    }
31
32    // Which can then be used as
33
34    fn pi_squared () -> f64 {
35        PI.f64 * PI.f64
36    }
37```
38
39A few features are supported:
40
41```rust
42    use polymorphic_constant::polymorphic_constant;
43
44    polymorphic_constant! {
45
46        /// Doc comment attributes
47        const PI: f32 | f64 = 3.141592653589793;
48
49        // Visibility modifiers (for both constant and type)
50        pub (crate) const E: f32 | f64 = 2.7182818284590452;
51
52        // Nonzero numeric types (NonZeroI32, NonZeroU8, etc)
53        const ASCII_LINE_RETURN: u8 | nz_u8 = 10;
54    }
55
56    // You can handle constants like any const struct
57    const PI_COPY: PI = PI;
58    const PI_F32: f32 = PI.f32;
59    
60    // Into is implemented for every variant of the constant
61    fn times_pi<T: std::ops::Mul<T>> (value: T) -> <T as std::ops::Mul>::Output
62    where
63        PI: Into<T>,
64    {
65        value * PI.into()
66    }
67
68    assert_eq!(times_pi(2.0), 6.283185307179586f64);
69```
70
71# Safety
72
73This system ensures that you keep all the safeties and warnings given by rust, but no more
74
75Any incompatible type will prevent compilation:
76
77* Float literals cannot be stored if it would convert them to infinity
78```compile_fail
79    # use polymorphic_constant::polymorphic_constant;
80    
81    # polymorphic_constant! {
82        const FAILS: f32 | f64 =  3141592653589793238462643383279502884197.0;
83    # }
84```
85
86* Literals cannot be stored in a type too small to hold them
87```compile_fail
88    # use polymorphic_constant::polymorphic_constant;
89    
90    # polymorphic_constant! {
91        const FAILS: u64 | nz_i8 = 128;
92    # }
93```
94
95* Negative numbers cannot be stored in unsigned types
96```compile_fail
97    # use polymorphic_constant::polymorphic_constant;
98    
99    # polymorphic_constant! {
100        const FAILS: i64 | u8 = -1;
101    # }
102```
103
104* 0 cannot be stored in non-zero types
105```compile_fail
106    # use polymorphic_constant::polymorphic_constant;
107    
108    # polymorphic_constant! {
109        const FAILS: nz_u8 | nz_u16 | nz_u32 = 0;
110    # }
111```
112
113* However, floats may lose precision, and a lot of it
114```rust
115    # use polymorphic_constant::polymorphic_constant;
116    
117    # polymorphic_constant! {
118        const SUCCEEDS: f32 = 3.141592653589793238462643383279;
119    # }
120```
121
122# Warnings
123
124Currently, the same constant cannot hold both int and float variants
125```compile_fail
126    # use polymorphic_constant::polymorphic_constant;
127    
128    # polymorphic_constant! {
129        const FAIL: i32 = 0.1;
130    # }
131```
132```compile_fail
133    # use polymorphic_constant::polymorphic_constant;
134    
135    # polymorphic_constant! {
136        const FAIL: f32 = 0;
137    # }
138```
139
140The constant also has to be initialized with an untyped literal
141```compile_fail
142    # use polymorphic_constant::polymorphic_constant;
143    
144    # polymorphic_constant! {
145        const FAIL: i32 = 0u32;
146    # }
147```
148
149It is still unclear if accepting the examples above could be dangerous,
150thus the conservative choice.
151
152# Example
153
154```
155use polymorphic_constant::polymorphic_constant;
156
157polymorphic_constant! {
158    const HEIGHT: i8 | u8 | i16 | u16 | i32 | u32 = 16;
159    const WIDTH: i8 | u8 | i16 | u16 | i32 | u32 = 32;
160}
161
162fn main() {
163    let size = HEIGHT.i16 * WIDTH.i16;
164
165    assert_eq!(size, 16 * 32);
166
167    let height_copy:i32 = HEIGHT.into();
168
169    assert_eq!(HEIGHT.i32, height_copy);
170}
171```
172
173# Support
174
175I would love any feedback on usage, for future ameliorations and features.
176*/
177
178/**
179Define one or more polymorphic numerical constants. A constant X of value 10, available in i32 and u32 will read:
180```
181# use polymorphic_constant::polymorphic_constant;
182polymorphic_constant! {
183    const X: i32 | u32 = 10;
184}
185```
186and be used like:
187```
188# use polymorphic_constant::polymorphic_constant;
189# polymorphic_constant! { const X: i32 | u32 = 10; }
190let x_i32 = X.i32;
191```
192*/
193
194#[macro_export(local_inner_macros)]
195macro_rules! polymorphic_constant {
196    // Handle the const (pub?) CONST format
197    ($(#[$attr:meta])* ($($vis:tt)*) const $name:ident : $( $numeric_type:ident )|* = $lit:literal; $($nextLine:tt)*) => {
198
199        // Generate the struct to hold the constant
200
201        // Remove warnings
202        #[allow(non_camel_case_types)]
203        // Derive the common traits, but only if std is available
204        #[cfg_attr(not(no_std), derive(Debug, Clone, Copy))]
205        // Expend the attributes passed by the user
206        $(#[$attr])*
207        // Add the visibility attributes
208        $($vis)*
209        // Create the struct
210        struct $name {
211            // For each type (f32, ...) create a new property
212            $($numeric_type: __nz_impl!(@GET_TYPE $numeric_type),)*
213        }
214
215        // Implement `into` for every type
216        $(impl ::core::convert::Into<__nz_impl!(@GET_TYPE $numeric_type)> for $name {
217            fn into(self) -> __nz_impl!(@GET_TYPE $numeric_type) {
218                self.$numeric_type
219            }
220        })*
221
222        // Expand the visibility, this time for the constant
223        $($vis)*
224        // Instantiate the struct and create the constant
225        const $name: $name = $name {
226            $($numeric_type: __nz_impl!(@MAKE_VAL $lit, $numeric_type ),)*
227        };
228        // Keep munching until the next ;
229        polymorphic_constant!($($nextLine)*);
230    };
231
232    // Handle `const CONST` format
233    ($(#[$attr:meta])* const $($t:tt)*) => {
234        // use `()` to explicitly forward the information about private items
235        polymorphic_constant!($(#[$attr])* () const $($t)*);
236    };
237    // Handle `pub const CONST` format
238    ($(#[$attr:meta])* pub const $($t:tt)*) => {
239        polymorphic_constant!($(#[$attr])* (pub) const $($t)*);
240    };
241    // Handle `pub (crate) CONST` format and similar
242    ($(#[$attr:meta])* pub ($($vis:tt)+) const $($t:tt)*) => {
243        polymorphic_constant!($(#[$attr])* (pub ($($vis)+)) const $($t)*);
244    };
245    () => {};
246}
247
248#[macro_export]
249#[doc(hidden)]
250macro_rules! __nz_impl {
251    // constally obtain a nonzero struct
252    // Surprisingly fails to compile if $lit is 0 or not in range
253    (@MAKE_VAL $lit:literal, nz_i8   ) => { unsafe { ::std::num::NonZeroI8::new_unchecked($lit) } };
254    (@MAKE_VAL $lit:literal, nz_i16  ) => { unsafe { ::std::num::NonZeroI16::new_unchecked($lit) } };
255    (@MAKE_VAL $lit:literal, nz_i32  ) => { unsafe { ::std::num::NonZeroI32::new_unchecked($lit) } };
256    (@MAKE_VAL $lit:literal, nz_i64  ) => { unsafe { ::std::num::NonZeroI64::new_unchecked($lit) } };
257    (@MAKE_VAL $lit:literal, nz_i128 ) => { unsafe { ::std::num::NonZeroI128::new_unchecked($lit) } };
258    (@MAKE_VAL $lit:literal, nz_isize) => { unsafe { ::std::num::NonZeroIsize::new_unchecked($lit) } };
259    (@MAKE_VAL $lit:literal, nz_u8   ) => { unsafe { ::std::num::NonZeroU8::new_unchecked($lit) } };
260    (@MAKE_VAL $lit:literal, nz_u16  ) => { unsafe { ::std::num::NonZeroU16::new_unchecked($lit) } };
261    (@MAKE_VAL $lit:literal, nz_u32  ) => { unsafe { ::std::num::NonZeroU32::new_unchecked($lit) } };
262    (@MAKE_VAL $lit:literal, nz_u64  ) => { unsafe { ::std::num::NonZeroU64::new_unchecked($lit) } };
263    (@MAKE_VAL $lit:literal, nz_u128 ) => { unsafe { ::std::num::NonZeroU128::new_unchecked($lit) } };
264    (@MAKE_VAL $lit:literal, nz_usize) => { unsafe { ::std::num::NonZeroUsize::new_unchecked($lit) } };
265    (@MAKE_VAL $lit:literal, $numeric_type:ident) => { $lit };
266
267    // Get the full nonzero type from shorthand
268    // Fails in nonstd
269    (@GET_TYPE nz_i8   ) => { ::std::num::NonZeroI8 };
270    (@GET_TYPE nz_i16  ) => { ::std::num::NonZeroI16 };
271    (@GET_TYPE nz_i32  ) => { ::std::num::NonZeroI32 };
272    (@GET_TYPE nz_i64  ) => { ::std::num::NonZeroI64 };
273    (@GET_TYPE nz_i128 ) => { ::std::num::NonZeroI128 };
274    (@GET_TYPE nz_isize) => { ::std::num::NonZeroIsize };
275    (@GET_TYPE nz_u8   ) => { ::std::num::NonZeroU8 };
276    (@GET_TYPE nz_u16  ) => { ::std::num::NonZeroU16 };
277    (@GET_TYPE nz_u32  ) => { ::std::num::NonZeroU32 };
278    (@GET_TYPE nz_u64  ) => { ::std::num::NonZeroU64 };
279    (@GET_TYPE nz_u128 ) => { ::std::num::NonZeroU128 };
280    (@GET_TYPE nz_usize) => { ::std::num::NonZeroUsize };
281    (@GET_TYPE $numeric_type:ident) => { $numeric_type };
282}