#units #linter #dimension #unit #dimensional-analysis #uom

nightly yaiouom

可扩展、强类型的度量单位,以自定义类型系统(基于F#的度量单位)实现,作为linters

4个版本

使用旧的Rust 2015

0.1.3 2018年4月4日
0.1.2 2018年4月3日
0.1.1 2018年4月3日
0.1.0 2018年4月3日

#823 in 科学

26每月下载量

MIT许可

24KB
322

静态检查的可扩展度量单位。

Yaiouom实现了一个度量单位的机制。它可以用来操作各种度量,包括物理/工程(m,kg,s,A,m * s ^ 1,...),货币(EUR,USD,...),统计数据(每桶美元,每灯泡工程师,每户每年美元,...)

尽管这不是Rust中第一个度量单位实现,但这是第一个既可扩展(你可以轻松添加新的基本单位),又是组合的(在不同的crates中定义的两个单位可以无缝交互),并且是类型安全的(如果尝试在不转换的情况下混合多个不兼容的度量单位,编译器将通知你)。然而,在使用此crate之前,请阅读以下解释。

安装

在你的Cargo.toml中添加依赖项

[dependencies]
yaiouom = "*" # Or some more specific version number

你还需要添加linter

$ cargo install +nightly cargo-yaoioum yaoioum-check

这假设你正在使用rustup。如果你不是,你应该首先按照以下方式配置SYSROOT

$ SYSROOT=`rustc --print sysroot` cargo install +nightly cargo-yaoioum yaoioum-check

用法

以下计算了一个以f64 m * s^-1为单位的速度

extern crate yaiouom;

use yaiouom::*;
use yaiouom::si::*;

fn get_speed(distance: Measure<f64, Meter>, duration: Measure<f64, Second>) -> Measure<f64, Mul<Meter, Inv<Second>>> {
    return (distance / duration).unify();
}

fn main() {
    let distance = Meter::new(100.);
    let duration = Second::new(25.);
    let speed = get_speed(distance, duration);
}

查看这个对unify()的调用?这是因为Rust的内置类型系统不足以意识到例如m * ss * m是同一件事。这个对unify()的调用指示Rust的内置类型系统将验证委托给刚刚安装的yaoioum-check

要构建你的代码,使用

$ cargo +nightly yaoioum build

除了像往常一样构建你的代码之外,这还会运行yaoioum-check提供的额外类型检查器。

请注意,乘法是交换律的,所以这相当于以下(我们更改了函数的结果)。

extern crate yaiouom;

use yaiouom::*;
use yaiouom::si::*;

fn get_speed(distance: Measure<f64, Meter>, duration: Measure<f64, Second>) -> Measure<f64, Mul<Inv<Second>, Meter>> {
    return (distance / duration).unify();
}

或者甚至相当于以下(我们更改了distance的类型)

extern crate yaiouom;

use yaiouom::*;
use yaiouom::si::*;

fn get_speed(distance: Measure<f64, Mul<Second, Mul<Meter, Inv<Second>>>, duration: Measure<f64, Second>) -> Measure<f64, Mul<Inv<Second>, Meter>> {
    return (distance / duration).unify();
}

或者,如果你希望更通用,

extern crate yaiouom;

use yaiouom::*;
use yaiouom::si::*;


trait Distance: Unit {}
trait Duration: Unit {}

// The following builds unsafely with Rust, then yaiouom-checker ensures the safety of `unify`.
fn get_speed_generic<A: Distance, B: Duration>(distance: Measure<f64, A>, duration: Measure<f64, B>) -> Measure<f64, Mul<A, Inv<B>>> {
    return (distance / duration).unify();
}

或者,如果您希望更加通用,

extern crate yaiouom;

use std;

use yaiouom::*;
use yaiouom::si::*;


trait Distance: Unit {}
trait Duration: Unit {}

// The following builds unsafely with Rust, then yaiouom-checker ensures the safety of `unify`.
fn get_speed_generic<A, B, T>(distance: Measure<T, A>, duration: Measure<T, B>) -> Measure<T::Output, Mul<A, Inv<B>>>
    where A: Distance,
          B: Duration,
          T: std::ops::Mul<T>
{
    return (distance / duration).unify();
}

您可以轻松地添加新的度量单位

struct Kilometer;
impl BaseUnit for Kilometer {
    /// This name is used mostly for error messages.
    const NAME: &'static str = "km";
}

fn get_speed_km(distance: Measure<f64, Kilometer>, duration: Measure<f64, Second>) -> Measure<f64, Mul<Kilometer, Inv<Second>>> {
    return (distance / duration).unify();
}

另一方面,如果您尝试编写一个滥用度量单位的程序,伴随的检查器将通知您您的错误

struct Kilometer;
impl BaseUnit for Kilometer {
    const NAME: &'static str = "km";
}

fn get_speed_bad_unify(distance: Measure<f64, Kilometer>, duration: Measure<f64, Second>) -> Measure<f64, Mul<Meter, Inv<Second>>> {
    return (distance / duration).unify();
}

// 69 | / fn get_speed_bad(distance: Measure<f64, Kilometer>, duration: Measure<f64, Second>) -> Measure<f64, Mul<Meter, Inv<Second>>> {
// 70 | |     return ((Dimensionless::new(1.) / duration) * distance ).unify();
//    | |            --------------------------------------------------------- in this unification
// 71 | | }
//    | |_^ While examining this function
//    |
//    = note: expected unit of measure: `Kilometer`
//               found unit of measure: `yaiouom::si::Meter`

或者,如果您出于某种原因决定在没有代码检查器的情况下运行代码的调试版本,

thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `km * s^-1`,
 right: `m * s^-1`', src/unit.rs:158:9

关于检查

在撰写本文时,Rust类型系统还不够强大,无法允许度量单位的可扩展、组合、类型安全表示。因此,实现度量单位的其他crate需要做出选择

  • 要么防止组合扩展;
  • 或者放弃类型安全。

这个crate采用不同的方法,通过将安全性检查委托给专门的检查器。这个检查器通过一种机制改进了Rust的类型系统,确保度量单位的使用是安全的。

如果您不使用检查器,最终得到的二进制文件将在调试构建中执行(慢速)动态单位检查,在优化构建中则没有单位检查,所以您应该没问题。但是,仍然,使用代码检查器确保您永远不会遇到此类恐慌是一个好主意:)

