#编译 #函数 #静态 #大小 #

constany_stage_two

将任何函数转换为常量

3 个不稳定版本

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

#1140 in 开发工具

AGPL-3.0-only

8KB
127

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 中有两个功能,并将 stage onestage 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