如何在Rust中使用非常量初始值设定项来初始化不可变的全局变量? [英] How to initialize immutable globals with non-const initializer in Rust?

查看:149
本文介绍了如何在Rust中使用非常量初始值设定项来初始化不可变的全局变量?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试获取一个仅在运行时初始化一次的变量.在C/C ++中, static 是我要寻找的关键字,但是在Rust中,它必须由常量初始化.

I am trying to get a variable that is only initialized once, at runtime. In C/C++ static would be the keyword that I would be looking for, yet in Rust, it must be initialized by a constant.

静态mut 是不安全的,我可以理解为什么,但是从概念上讲它并不能捕获我想要的,我想要一个不可变的变量.

static mut is unsafe, and I can see why, but it doesn't conceptually capture what I want, I want an immutable variable.

举一个tribonacci函数的简单例子:

Take this trivial example of a tribonacci function:

static sqrt_33_mul_3: f64 = 3.0 * 33.0f64.powf(0.5);

static tribonacci_constant: f64 = 1.0
+ (19.0 - sqrt_33_mul_3).powf(1.0 / 3.0)
+ (19.0 + sqrt_33_mul_3).powf(1.0 / 3.0);

fn tribonacci(n: f64) -> f64 {
    return (
        (tribonacci_constant / 3.0).powf(n)
        / (
            (4.0 / 3.0)
            * tribonacci_constant
            - (1.0 / 9.0)
            * tribonacci_constant.powf(2.0) - 1.0
        )
    ).round();
}

我希望函数外部的两个静态变量仅初始化一次,并且不要在每次运行该函数时都调用powf

I want the two static variables outside of the function to be initialized only once, and powf to not be called with every run of the function

我对Rust来说是个新手,不知道普通的,有经验的用户可能有什么常识.

I am incredibly new to Rust and do not know what may be common knowledge to the average, experienced user.

如果可以的话,这怎么可能呢?

Is this possible, if so, how can it be done?

推荐答案

如果 f64 :: powf 是const函数,则编译器应转换 3.0 * 33.0f64.powf(0.5)降到一个固定值.

If f64::powf was a const function then the compiler should convert things like 3.0 * 33.0f64.powf(0.5) down to a single fixed value.

虽然可以使用 lazy_static 解决此问题,但是使用lazy_statics背后有一定的成本,因为它们被设计为不仅支持简单的浮点常量.

While lazy_static can be used to solve this problem, there is a cost behind using lazy_statics, because they're designed to support more than just simple floating-point constants.

您可以通过使用Criterion对这两种实现进行基准测试来查看此费用:

You can see this cost by benchmarking the two implementations using Criterion:

pub mod ls {
    use lazy_static::lazy_static; // 1.4.0

    lazy_static! {
        //TODO: Should this be a pow(1.0/3.0)?
        pub static ref cbrt_33_mul_3: f64 = 3.0 * 33.0f64.powf(0.5);
        
        pub static ref tribonacci_constant: f64 = 1.0
        + (19.0 - *cbrt_33_mul_3).powf(1.0 / 3.0)
        + (19.0 + *cbrt_33_mul_3).powf(1.0 / 3.0);
    }

    pub fn tribonacci(n: f64) -> f64 {
        return (
            (*tribonacci_constant / 3.0).powf(n)
            / (
                (4.0 / 3.0)
                * *tribonacci_constant
                - (1.0 / 9.0)
                * tribonacci_constant.powf(2.0) - 1.0
            )
        ).round();
    }
}

pub mod hc {
    pub fn tribonacci(n: f64) -> f64 {
        let p = 1.839286755214161;
        let s = 0.3362281169949411;
        return (s * p.powf(n)).round();
    }
}

