#measure #system #checking #unit #unify #yaiouom #units-of-measure

nightly app yoric/yaiouom-checker

对Rust类型系统进行单元检查的原型扩展

1个不稳定版本

使用旧的Rust 2015

0.1.0 2018年4月3日

#52 in #measure

98 星 & 7 关注者

18KB
395 行代码(不包括注释)

单位。

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

虽然这不是Rust中第一个实现单位的版本,但这是第一个既可扩展(您可以轻松添加新的基本单位),又可组合(在两个不同crate中定义的两个单位可以无障碍地交互)且类型安全的版本(编译器将在您尝试混合多个不兼容的单位之前通知您)。然而,在使用此crate之前,请阅读以下说明。

示例

以下计算f64 m * s^-1的速度

extern crate yaiouom;

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

// The following builds unsafely with Rust, then yaiouom-checker ensures the safety of `unify`.
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的调用感兴趣,您应该真正查看unify 的文档 :)

请注意,乘法是可交换的,因此这等价于以下(我们改变了函数的结果)。

extern crate yaiouom;

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

// The following builds unsafely with Rust, then yaiouom-checker ensures the safety of `unify`.
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::*;

// The following builds unsafely with Rust, then yaiouom-checker ensures the safety of `unify`.
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 {
    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();
}

另一方面,如果您尝试编写一个滥用单位的程序,配套的 linter 将会通知您的错误

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`

或者,如果您出于某种原因决定在没有linter的情况下运行代码,

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

统一和配套的linter

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

  • 或者防止可组合的可扩展性;
  • 或者放弃类型安全。

这个crate采用不同的方法,通过委托安全检查到一个专门的检查器 yaiouom-checker。这个检查器通过一种机制扩展了Rust的类型系统,确保测量单位被安全使用。

如果你不使用这个检查器,你的二进制程序在调试构建中会执行(慢速的)动态单位检查,而在优化构建中则不进行单位检查。

这个代码检查器可以确保你永远不会遇到这种动态崩溃。

你真的应该使用这个配套的代码检查器:)。另外,请参阅unify的文档。

值的表示

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

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

局限性

如上所述,

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

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

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

某些值不能进行乘法或除法运算(例如,°C、°F、pH、dB)。我们并不试图区分可以乘除和不能乘除的单位,尽管这可能在未来的版本中实现。

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

致谢

虽然这个精炼类型比原始类型简单得多(而且限制更多),但它受到了F#类型系统的强烈启发。

无运行时依赖