3 个不稳定版本

0.2.0 2021 年 7 月 6 日
0.1.1 2019 年 11 月 26 日
0.1.0 2019 年 5 月 19 日

Rust 模式 中排名 1761

Download history 77/week @ 2024-03-12 91/week @ 2024-03-19 100/week @ 2024-03-26 113/week @ 2024-04-02 40/week @ 2024-04-09 44/week @ 2024-04-16 93/week @ 2024-04-23 79/week @ 2024-04-30 73/week @ 2024-05-07 86/week @ 2024-05-14 209/week @ 2024-05-21 134/week @ 2024-05-28 146/week @ 2024-06-04 77/week @ 2024-06-11 83/week @ 2024-06-18 64/week @ 2024-06-25

每月下载量 389
17 包中使用(直接使用 10 个)

MIT 许可证

24KB
289 行代码(不包括注释)

numeric_literals

Build Status

numeric_literals 是一个 Rust 库,它提供过程式属性宏,用于将数字字面量替换为任意表达式。

虽然 Rust 的显式性通常是一件好事,但当编写泛型标量类型的数字代码时,它却是一个大问题。例如,考虑编写一个函数,该函数返回任何实现 T: num::Float 的类型的黄金比例。一个实现可能看起来如下所示。

extern crate num;
use num::Float;

fn golden_ratio<T: Float>() -> T {
    ( T::one() + T::sqrt(T::from(5).unwrap())) / T::from(2).unwrap()
}

对于这样一个简单的任务来说,这无疑是相当混乱的。使用 numeric_literals,我们可以改写为

use num::Float;
use numeric_literals::replace_numeric_literals;
#[replace_numeric_literals(T::from(literal).unwrap())]
fn golden_ratio<T: Float>() -> T {
   (1 + 5.sqrt()) / 2
}

上述两个代码段基本上执行相同的功能(除了使用 T::from(1) 而不是 T::one())。然而,在后者中,replace_numeric_literals 属性将任何数字字面量替换为表达式 T::from(literal).unwrap(),其中 literal 是每个单个字面量的占位符。

没有魔法:代码仍然明确说明了它对数字字面量做了什么。区别在于,我们可以为所有数字字面量声明一次这种行为。此外,我们将转换行为从需要字面量的地方移开,通过减少对确切类型明确性的噪音,提高了可读性。

浮点数和整数字面量替换

在替换数字字面量时的问题在于,无法区分用于索引等目的的字面量与属于数值计算的字面量。在上面的例子中,如果你还需要使用常量索引(如 array[0])来索引数组,宏会尝试将索引 0 转换为浮点类型,这显然会失败。幸运的是,在大多数情况下,这些例子会因为类型不匹配而无法编译。解决这个问题的可能方法之一是使用独立的宏 replace_float_literalsreplace_int_literals,这两个宏的工作方式完全相同,但分别只触发于浮点或整数字面量。下面是一个来自有限元代码的例子,它使用浮点字面量替换来改善通用代码中数值常量的可读性。

#[replace_float_literals(T::from_f64(literal).expect("Literal must fit in T"))]
pub fn assemble_element_mass<T>(quad: &Quad2d<T>) -> MatrixN<T, U8>
where
   T: RealField
{
    let phi = |alpha, beta, xi: &Vector2<T>| -(1.0 + alpha * xi[0]) * (1.0 + beta * xi[1]) / 4.0;
    let phi_grad = |alpha, beta, xi: &Vector2<T>| {
        Vector2::new(
            alpha * (1.0 + beta * xi[1]) / 4.0,
            beta * (1.0 + alpha * xi[0]) / 4.0,
        )
    };
    let alphas = [-1.0, 1.0, 1.0, -1.0];
    let betas = [-1.0, -1.0, 1.0, 1.0];

    // And so on...
}

一般情况下,应该 谨慎使用宏。建议将宏与使用字面量的代码区域保持接近,以避免让代码读者产生困惑。宏展开前的Rust代码通常不是有效的Rust代码(因为缺乏显式的类型转换),但没有属性上下文,代码仍然不清楚为什么它仍然可以编译。未来的一个选项是将属性仅应用于非常局部的、数值常数较多的代码块。然而,目前Rust不允许属性宏应用于块或单个表达式。

宏调用中的替换

默认情况下,这个crate的宏也会替换宏调用中的字面量。这使得以下代码可以编译

use num::Float;
use numeric_literals::replace_numeric_literals;

#[replace_numeric_literals(T::from(literal).unwrap())]
fn zeros<T: Float>(n: usize) -> Vec<T> {
    vec![0.0; n]
}

如果这种行为不想要,可以通过参数禁用在宏内部进行替换

#[replace_numeric_literals(T::from(literal).unwrap()), visit_macros = false]

带后缀的字面量

在Rust中,可以使用字面量后缀来区分字面量的类型。例如,表达式 1_f64.sqrt() 中的后缀 _f64 明确表示值 1 的类型是 f64。这个crate的宏也支持所有浮点数和整数后缀。例如

use num::Float;
use numeric_literals::replace_numeric_literals;

#[replace_numeric_literals(T::from(literal).unwrap())]
fn golden_ratio<T: Float>() -> T {
   (1.0_f64 + 5f32.sqrt()) / 2.0
}

许可证

此crate采用MIT许可证。有关详细信息,请参阅LICENSE。

依赖项

~1.5MB
~36K SLoC