fn criterion_benchmark(c: &mut Criterion) {
    c.bench_function("trib 5.1 ls", |b| b.iter(|| ls::tribonacci(black_box(5.1))));
    c.bench_function("trib 5.1 hc", |b| b.iter(|| hc::tribonacci(black_box(5.1))));
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

成本很小,但是如果这在您的核心循环中,则可能会很大.在我的机器上,(删除不相关的行之后)我得到了

The cost is small, but may be significant if this is in your core loops. On my machine, I get (after removing unrelated lines)

trib 5.1 ls             time:   [47.946 ns 48.832 ns 49.796 ns]                         
trib 5.1 hc             time:   [38.828 ns 39.898 ns 41.266 ns]                         

这大约有20%的差异.

This is about a 20% difference.

如果您不喜欢在代码中包含硬编码的常量,则实际上可以在构建时使用 build.rs 脚本生成这些常量.

If you don't like having hardcoded constants in your code, you can actually generate these at build time using a build.rs script.

我完整的基准测试示例如下:

My complete example for benchmarking looks like this:

build.rs

use std::env;
use std::fs;
use std::path::Path;

fn main() {
    let out_dir = env::var_os("OUT_DIR").unwrap();
    let dest_path = Path::new(&out_dir).join("constants.rs");

    //TODO: Should this be a pow(1.0/3.0)?
    let cbrt_33_mul_3: f64 = 3.0 * 33.0f64.powf(0.5);
    
    let tribonacci_constant: f64 = 1.0 
        + (19.0 - cbrt_33_mul_3).powf(1.0 / 3.0)
        + (19.0 + cbrt_33_mul_3).powf(1.0 / 3.0);

    let p = tribonacci_constant / 3.0;
    let s = 1.0 / (
        (4.0 / 3.0)
        * tribonacci_constant
        - (1.0 / 9.0)
        * tribonacci_constant.powf(2.0) - 1.0
    );

    fs::write(
        &dest_path,
        format!("\
        pub mod tribonacci {{\n\
            pub const P: f64 = {:.32};\n\
            pub const S: f64 = {:.32};\n\
        }}\n", p, s)
    ).unwrap();
    println!("cargo:rerun-if-changed=build.rs");
}

src/lib.rs

pub mod constants {
    include!(concat!(env!("OUT_DIR"), "/constants.rs"));
}

pub mod ls {
    use lazy_static::lazy_static; // 1.4.0

    lazy_static! {
        //TODO: Should this be a pow(1.0/3.0)?
        pub static ref cbrt_33_mul_3: f64 = 3.0 * 33.0f64.powf(0.5);
        
        pub static ref tribonacci_constant: f64 = 1.0
        + (19.0 - *cbrt_33_mul_3).powf(1.0 / 3.0)
        + (19.0 + *cbrt_33_mul_3).powf(1.0 / 3.0);
    }

    pub fn tribonacci(n: f64) -> f64 {
        return (
            (*tribonacci_constant / 3.0).powf(n)
            / (
                (4.0 / 3.0)
                * *tribonacci_constant
                - (1.0 / 9.0)
                * tribonacci_constant.powf(2.0) - 1.0
            )
        ).round();
    }

}

pub mod hc {
    pub fn tribonacci(n: f64) -> f64 {
        let p = super::constants::tribonacci::P;
        let s = super::constants::tribonacci::S;
        return (s * p.powf(n)).round();
    }
}

benches/my_benchmark.rs

use criterion::{black_box, criterion_group, criterion_main, Criterion};
use rust_gen_const_vs_lazy_static::ls;
use rust_gen_const_vs_lazy_static::hc;

fn criterion_benchmark(c: &mut Criterion) {
    c.bench_function("trib 5.1 ls", |b| b.iter(|| ls::tribonacci(black_box(5.1))));
    c.bench_function("trib 5.1 hc", |b| b.iter(|| hc::tribonacci(black_box(5.1))));
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

Cargo.toml

[package]
name = "rust_gen_const_vs_lazy_static"
version = "0.1.0"
edition = "2018"

[dependencies]
"lazy_static" = "1.4.0"

[dev-dependencies]
criterion = "0.3"

[[bench]]
name = "my_benchmark"
harness = false

$ OUTDIR/constants.rs (生成)

pub mod tribonacci {
pub const P: f64 = 1.83928675521416096216853475198150;
pub const S: f64 = 0.33622811699494109527464047459944;
}


正如Dilshod Tadjibaev所建议的那样,使用proc-macros可以实现类似的结果,尽管在这种情况下它需要做更多的工作.这提供了与生成时完全相同的速度.


As suggested by Dilshod Tadjibaev it is possible to achieve a similar result using proc-macros, though it requires a little more work in this case. This gives exactly the same speed as build-time generation.

要进行设置,我为宏 trib_macros 创建了一个新的条板箱,因为proc-macros需要放在自己的条板箱中.这个新的箱子只包含两个文件 Cargo.toml src/lib.rs

To set this up I created a new crate for the macros trib_macros, as proc-macros need to be in their own crate. This new crate contained just two files Cargo.toml and src/lib.rs

Cargo.toml

[package]
name = "trib_macros"
version = "0.1.0"
edition = "2018"


[lib]
proc-macro = true

src/lib.rs

extern crate proc_macro;
use proc_macro::TokenStream;

#[proc_macro]
pub fn tp(_item: TokenStream) -> TokenStream {
    let cbrt_33_mul_3: f64 = 3.0 * 33.0f64.powf(0.5);
    
    let tribonacci_constant: f64 = 1.0 
        + (19.0 - cbrt_33_mul_3).powf(1.0 / 3.0)
        + (19.0 + cbrt_33_mul_3).powf(1.0 / 3.0);

    let p = tribonacci_constant / 3.0;
    format!("{}f64",p).parse().unwrap()
}

#[proc_macro]
pub fn ts(_item: TokenStream) -> TokenStream {
    let cbrt_33_mul_3: f64 = 3.0 * 33.0f64.powf(0.5);
    
    let tribonacci_constant: f64 = 1.0 
        + (19.0 - cbrt_33_mul_3).powf(1.0 / 3.0)
        + (19.0 + cbrt_33_mul_3).powf(1.0 / 3.0);

    let s = 1.0 / (
        (4.0 / 3.0)
        * tribonacci_constant
        - (1.0 / 9.0)
        * tribonacci_constant.powf(2.0) - 1.0
    );
    format!("{}f64",s).parse().unwrap()
}

然后,我们需要调整原始包装箱中的 Cargo.toml 才能将其插入.

Then we need to adjust the Cargo.toml of the original crate to pull this in.

[dependencies]
...
trib_macros = { path = "path/to/trib_macros" }

最后使用它是相对干净的:

And finally using it is relatively clean:

pub mod mc {
    use trib_macros::{ts,tp};

    pub fn tribonacci(n: f64) -> f64 {
        return (ts!() * tp!().powf(n)).round();
    }
}

绝对有一种更整洁的方法来输出float文字标记,但我找不到它.

There's definitely a neater way to output the float literal tokens, but I couldn't find it.

您可以在> https://github.com/mikeando/rust_code_gen_example

这篇关于如何在Rust中使用非常量初始值设定项来初始化不可变的全局变量?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