3 个不稳定版本

0.2.0 2020 年 8 月 27 日
0.1.1 2020 年 8 月 26 日
0.1.0 2020 年 8 月 26 日

开发工具 中排名 #2219

AGPL-3.0-only

12KB
123

Constany 是一个 Rust 宏,允许任何函数返回常量结果。

Rust 中的常量函数是一组在编译时评估其结果的函数。它可以显著减少生成的二进制文件大小并提高性能。然而,由于技术和逻辑限制,某些表达式不能作为常量函数进行评估。例如

fn main() {
   println!("{}", add_one_to_six());
}
const fn add_one_to_six() -> String {
   let mut a = 1;
   for b in 1..7 { // error[E0744]: `for` is not allowed in a `const fn`
       a += b;
   }
   return a.to_string();
}

Constany 使用一种变通方法:通过使用 Constany,函数和主函数将被编译两次。函数的值将在第一次编译时评估,然后在第二次编译时将值封装为常量函数。

Cargo.toml:

[features]
stage_one = ["constany_stage_one"]
stage_two = ["constany_stage_two"]
[dependencies]
constany_stage_one = {version = "0.1", optional = true}
constany_stage_two = {version = "0.1", optional = true}
constany_blank = {version = "1"}

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() {
    println!("Hello, world!");
    function_evaled_at_compile_time();
}
#[constany::const_fn]
fn function_evaled_at_compile_time() -> i32 {
    let mut a = 1;
    let b = 5;
    for _ in 0..b {
        a += 1; // For loop is not allowed in `const fn`
    }
    a
}

当你需要构建函数时,这样做

$ cargo run --featues stage_one
$ cargo build --features stage_two // If you want to run the command instead, use `cargo run`

并且你的函数将被解释为常量函数。

多个常量函数

具有多个常量函数也适用,你只需确保你想设置为常量的每个函数都标记为 const_fn,并且函数名称位于 main_fn

#[cfg(any(
    not(any(feature = "stage_one", feature = "stage_two")),
    all(feature = "stage_two", feature = "stage_one")
))]
use constany_blank as constany;
#[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", "function_evaled_at_compile_time")]
fn main() {
    println!("Hello, world!");
    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
}

具有非原始结果的函数

返回非原始结果(可能是 structenum)的函数既麻烦又容易出错。最优雅的方法是在第一阶段使用 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 trait打印结构,然后使用pub trait重建它。这种解决方案可以不使用unsafe代码重建结构。然而,这要求结构实现Debug。当前的实现还要求结构没有paths,例如std::string::String(如果标识符中有::,则此解决方案可能无法正常工作)。

memop解决方案直接转换内存。这种解决方案可以重建任何结构,但请注意,这种方法是unsafe且非常危险的。生成的函数将是fn而不是const_fn,因为在const中不允许内存分配,尽管内存本身在函数中是硬编码的。

依赖项

~1.5MB
~36K SLoC