2 个稳定版本

1.0.1 2020 年 8 月 26 日

#2698Rust 模式

AGPL-3.0-only

3KB

Constany:将任何 Rust 函数转换为常量函数

Stage One License Downloads

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_oneconstany_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