它是如何工作的

检查器非常简单。

它在常规类型推理/检查之后检查每个函数。每次它看到对 Measure::<T, U: Unit>::unify<V: Unit> 的调用时,它会检查 U 是否实际上等同于 V。这就是它将 A/A 简化为 Dimensionless 的地方,它接受 A * B == B * A(如果您好奇,单位语法是一个阿贝尔群)。

如果在所有简化、交换等之后,它发现 UV 不兼容,它将产生一个类型错误。

值的表示

不同的值有不同的规则。许多是f32或f64,但货币计算等需要使用有理数或定点算术。一些电气测量实际上是复数值。统计学可能使用整数值来表示人口等。

因此,yaiouom没有硬编码特定值的表示。具有单位的值是一个 Measure<T, U: Unit>,其中 T 可以是任何类型的数字或类似数字的值。

限制

如上所述,

请使用伴随的代码检查器!另外,请参阅 unify 的文档。

这个crate试图严格保持最小化。

单位转换是一件复杂的事情。我们并不试图解决这个问题。

一些值不能相乘或相除(例如,°C,°F,pH,dB)。我们并不试图区分可以乘除和不能乘除的单位,尽管这可能在未来的版本中发生。

一些值在相减时有不同的定义。例如,两个日期之差以秒为单位是一个持续时间。两个°C温度之差可能是一个可以乘除的值。我们并不试图区分这些。

致谢

虽然这种改进类型比原始类型简单得多(也有一点局限),但最初它被设计成F#类型系统的基本直译。然后它逐渐发展起来了 :)

依赖项

~615KB
~11K SLoC