2 个稳定版本
1.0.1 | 2020 年 8 月 26 日 |
---|
#2698 在 Rust 模式
3KB
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
中有两个功能,并将 阶段一
和 阶段二
作为依赖项导入。
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
标志编译代码的情况下不是必需的;它只是 constany 宏的空实现,以避免编译器报错。
下一步是涉及 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
~34K SLoC