3 个不稳定版本
0.2.0 | 2020年8月27日 |
---|---|
0.1.1 | 2020年8月26日 |
0.1.0 | 2020年8月26日 |
#1140 in 开发工具
8KB
127 行
Constany:将任何 Rust 函数转换为常量函数
Constany 允许您从任何表达式构建常量(或至少是伪常量)函数
在 Rust 中,常量函数是一类可以在编译时被编译器解释的函数。常量函数有各种限制以确保它们可以在编译时评估。在大多数情况下,这些限制是有益的,因为它们可以防止误用。然而,有时这种使用是有意的
use std::collections::HashMap;
const DEFAULT_USER: HashMap<&'static str, u8> = HashMap::new(); // Error!
fn main() {}
或
fn main() {}
const fn add_one_to_six() -> u8 {
let mut a = 1;
for b in 1..7 {
a += b;
} // Error!
a
}
Constany 提供了一种绕过这些限制的方法。
为什么使用常量函数?
-
编译时评估:更快的运行时执行
-
较小的二进制大小(如果函数本身很大)
constany 的工作原理?
constany 使用一种解决方案:标记为 constany::const_fn
的函数和主函数将被编译两次。第一次,记录函数的值。第二次,将函数替换为值。
警告
-
在大多数情况下,constany 会导致意外的后果。请注意,由 constany 标记的函数将在编译期间执行,而不是在执行期间。
-
由 constany 生成的函数在技术上不等于
const fn
:constany 将函数的结果硬编码到二进制文件中,无论其上下文如何;const fn
只在从常量环境调用时产生静态结果。
用法
警告:库支持尚未实现。欢迎提交 PR。
从版本 0.2 开始,constany_stage_one
和 constany_stage_two
不再是可选依赖项。这不会影响构建的二进制文件大小。
使用 constany 比普通库复杂一些。
首先,您需要确保在 Cargo.toml
中有两个功能,并将 stage one
和 stage two
作为依赖项导入。
Cargo.toml:
[features]
stage_one = []
stage_two = []
[dependencies]
constany_stage_one = {version = "0.2"}
constany_stage_two = {version = "0.2"}
constany_blank = {version = "1"}
constany_blank
如果没有语法检查器,并且程序员不会忘记使用 --feature
标志编译代码,则不是必需的;它只是一个空白实现,用于避免编译器报错。
下一步是涉及 main.rs
main.rs:
#[cfg(any(
not(any(feature = "stage_one", feature = "stage_two")),
all(feature = "stage_two", feature = "stage_one")
))]
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.
#[cfg(all(feature = "stage_one", not(feature = "stage_two")))]
use constany_stage_one as constany;
#[cfg(all(feature = "stage_two", not(feature = "stage_one")))]
use constany_stage_two as constany;
#[constany::main_fn("function_evaled_at_compile_time")]
fn main() {
// Blah Blah Blah
function_evaled_at_compile_time();
// Blah Blah Blah
}
#[constany::const_fn]
fn function_evaled_at_compile_time() -> i32 {
let mut a = 1;
let b = 5;
for _ in 0..b {
a += 1;
}
a
}
确保main
函数被标记为constany::main_fn()
,并且常量函数列表在括号内。否则,函数将不会被编译成常量。
编译为二进制应用程序
手动编译(方法较长)
当您需要构建函数时,执行
$ cargo run --features stage_one
$ cargo build --features stage_two // If you want to run the code instead, use `cargo run`
您的函数将被解释为常量函数。
使用构建脚本编译(实验方法)
您可以将我们的构建脚本添加到您的代码文件夹中。请将其添加到src
外部,在Cargo.toml
相同的文件夹中
|- Cargo.toml
|- Cargo.lock
|- src/
|- main.rs
|- blah.rs
|- build.rs // HERE!!!
问题 & 注意事项
多个常量函数
具有多个常量函数也适用,您只需确保您想标记为常量的每个函数都带有const_fn
标记,并且函数名称在main_fn
中
// --snip--
// Please look at previous example for this part
// --snip--
#[constany::main_fn("function_evaled_at_compile_time", "function_evaled_at_compile_time_2")]
fn main() {
function_evaled_at_compile_time();
function_evaled_at_compile_time_2();
}
#[constany::const_fn]
fn function_evaled_at_compile_time() -> i32 {
let mut a = 1;
let b = 5;
for _ in 0..b {
a += 1;
}
a
}
#[constany::const_fn]
fn function_evaled_at_compile_time_2() -> i32 {
let mut a = 1;
let b = 100;
for _ in 0..b {
a += 1;
}
a
}
返回非原始结果的功能
返回非原始结果很麻烦且容易出错。最优雅的方法是使用lazy_static
进行第一阶段以避免编译器警告,并使用常量值函数进行第二阶段
#[cfg(feature = "stage_two")]
const ABC: String = constant_function().to_string();
#[cfg(not(feature = "stage_two"))]
lazy_static::lazy_static! {
const ref ABC: String = constant_function().to_string();
}
然而,这将对大多数非原始类型无效,因为它们的构造函数不太可能是static
。
有两个解决方案:debug + Pub解决方案和memop解决方案。
Debug + Pub解决方案
debug + Pub解决方案首先使用debug
特性和pub
特性来打印结构,并使用它们来重建它。
此解决方案可以重建结构而无需使用unsafe
代码。然而,这需要结构派生自Debug
。
当前实现还要求结构没有paths
,例如std::string::String
(如果标识符中有::
,则此解决方案可能无法正常工作)。
要使用此解决方案,您只需将constany::const_fn
标记即可,因为这是constany的默认解决方案。
Memop解决方案
Memop解决方案直接转储内存。此解决方案可以重建任何结构,但请注意,此方法是unsafe
且非常危险。
生成的函数将是fn
而不是const_fn
,因为在const
中不允许内存分配,尽管内存本身在函数中是硬编码的。
要使用此解决方案,您需要将目标函数标记为constany::const_fn(memop)
// --snip--
// Please look at previous example for this part
// --snip--
#[constany::main_fn("function_evaled_at_compile_time")]
fn main() {
function_evaled_at_compile_time();
}
#[constany::const_fn(memop)]
fn function_evaled_at_compile_time() -> String {
let mut a = 1;
let b = 5;
for _ in 0..b {
a += 1;
}
a.to_string()
}
请注意,如果函数返回的是Rust中的原始类型,则无论memop
标志如何,都不会使用内存操作。
确保返回值是硬编码的
Constany已经确保返回值被硬编码到函数中。但是,如果您想有双重安全预防措施,可以向函数标记添加force_const
标志。这将使结果成为一个在函数外部声明的常量值,而函数只是一个返回该值的包装器。
// --snip--
// Please look at previous example for this part
// --snip--
#[constany::main_fn("function_evaled_at_compile_time")]
fn main() {
function_evaled_at_compile_time();
}
#[constany::const_fn(memop, force_const)]
fn function_evaled_at_compile_time() -> String {
let mut a = 1;
let b = 5;
for _ in 0..b {
a += 1;
}
a.to_string()
}
贡献
欢迎提交拉取请求。对于重大变更,请先提交一个问题,讨论您想要进行的变更。
lib.rs
:
请参阅constany_stage_one
文档。此包用于constany构建的第二阶段,将基于第一阶段工件生成最终的常量函数。
依赖
~1.5MB
~37K SLoC