1 个不稳定版本
使用旧的 Rust 2015
0.1.0 | 2018年4月4日 |
---|
#401 in 科学
21KB
391 行
静态检查、可扩展的量度单位。
Yaiouom 实现了一种量度单位机制。它可以用来操作各种度量,包括物理/工程(m,kg,s,A,m * s ^ 1,...),货币(EUR,USD,...),统计学(每桶美元,每盏灯泡的工程师,每户每年美元,...)
虽然这不是 Rust 中第一个实现量度单位的库,但这是第一个既可扩展(你可以轻松添加新的基本单位),又可组合(在不同的 crate 中定义的两个单位可以无障碍地交互)且类型安全的(编译器会在你尝试在不进行转换的情况下混合几个不兼容的量度单位时通知你)。然而,在使用此 crate 之前,请阅读以下说明。
安装
在你的 Cargo.toml 中添加一个依赖项
[dependencies]
yaiouom = "*" # Or some more specific version number
你还想添加一个检查器
$ 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 * s
与 s * 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
(如果你好奇,单位的语法是一个阿贝尔群)。
如果在所有简化、交换等之后发现 U
和 V
不兼容,它会产生一个类型错误。
值的表示
不同的值有不同的规则。许多是 f32 或 f64,但货币计算等需要使用有理数或定点算术。一些电学度量实际上是复数值。统计学可能使用整数值表示人口等。
因此,yaiouom 不对值的特定表示进行硬编码。具有单位的值是 Measure<T, U: Unit>
,其中 T
可以是任何类型的数字或类似数字的值。
限制
如上所述,
请使用配套的代码检查器!另外,请参阅
unify
的文档。
这个crate试图尽可能保持最小化。
单位转换是一件复杂的事情。我们不试图解决这个问题。
某些值不能相乘或相除(例如,°C,°F,pH,dB)。我们不试图区分可以相乘/除的单位与可以相乘/除的单位,尽管这可能在未来的版本中发生。
某些值在相减时有不同的定义。例如,两个日期的差值是秒的持续时间。两个 °C 温度的差值可能是一个可以相乘或相除的值。我们不试图区分这些。
致谢
虽然这种精炼类型比原始类型简单得多(也有限制),但它最初旨在是 F# 类型系统的一个更或更直接的重构。然后它就演变了:)