├── .gitignore ├── Cargo.toml ├── build.rs ├── constany_blank ├── Cargo.toml └── src │ └── lib.rs ├── constany_stage_one ├── Cargo.toml └── src │ └── lib.rs ├── constany_stage_two ├── Cargo.toml └── src │ └── lib.rs ├── docs ├── _config.yml └── index.md └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "constany_stage_one", 5 | "constany_stage_two", 6 | "constany_blank" 7 | ] -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | if let Ok(i) = std::env::var("NO_BUILD") { 3 | if &i == "true" { 4 | return; 5 | } 6 | } 7 | std::process::Command::new(std::env::var("CARGO").unwrap()) 8 | .args(&["run", "--release", "--features", "stage_one"]) 9 | .env("NO_BUILD", "true") 10 | .status() 11 | .unwrap(); 12 | println!("cargo:rustc-cfg=feature=\"stage_two\""); 13 | } 14 | -------------------------------------------------------------------------------- /constany_blank/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "constany_blank" 3 | version = "1.0.2" 4 | authors = ["moelife-coder <61054382+moelife-coder@users.noreply.github.com>"] 5 | edition = "2018" 6 | description = "Convert any function to constant" 7 | repository = "https://github.com/moelife-coder/constany/" 8 | license = "AGPL-3.0-only" 9 | keywords = ["function", "compile", "static", "size", "macro"] 10 | categories = ["development-tools"] 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [lib] 15 | proc-macro = true -------------------------------------------------------------------------------- /constany_blank/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! **Please refer to [`constany_stage_one` document](https://docs.rs/constany_stage_one/0.1.0/constany_stage_one/).** 2 | //! This crate is a blank implementation for `constany` to satisfy grammar checker and avoid conflict. 3 | 4 | extern crate proc_macro; 5 | 6 | use crate::proc_macro::TokenStream; 7 | 8 | #[proc_macro_attribute] 9 | pub fn const_fn(_: TokenStream, item: TokenStream) -> TokenStream { 10 | item 11 | } 12 | 13 | #[proc_macro_attribute] 14 | pub fn main_fn(_: TokenStream, item: TokenStream) -> TokenStream { 15 | item 16 | } 17 | -------------------------------------------------------------------------------- /constany_stage_one/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "constany_stage_one" 3 | version = "0.2.0" 4 | authors = ["moelife-coder <61054382+moelife-coder@users.noreply.github.com>"] 5 | edition = "2018" 6 | description = "Convert any function to constant" 7 | repository = "https://github.com/moelife-coder/constany/" 8 | license = "AGPL-3.0-only" 9 | keywords = ["function", "compile", "static", "size", "macro"] 10 | categories = ["development-tools"] 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [lib] 15 | proc-macro = true 16 | 17 | [dependencies] 18 | syn = {version = "1.0", features = ["full"]} 19 | quote = "1.0" 20 | seahash = "4.0" -------------------------------------------------------------------------------- /constany_stage_one/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! **Constany is a rust macro to allow constant result for any function.** 2 | //! 3 | //! Constant functions in rust is a group of function with its result will be evaluated during compile time. It can significantly reduce generated binary size and improve performance. However, due to technical and logical limitations, some expression cannot be evaluated as constant function. For example: 4 | //! ```compile_fail 5 | //! fn main() { 6 | //! println!("{}", add_one_to_six()); 7 | //! } 8 | //! const fn add_one_to_six() -> String { 9 | //! let mut a = 1; 10 | //! for b in 1..7 { // error[E0744]: `for` is not allowed in a `const fn` 11 | //! a += b; 12 | //! } 13 | //! return a.to_string(); 14 | //! } 15 | //! ``` 16 | //! 17 | //! Constany use a workaround for this: by using Constany, the function and the main function will be compiled twice. The value of the function will be evaluated at the first time, and the value will be wrapped into a constant function at the second time. 18 | //! 19 | //! `Cargo.toml:` 20 | //! ```toml 21 | //! [features] 22 | //! stage_one = ["constany_stage_one"] 23 | //! stage_two = ["constany_stage_two"] 24 | //! [dependencies] 25 | //! constany_stage_one = {version = "0.1", optional = true} 26 | //! constany_stage_two = {version = "0.1", optional = true} 27 | //! constany_blank = {version = "1"} 28 | //! ``` 29 | //! 30 | //! `main.rs:` 31 | //! ```ignore 32 | //! #[cfg(any( 33 | //! not(any(feature = "stage_one", feature = "stage_two")), 34 | //! all(feature = "stage_two", feature = "stage_one") 35 | //! ))] 36 | //! use constany_blank as constany; // This line is for grammar checkers that enable all feature / disable all feature. If you do not have a checker, you can delete those lines safely. 37 | //! #[cfg(all(feature = "stage_one", not(feature = "stage_two")))] 38 | //! use constany_stage_one as constany; 39 | //! #[cfg(all(feature = "stage_two", not(feature = "stage_one")))] 40 | //! use constany_stage_two as constany; 41 | //! #[constany::main_fn("function_evaled_at_compile_time")] 42 | //! fn main() { 43 | //! println!("Hello, world!"); 44 | //! function_evaled_at_compile_time(); 45 | //! } 46 | //! #[constany::const_fn] 47 | //! fn function_evaled_at_compile_time() -> i32 { 48 | //! let mut a = 1; 49 | //! let b = 5; 50 | //! for _ in 0..b { 51 | //! a += 1; // For loop is not allowed in `const fn` 52 | //! } 53 | //! a 54 | //! } 55 | //! ``` 56 | //! When you need to build the function, do this: 57 | //! ```bash 58 | //! $ cargo run --featues stage_one 59 | //! $ cargo build --features stage_two // If you want to run the command instead, use `cargo run` 60 | //! ``` 61 | //! And your function will be interpreted as constant function. 62 | //! 63 | //! ## Multiple constant function 64 | //! Having multiple constant functions are also applicable, you just need to make sure every function you want to make constant are labeled with `const_fn` and the function name is inside `main_fn`: 65 | //! ```ignore 66 | //! #[cfg(any( 67 | //! not(any(feature = "stage_one", feature = "stage_two")), 68 | //! all(feature = "stage_two", feature = "stage_one") 69 | //! ))] 70 | //! use constany_blank as constany; 71 | //! #[cfg(all(feature = "stage_one", not(feature = "stage_two")))] 72 | //! use constany_stage_one as constany; 73 | //! #[cfg(all(feature = "stage_two", not(feature = "stage_one")))] 74 | //! use constany_stage_two as constany; 75 | //! #[constany::main_fn("function_evaled_at_compile_time", "function_evaled_at_compile_time")] 76 | //! fn main() { 77 | //! println!("Hello, world!"); 78 | //! function_evaled_at_compile_time(); 79 | //! function_evaled_at_compile_time_2(); 80 | //! } 81 | //! #[constany::const_fn] 82 | //! fn function_evaled_at_compile_time() -> i32 { 83 | //! let mut a = 1; 84 | //! let b = 5; 85 | //! for _ in 0..b { 86 | //! a += 1; 87 | //! } 88 | //! a 89 | //! } 90 | //! #[constany::const_fn] 91 | //! fn function_evaled_at_compile_time_2() -> i32 { 92 | //! let mut a = 1; 93 | //! let b = 100; 94 | //! for _ in 0..b { 95 | //! a += 1; 96 | //! } 97 | //! a 98 | //! } 99 | //! ``` 100 | //! 101 | //! ## Function with non-primitive result 102 | //! Returning a non-primitive result (probably `struct` or `enum`) is troublesome and prone to error. The most elegant way is to use `lazy_static` for stage one and default to avoid compiler warning, and use constant value function for stage two: 103 | //! ```compile_fail 104 | //! #[cfg(feature = "stage_two")] 105 | //! const ABC: String = constant_function().to_string(); 106 | //! #[cfg(not(feature = "stage_two"))] 107 | //! lazy_static::lazy_static! { 108 | //! const ref ABC: String = constant_function().to_string(); 109 | //! } 110 | //! ``` 111 | //! However, this will not work for most of the non-primitive type because their constructor is usually not `static`. 112 | //! 113 | //! There are two workaround for this: the `debug + pub` solution and `memop` solution. 114 | //! 115 | //! The `debug + pub` solution first use `debug` trait to print the structure, and use the `pub` trait to rebuild it. 116 | //! This solution can recreate the structure without `unsafe` code. However, this require the structure to derive `Debug`. 117 | //! Current implementation also require the structure to not have `paths`, such as `std::string::String` (if there are `::` in the identifier, it's likely that this solution will not work out). 118 | //! 119 | //! The `memop` solution transmute the memory directly. 120 | //! This solution can rebuild any structure, but please note that this method is `unsafe` and very dangerous. 121 | //! The generated function will be `fn` instead of `const_fn` because memory allocation is not allowed in `const`, although the memory itself is hard-coded inside the function. 122 | 123 | extern crate proc_macro; 124 | 125 | use crate::proc_macro::TokenStream; 126 | use quote::quote; 127 | use syn; 128 | 129 | fn is_primitive_type(input: &syn::Type) -> bool { 130 | match input { 131 | syn::Type::Path(i) => { 132 | if i.path.leading_colon.is_none() && i.path.segments.len() == 1 { 133 | match i.path.segments[0].ident.to_string().as_str() { 134 | "i8" | "i16" | "i32" | "i64" | "i128" | "isize" | "u8" | "u16" | "u32" 135 | | "u64" | "u128" | "usize" | "f32" | "f64" | "bool" | "char" | "str" => true, 136 | _ => false, 137 | } 138 | } else { 139 | false 140 | } 141 | } 142 | syn::Type::Array(i) => is_primitive_type(&i.elem), 143 | syn::Type::Group(i) => is_primitive_type(&i.elem), 144 | syn::Type::Slice(i) => is_primitive_type(&i.elem), 145 | syn::Type::Tuple(i) => { 146 | for i in &i.elems { 147 | if !is_primitive_type(&i) { 148 | return false; 149 | } 150 | } 151 | return true; 152 | } 153 | _ => false, 154 | } 155 | } 156 | 157 | /// Generate a constant function 158 | #[proc_macro_attribute] 159 | pub fn const_fn(attr: TokenStream, bare_item: TokenStream) -> TokenStream { 160 | let item: syn::ItemFn = syn::parse(bare_item.clone()).unwrap(); 161 | let name = &item.sig.ident; 162 | let visibility = &item.vis; 163 | let output_type = &item.sig.output; 164 | let wrapper_fn_name = quote::format_ident!("_{}_wrapper_fn", name.to_string()); 165 | let (generation_method, fbyte) = if match output_type { 166 | syn::ReturnType::Default => { 167 | return syn::Error::new_spanned( 168 | output_type, 169 | "Fn with `()` output should not become constant", 170 | ) 171 | .to_compile_error() 172 | .into() 173 | } 174 | syn::ReturnType::Type(_, i) => is_primitive_type(&i), 175 | } { 176 | ( 177 | quote! { 178 | format!("{:?}", #name()) 179 | }, 180 | 0u8, 181 | ) 182 | } else { 183 | let a = quote::format_ident!("{}", name.to_string()); 184 | if attr.to_string().contains("memop") { 185 | let output_type = match output_type { 186 | syn::ReturnType::Default => unimplemented!(), 187 | syn::ReturnType::Type(_, j) => j, 188 | }; 189 | ( 190 | quote! { 191 | format!("{:?}", unsafe { 192 | std::mem::transmute::<#output_type, [u8; std::mem::size_of::<#output_type>()]>(#a()) 193 | }) 194 | }, 195 | 1, 196 | ) 197 | } else { 198 | // This should be changed. 199 | ( 200 | quote! { 201 | format!("{:?}", #name()) 202 | }, 203 | 0, 204 | ) 205 | } 206 | }; 207 | let code_hash = seahash::hash(bare_item.to_string().as_bytes()); 208 | let generated = quote! { 209 | #item 210 | #visibility fn #wrapper_fn_name() -> (String, u8, u64) { 211 | (#generation_method, #fbyte, #code_hash) 212 | } 213 | }; 214 | generated.into() 215 | } 216 | 217 | /// Attribute appending on `fn main()` 218 | /// 219 | /// When generating a constant function, you need to include it in the attribute: eg. `#[main_fn(a_constant_function, another_constant_function)]` 220 | #[proc_macro_attribute] 221 | pub fn main_fn(attr: TokenStream, item: TokenStream) -> TokenStream { 222 | drop(item); 223 | let mut attr_vec: Vec = Vec::new(); 224 | for i in attr { 225 | let i: proc_macro::TokenStream = i.into(); 226 | attr_vec.push(i.to_string()) 227 | } 228 | let mut fn_vec = Vec::new(); 229 | for i in attr_vec { 230 | if i != "," { 231 | let fn_name = &i[1..i.len() - 1]; 232 | let wrapper_fn_name = quote::format_ident!("_{}_wrapper_fn", fn_name.to_string()); 233 | fn_vec.push((wrapper_fn_name, format!("target/{}.res", fn_name))); 234 | } 235 | } 236 | let mut generated = quote! {}; 237 | for i in fn_vec { 238 | let (i, j) = i; 239 | generated = quote! { 240 | #generated 241 | let (j, i, k) = #i(); 242 | let mut constructed = vec![i]; 243 | constructed.extend_from_slice(&k.to_be_bytes()); 244 | constructed.extend_from_slice(&j.into_bytes()); 245 | std::fs::write(#j, constructed).unwrap(); 246 | } 247 | } 248 | //let item: syn::ItemFn = syn::parse(item).unwrap(); 249 | let generated = quote! { 250 | fn main() { 251 | #generated 252 | } 253 | }; 254 | generated.into() 255 | } 256 | -------------------------------------------------------------------------------- /constany_stage_two/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "constany_stage_two" 3 | version = "0.2.0" 4 | authors = ["moelife-coder <61054382+moelife-coder@users.noreply.github.com>"] 5 | edition = "2018" 6 | description = "Convert any function to constant" 7 | repository = "https://github.com/moelife-coder/constany/" 8 | license = "AGPL-3.0-only" 9 | keywords = ["function", "compile", "static", "size", "macro"] 10 | categories = ["development-tools"] 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [lib] 15 | proc-macro = true 16 | 17 | [dependencies] 18 | syn = {version = "1.0", features = ["full"]} 19 | quote = "1.0" 20 | seahash = "4.0" -------------------------------------------------------------------------------- /constany_stage_two/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! **Please refer to [`constany_stage_one` document](https://docs.rs/constany_stage_one/0.1.0/constany_stage_one/).** 2 | //! This crate is for the second stage of constany build, which will generate the final constant function based on stage one artifact. 3 | 4 | extern crate proc_macro; 5 | 6 | use crate::proc_macro::TokenStream; 7 | use quote::quote; 8 | use syn; 9 | 10 | #[proc_macro_attribute] 11 | pub fn const_fn(attr: TokenStream, bare_item: TokenStream) -> TokenStream { 12 | let item: syn::ItemFn = syn::parse(bare_item.clone()).unwrap(); 13 | let name = &item.sig.ident; 14 | let visibility = &item.vis; 15 | let data = std::fs::read(&format!("target/{}.res", item.sig.ident.to_string())).expect("Unable to load function content resource. Please make sure you have executed --stage-one before compiling the final product."); 16 | use std::convert::TryInto; 17 | if u64::from_be_bytes( 18 | data[1..9] 19 | .try_into() 20 | .expect("Broken resource file. Please execute stage one again."), 21 | ) != seahash::hash(bare_item.to_string().as_bytes()) 22 | { 23 | panic!("Incorrect function hash. Please make sure you have executed --stage-one before compiling the final product.") 24 | }; 25 | let return_type = &item.sig.output; 26 | let real_data = String::from_utf8(data[9..].to_vec()).unwrap(); 27 | let const_value = if attr.to_string().contains("force_const") { 28 | true 29 | } else { 30 | false 31 | }; 32 | let constructed = match data[0] { 33 | 0 => { 34 | // A super hacky method to generate useable result. 35 | if const_value { 36 | let const_name = quote::format_ident!("CONST_VALUE_OF_FN_{}", name); 37 | let output_type_2 = match return_type { 38 | syn::ReturnType::Default => unimplemented!(), 39 | syn::ReturnType::Type(_, j) => j, 40 | }; 41 | let generated = quote! { 42 | const #const_name: #output_type_2 = DONOTTOUCHME; 43 | } 44 | .to_string(); 45 | let generated = generated.replace("DONOTTOUCHME", &real_data); 46 | let generated: syn::Item = syn::parse_str(&generated).unwrap(); 47 | quote! { 48 | #generated 49 | #visibility const fn #name() #return_type { 50 | return #const_name 51 | } 52 | } 53 | } else { 54 | let generated = quote! { 55 | #visibility const fn #name() #return_type { 56 | return DONOTTOUCHME 57 | } 58 | } 59 | .to_string(); 60 | let generated = generated.replace("DONOTTOUCHME", &real_data); 61 | let generated: syn::ItemFn = syn::parse_str(&generated).unwrap(); 62 | quote! { 63 | #generated 64 | } 65 | } 66 | } 67 | 1 => { 68 | let byte_count = real_data.matches(',').count() + 1; 69 | let output_type_2 = match return_type { 70 | syn::ReturnType::Default => unimplemented!(), 71 | syn::ReturnType::Type(_, j) => j, 72 | }; 73 | let (advanced_generated, generated) = if const_value { 74 | let const_name = quote::format_ident!("CONST_VALUE_OF_FN_{}", name); 75 | ( 76 | Some(quote! { 77 | const #const_name : [u8; THISISTHEBYTECOUNT] = THISISTHEVALUE; 78 | }), 79 | quote! { 80 | #visibility fn #name() #return_type { 81 | unsafe { 82 | std::mem::transmute::<[u8; THISISTHEBYTECOUNT], #output_type_2>(#const_name) 83 | } 84 | } 85 | }, 86 | ) 87 | } else { 88 | ( 89 | None, 90 | quote! { 91 | #visibility fn #name() #return_type { 92 | let constant_value = THISISTHEVALUE; 93 | unsafe { 94 | std::mem::transmute::<[u8; THISISTHEBYTECOUNT], #output_type_2>(constant_value) 95 | } 96 | } 97 | }, 98 | ) 99 | }; 100 | let generated = generated.to_string(); 101 | let advanced_generated = if let Some(i) = advanced_generated { 102 | let constructed = i 103 | .to_string() 104 | .replace("THISISTHEBYTECOUNT", &byte_count.to_string()) 105 | .replace("THISISTHEVALUE", &real_data); 106 | let generated: syn::Item = syn::parse_str(&constructed).unwrap(); 107 | quote! { 108 | #generated 109 | } 110 | } else { 111 | quote! {} 112 | }; 113 | let generated = generated 114 | .replace("THISISTHEBYTECOUNT", &byte_count.to_string()) 115 | .replace("THISISTHEVALUE", &real_data); 116 | let generated: syn::ItemFn = syn::parse_str(&generated).unwrap(); 117 | quote! { 118 | #advanced_generated 119 | #generated 120 | } 121 | } 122 | _ => unimplemented!(), 123 | }; 124 | return constructed.into(); 125 | } 126 | #[proc_macro_attribute] 127 | pub fn main_fn(_: TokenStream, item: TokenStream) -> TokenStream { 128 | let item: syn::ItemFn = syn::parse(item).unwrap(); 129 | let generated = quote! { 130 | #item 131 | }; 132 | generated.into() 133 | } 134 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Constany: convert any rust function to const function 2 | 3 | ![Stage One](https://img.shields.io/crates/v/constany_stage_one) ![License](https://img.shields.io/crates/l/constany_stage_one) ![Downloads](https://img.shields.io/crates/d/constany_blank) 4 | 5 | > Constany allows you to build const (or at least pseudo-const) function out of any expression 6 | 7 | In rust, const functions are a type of functions that may be interpreted by the compiler at compile time. Const functions have various restrictions to make sure that they can be evaluated at compile time. For most of the time, these restrictions are beneficial because it prevent misuse. However, sometimes the use is intended: 8 | 9 | ```rust 10 | use std::collections::HashMap; 11 | 12 | const DEFAULT_USER: HashMap<&'static str, u8> = HashMap::new(); // Error! 13 | 14 | fn main() {} 15 | ``` 16 | 17 | or 18 | 19 | ```rust 20 | fn main() {} 21 | 22 | const fn add_one_to_six() -> u8 { 23 | let mut a = 1; 24 | for b in 1..7 { 25 | a += b; 26 | } // Error! 27 | a 28 | } 29 | ``` 30 | 31 | Constany provides a workaround to manually override those limitations. 32 | 33 | ## Why const function? 34 | 35 | - Compile-time evaluation: faster runtime execution 36 | 37 | - Smaller binary size (if the function itself is LARGE) 38 | 39 | ## How constany works? 40 | 41 | Constany use a workaround for this: the function marked as `constany::const_fn` and the main function will be compiled twice. For the first time, the value of the function will be recorded. For the second time, the function will be replaced by the value. 42 | 43 | ## Warnings 44 | 45 | - For most of cases, constany will cause unexpected consequences. Please be aware that functions marked by constany will be executed **during compilation**, not during execution. 46 | 47 | - Functions generated by constany is not technically equivaient to `const fn`: constany makes the result of the function hard-coded into the binary, regardless the context of it; `const fn` will only produce static result when calling from a const environment. 48 | 49 | ## Usage 50 | 51 | **WARNING: library support is not implemented yet. PRs are welcomed.** 52 | 53 | **Starting from version 0.2, `constany_stage_one` and `constany_stage_two` are not optional depencies anymore. This will not affect built binary size.** 54 | 55 | Using constany is a bit tricker than normal library. 56 | 57 | Firstly, you need to make sure there's two feature in `Cargo.toml` and import `stage one` and `stage two` as dependicies. 58 | 59 | `Cargo.toml:` 60 | ```toml 61 | [features] 62 | stage_one = [] 63 | stage_two = [] 64 | [dependencies] 65 | constany_stage_one = {version = "0.2"} 66 | constany_stage_two = {version = "0.2"} 67 | constany_blank = {version = "1"} 68 | ``` 69 | 70 | `constany_blank` is not necessary if there's no grammar checker and programmer will not accidently compile the code without `--feature` flag; it is simply a blank implementation for constany macros to avoid the compiler to complain. 71 | 72 | The next step involves `main.rs`: 73 | 74 | `main.rs:` 75 | ```rust 76 | #[cfg(any( 77 | not(any(feature = "stage_one", feature = "stage_two")), 78 | all(feature = "stage_two", feature = "stage_one") 79 | ))] 80 | use constany_blank as constany; // This line is for grammar checkers that enable all feature / disable all feature. If you do not have a checker, you can delete those lines safely. 81 | #[cfg(all(feature = "stage_one", not(feature = "stage_two")))] 82 | use constany_stage_one as constany; 83 | #[cfg(all(feature = "stage_two", not(feature = "stage_one")))] 84 | use constany_stage_two as constany; 85 | #[constany::main_fn("function_evaled_at_compile_time")] 86 | fn main() { 87 | // Blah Blah Blah 88 | function_evaled_at_compile_time(); 89 | // Blah Blah Blah 90 | } 91 | #[constany::const_fn] 92 | fn function_evaled_at_compile_time() -> i32 { 93 | let mut a = 1; 94 | let b = 5; 95 | for _ in 0..b { 96 | a += 1; 97 | } 98 | a 99 | } 100 | ``` 101 | 102 | Make sure `main` function is marked with `constany::main_fn()` and the constant function list is inside the bracket. Otherwise the function will not be compiled to constant. 103 | 104 | ### Compile for binary application 105 | 106 | #### Compile manually (the long way) 107 | 108 | When you need to build the function, execute: 109 | 110 | ```bash 111 | $ cargo run --featues stage_one 112 | $ cargo build --features stage_two // If you want to run the code instead, use `cargo run` 113 | ``` 114 | 115 | And your function will be interpreted as constant function. 116 | 117 | #### Compile using build script (the experimental way) 118 | 119 | You can add [our build script](build.rs) to your code folder. Please add it outside `src`, in the same folder as `Cargo.toml`: 120 | 121 | ``` 122 | |- Cargo.toml 123 | |- Cargo.lock 124 | |- src/ 125 | |- main.rs 126 | |- blah.rs 127 | |- build.rs // HERE!!! 128 | ``` 129 | ## Issues & Gotchas 130 | 131 | ### Multiple constant function 132 | 133 | Having multiple constant functions are also applicable, you just need to make sure every function you want to make constant are labeled with `const_fn` and the function name is inside `main_fn`: 134 | 135 | ```rust 136 | // --snip-- 137 | // Please look at previous example for this part 138 | // --snip-- 139 | #[constany::main_fn("function_evaled_at_compile_time", "function_evaled_at_compile_time_2")] 140 | fn main() { 141 | function_evaled_at_compile_time(); 142 | function_evaled_at_compile_time_2(); 143 | } 144 | #[constany::const_fn] 145 | fn function_evaled_at_compile_time() -> i32 { 146 | let mut a = 1; 147 | let b = 5; 148 | for _ in 0..b { 149 | a += 1; 150 | } 151 | a 152 | } 153 | #[constany::const_fn] 154 | fn function_evaled_at_compile_time_2() -> i32 { 155 | let mut a = 1; 156 | let b = 100; 157 | for _ in 0..b { 158 | a += 1; 159 | } 160 | a 161 | } 162 | ``` 163 | 164 | ### Function with non-primitive result 165 | 166 | Returning a non-primitive result is troublesome and prone to error. The most elegant way is to use `lazy_static` for stage one to avoid compiler warning, and use constant value function for stage two: 167 | 168 | ```rust 169 | #[cfg(feature = "stage_two")] 170 | const ABC: String = constant_function().to_string(); 171 | #[cfg(not(feature = "stage_two"))] 172 | lazy_static::lazy_static! { 173 | const ref ABC: String = constant_function().to_string(); 174 | } 175 | ``` 176 | However, this will not work for most of the non-primitive type because their constructor is unlikely to be `static`. 177 | 178 | There are two workaround for this: the `debug + pub` solution and `memop` solution. 179 | 180 | #### The Debug + Pub solution 181 | 182 | The `debug + pub` solution first use `debug` trait to print the structure, and use the `pub` trait to rebuild it. 183 | 184 | This solution can recreate the structure without `unsafe` code. However, this require the structure to derive `Debug`. 185 | 186 | Current implementation also require the structure to not have `paths`, such as `std::string::String` (if there are `::` in the identifier, it's likely that this solution will not work out). 187 | 188 | To use this solution, you can simply label `constany::const_fn` because this is the default solution for constany. 189 | 190 | #### The Memop solution 191 | 192 | The `memop` solution transmute the memory directly. 193 | This solution can rebuild any structure, but please note that this method is `unsafe` and very dangerous. 194 | 195 | The generated function will be `fn` instead of `const_fn` because memory allocation is not allowed in `const`, although the memory itself is hard-coded inside the function. 196 | 197 | To use this solution, you need to label target function as `constany::const_fn(memop)`: 198 | 199 | ```rust 200 | // --snip-- 201 | // Please look at previous example for this part 202 | // --snip-- 203 | #[constany::main_fn("function_evaled_at_compile_time")] 204 | fn main() { 205 | function_evaled_at_compile_time(); 206 | } 207 | #[constany::const_fn(memop)] 208 | fn function_evaled_at_compile_time() -> String { 209 | let mut a = 1; 210 | let b = 5; 211 | for _ in 0..b { 212 | a += 1; 213 | } 214 | a.to_string() 215 | } 216 | ``` 217 | 218 | Please note that if the function is returning a primitive type in rust, the memory operation will not be used regardless the `memop` flag. 219 | 220 | ### Make sure the returning value is hard-coded 221 | 222 | Constany has already make sure that the returning value is hard-coded into the function. However, if you want to have a double-safety precaution, you can add `force_const` flag to the function mark. This will make the result as a constant value declared outside the function, and the function is simply a wrapper to return that value. 223 | 224 | ```rust 225 | // --snip-- 226 | // Please look at previous example for this part 227 | // --snip-- 228 | #[constany::main_fn("function_evaled_at_compile_time")] 229 | fn main() { 230 | function_evaled_at_compile_time(); 231 | } 232 | #[constany::const_fn(memop, force_const)] 233 | fn function_evaled_at_compile_time() -> String { 234 | let mut a = 1; 235 | let b = 5; 236 | for _ in 0..b { 237 | a += 1; 238 | } 239 | a.to_string() 240 | } 241 | ``` 242 | 243 | ## Contributing 244 | 245 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Constany: convert any rust function to const function 2 | 3 | ![Stage One](https://img.shields.io/crates/v/constany_stage_one) ![License](https://img.shields.io/crates/l/constany_stage_one) ![Downloads](https://img.shields.io/crates/d/constany_blank) 4 | 5 | > Constany allows you to build const (or at least pseudo-const) function out of any expression 6 | 7 | In rust, const functions are a type of functions that may be interpreted by the compiler at compile time. Const functions have various restrictions to make sure that they can be evaluated at compile time. For most of the time, these restrictions are beneficial because it prevent misuse. However, sometimes the use is intended: 8 | 9 | ```rust 10 | use std::collections::HashMap; 11 | 12 | const DEFAULT_USER: HashMap<&'static str, u8> = HashMap::new(); // Error! 13 | 14 | fn main() {} 15 | ``` 16 | 17 | or 18 | 19 | ```rust 20 | fn main() {} 21 | 22 | const fn add_one_to_six() -> u8 { 23 | let mut a = 1; 24 | for b in 1..7 { 25 | a += b; 26 | } // Error! 27 | a 28 | } 29 | ``` 30 | 31 | Constany provides a workaround to manually override those limitations. 32 | 33 | ## Why const function? 34 | 35 | - Compile-time evaluation: faster runtime execution 36 | 37 | - Smaller binary size (if the function itself is LARGE) 38 | 39 | ## How constany works? 40 | 41 | Constany use a workaround for this: the function marked as `constany::const_fn` and the main function will be compiled twice. For the first time, the value of the function will be recorded. For the second time, the function will be replaced by the value. 42 | 43 | ## Warnings 44 | 45 | - For most of cases, constany will cause unexpected consequences. Please be aware that functions marked by constany will be executed **during compilation**, not during execution. 46 | 47 | - Functions generated by constany is not technically equivaient to `const fn`: constany makes the result of the function hard-coded into the binary, regardless the context of it; `const fn` will only produce static result when calling from a const environment. 48 | 49 | ## Usage 50 | 51 | **WARNING: library support is not implemented yet. PRs are welcomed.** 52 | 53 | **Starting from version 0.2, `constany_stage_one` and `constany_stage_two` are not optional depencies anymore. This will not affect built binary size.** 54 | 55 | Using constany is a bit tricker than normal library. 56 | 57 | Firstly, you need to make sure there's two feature in `Cargo.toml` and import `stage one` and `stage two` as dependicies. 58 | 59 | `Cargo.toml:` 60 | ```toml 61 | [features] 62 | stage_one = [] 63 | stage_two = [] 64 | [dependencies] 65 | constany_stage_one = {version = "0.2"} 66 | constany_stage_two = {version = "0.2"} 67 | constany_blank = {version = "1"} 68 | ``` 69 | 70 | `constany_blank` is not necessary if there's no grammar checker and programmer will not accidently compile the code without `--feature` flag; it is simply a blank implementation for constany macros to avoid the compiler to complain. 71 | 72 | The next step involves `main.rs`: 73 | 74 | `main.rs:` 75 | ```rust 76 | #[cfg(any( 77 | not(any(feature = "stage_one", feature = "stage_two")), 78 | all(feature = "stage_two", feature = "stage_one") 79 | ))] 80 | use constany_blank as constany; // This line is for grammar checkers that enable all feature / disable all feature. If you do not have a checker, you can delete those lines safely. 81 | #[cfg(all(feature = "stage_one", not(feature = "stage_two")))] 82 | use constany_stage_one as constany; 83 | #[cfg(all(feature = "stage_two", not(feature = "stage_one")))] 84 | use constany_stage_two as constany; 85 | #[constany::main_fn("function_evaled_at_compile_time")] 86 | fn main() { 87 | // Blah Blah Blah 88 | function_evaled_at_compile_time(); 89 | // Blah Blah Blah 90 | } 91 | #[constany::const_fn] 92 | fn function_evaled_at_compile_time() -> i32 { 93 | let mut a = 1; 94 | let b = 5; 95 | for _ in 0..b { 96 | a += 1; 97 | } 98 | a 99 | } 100 | ``` 101 | 102 | Make sure `main` function is marked with `constany::main_fn()` and the constant function list is inside the bracket. Otherwise the function will not be compiled to constant. 103 | 104 | ### Compile for binary application 105 | 106 | #### Compile manually (the long way) 107 | 108 | When you need to build the function, execute: 109 | 110 | ```bash 111 | $ cargo run --features stage_one 112 | $ cargo build --features stage_two // If you want to run the code instead, use `cargo run` 113 | ``` 114 | 115 | And your function will be interpreted as constant function. 116 | 117 | #### Compile using build script (the experimental way) 118 | 119 | You can add [our build script](build.rs) to your code folder. Please add it outside `src`, in the same folder as `Cargo.toml`: 120 | 121 | ``` 122 | |- Cargo.toml 123 | |- Cargo.lock 124 | |- src/ 125 | |- main.rs 126 | |- blah.rs 127 | |- build.rs // HERE!!! 128 | ``` 129 | ## Issues & Gotchas 130 | 131 | ### Multiple constant function 132 | 133 | Having multiple constant functions are also applicable, you just need to make sure every function you want to make constant are labeled with `const_fn` and the function name is inside `main_fn`: 134 | 135 | ```rust 136 | // --snip-- 137 | // Please look at previous example for this part 138 | // --snip-- 139 | #[constany::main_fn("function_evaled_at_compile_time", "function_evaled_at_compile_time_2")] 140 | fn main() { 141 | function_evaled_at_compile_time(); 142 | function_evaled_at_compile_time_2(); 143 | } 144 | #[constany::const_fn] 145 | fn function_evaled_at_compile_time() -> i32 { 146 | let mut a = 1; 147 | let b = 5; 148 | for _ in 0..b { 149 | a += 1; 150 | } 151 | a 152 | } 153 | #[constany::const_fn] 154 | fn function_evaled_at_compile_time_2() -> i32 { 155 | let mut a = 1; 156 | let b = 100; 157 | for _ in 0..b { 158 | a += 1; 159 | } 160 | a 161 | } 162 | ``` 163 | 164 | ### Function with non-primitive result 165 | 166 | Returning a non-primitive result is troublesome and prone to error. The most elegant way is to use `lazy_static` for stage one to avoid compiler warning, and use constant value function for stage two: 167 | 168 | ```rust 169 | #[cfg(feature = "stage_two")] 170 | const ABC: String = constant_function().to_string(); 171 | #[cfg(not(feature = "stage_two"))] 172 | lazy_static::lazy_static! { 173 | const ref ABC: String = constant_function().to_string(); 174 | } 175 | ``` 176 | However, this will not work for most of the non-primitive type because their constructor is unlikely to be `static`. 177 | 178 | There are two workaround for this: the `debug + pub` solution and `memop` solution. 179 | 180 | #### The Debug + Pub solution 181 | 182 | The `debug + pub` solution first use `debug` trait to print the structure, and use the `pub` trait to rebuild it. 183 | 184 | This solution can recreate the structure without `unsafe` code. However, this require the structure to derive `Debug`. 185 | 186 | Current implementation also require the structure to not have `paths`, such as `std::string::String` (if there are `::` in the identifier, it's likely that this solution will not work out). 187 | 188 | To use this solution, you can simply label `constany::const_fn` because this is the default solution for constany. 189 | 190 | #### The Memop solution 191 | 192 | The `memop` solution transmute the memory directly. 193 | This solution can rebuild any structure, but please note that this method is `unsafe` and very dangerous. 194 | 195 | The generated function will be `fn` instead of `const_fn` because memory allocation is not allowed in `const`, although the memory itself is hard-coded inside the function. 196 | 197 | To use this solution, you need to label target function as `constany::const_fn(memop)`: 198 | 199 | ```rust 200 | // --snip-- 201 | // Please look at previous example for this part 202 | // --snip-- 203 | #[constany::main_fn("function_evaled_at_compile_time")] 204 | fn main() { 205 | function_evaled_at_compile_time(); 206 | } 207 | #[constany::const_fn(memop)] 208 | fn function_evaled_at_compile_time() -> String { 209 | let mut a = 1; 210 | let b = 5; 211 | for _ in 0..b { 212 | a += 1; 213 | } 214 | a.to_string() 215 | } 216 | ``` 217 | 218 | Please note that if the function is returning a primitive type in rust, the memory operation will not be used regardless the `memop` flag. 219 | 220 | ### Make sure the returning value is hard-coded 221 | 222 | Constany has already make sure that the returning value is hard-coded into the function. However, if you want to have a double-safety precaution, you can add `force_const` flag to the function mark. This will make the result as a constant value declared outside the function, and the function is simply a wrapper to return that value. 223 | 224 | ```rust 225 | // --snip-- 226 | // Please look at previous example for this part 227 | // --snip-- 228 | #[constany::main_fn("function_evaled_at_compile_time")] 229 | fn main() { 230 | function_evaled_at_compile_time(); 231 | } 232 | #[constany::const_fn(memop, force_const)] 233 | fn function_evaled_at_compile_time() -> String { 234 | let mut a = 1; 235 | let b = 5; 236 | for _ in 0..b { 237 | a += 1; 238 | } 239 | a.to_string() 240 | } 241 | ``` 242 | 243 | ## Contributing 244 | 245 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 246 | --------------------------------------------------------------------------------