7 个稳定版本
2.3.0 | 2022 年 3 月 29 日 |
---|---|
2.2.0 | 2022 年 3 月 28 日 |
1.3.0 | 2021 年 4 月 13 日 |
#537 在 Rust 模式
在 sita 中使用
210KB
2.5K SLoC
Typeables:Rust 类型别名和结构元组库
Typeables 是一个 Rust 库,用于语义类型,如单元类型(例如长度单位米,时间单位秒)、内容类型(例如电子邮件地址,电话号码)、区域类型(例如“en”代表英语,“zh”代表中文)等。
- 科学单位
- 安培
- 贝可勒尔
- 坎德拉
- 摄氏度
- 法拉第
- 克
- 格雷
- 赫兹
- 亨利
- 焦耳
- 卡塔
- 开尔文
- 千克
- 升
- 流明
- 勒克斯
- 米
- 米^2
- 米^3
- 米/秒
- 摩尔
- 欧姆
- 帕斯卡
- 弧度
- 秒
- 西门子
- 西弗
- 球面度
- 特斯拉
- 伏特
- 瓦特
- 韦伯
- 时间单位
- 年
- 月
- 周
- 天
- 小时
- 分钟
- 秒
- 时间段
- 公元年
- 年月
- 年周
- 月周
- 年日
- 月日
- 周内日
- 日小时
- 时分钟
- 分钟秒
- 货币
- 货币名称
- 货币符号
- 电子邮件地址
- 电子邮件地址
- 电子邮件地址地址
- 电子邮件地址名称
- 地理位置
- 纬度
- 经度
- 海拔
- 高程
- 开放位置代码
- what_freewords_code
- 区间
- 单位区间
- 双重区间
- 区域
- 区域代码
- 区域语言代码
- 区域国家代码
- 区域区域代码
- 区域脚本代码
- 区域变体代码
- 本地化
- 小数分隔符
- 分组分隔符
- 引号开始分隔符
- 引号结束分隔符
- 媒体类型
- 媒体类型代码
- 媒体类型超类型
- 媒体类型子类型
- 媒体类型后缀
- 媒体类型参数
- 媒体类型树
- 电话 e164
- 电话 e164 文本
- 电话 e164 国家代码
- 电话 e164 国内目的地代码
- 电话 e164 订户号码
- 电话 e164 群组识别代码
- 电话 e164 测试识别代码
- 语法
- 形容词
- 副词
- 名词
- 代词
- 动词
- 文本格式
- HTML 文本
- JSON 文本
- Markdown 文本
- YAML 文本
- XML 文本
- 内容
- 全球位置号码
- 国际工业分类修订版 4 代码
- 国际工业分类修订4名称
- 法人实体标识码
- 增值税识别号
- 日期时间
- 日期时间
- 日期
- 时间
- 时间偏移
- 时区
简介
Typeables基于Rust的"新类型"模式。它使用Rust结构元组作为其他类型(或类型)的包装器,以提供封装。
示例
pub struct MetreAsF64(pub f64); // This is a "New Type" struct tuple.
let length = MetreAsF64(1.2); // 1.2 metres as floating-point 64-bit.
Typeables可以帮助你编写更清晰、更强的代码,因为你可以对变量的类型、函数的输入和输出更加精确。
计算矩形面积的示例
pub struct MetreAsF64(pub f64); // Metre which is for distance.
pub struct Metre2AsF64(pub f64); // Metre^2 which is for area.
fn area(length: MetreAsF64, width: MetreAsF64) -> Metre2AsF64 {
Metre2AsF64(length.0 * width.0)
}
Typeables可以帮助你创建更好的领域驱动设计,更强的编译时检查,以及更清晰的运行时诊断。
什么是结构元组?
结构元组类似于对其他类型(如)的包装器
pub struct Year(pub i16);
结构元组可以让你代码更安全,因为它提供了封装
# pub struct Year(pub i16);
let x = Year(2022);
什么是类型别名?
类型别名类似于其他类型的昵称,如
pub type Year = i16;
类型别名可以使你的代码更清晰,因为它表达了你的意图
# pub type Year = i16;
let x: Year = 2022;
Typeables的功能是什么?
Typeables提供许多概念类型,每个都作为结构元组和类型别名实现
示例变量
use typeables::year::*;
let x = YearAsStructI16(2022); // Year as struct tuple
示例函数
use typeables::year::*;
fn f(x: YearAsStructI16) { // Year as struct tuple
println!("Year {}", x.0) // Use the zero field
}
如何使用Typeables重构代码?
Typeables帮助你从弱类型代码重构到强类型代码。
假设你从典型的代码开始
fn f(x: i16) {
println!("{}", x)
}
fn main() {
let x = 2022;
f(x)
}
步骤1. 将其重构为Typeables类型别名。这是注释。
use typeables::year::*;
fn f(x: YearAsTypeI16) {
println!("{}", x)
}
fn main() {
let x = 1 as YearAsTypeI16;
f(x)
}
步骤2. 将其重构为Typealias结构元组。这是封装。
use typeables::year::*;
fn f(x: YearAsStructI16) {
println!("{}", x.0)
}
fn main() {
let x = YearAsStructI16(2022);
f(x)
}
语义
语义示例
日历示例
# use typeables::year::*;
# use typeables::month::*;
let year = YearAsStructI16(2022);
let month = MonthAsStructI8(12);
纽约市格兰特中央车站的地理位置示例
# use typeables::{latitude::*, longitude::*};
let latitude = LatitudeAsDecimalDegreeAsStructF32(40.75);
let longitude = LongitudeAsDecimalDegreeAsStructF32(-73.97);
美国宇航局火星毅力号漫游车发射的日期时间格式示例
# use typeables::datetime::*;
let date_stamp = DateAsYYYYXMMXDDAsStructString(String::from("2020-07-30")); // Year 2020 on July 30th
let time_stamp = TimeAsHHXMMXSSAsStructString(String::from("07:50:00")); // 7:50 in the morning
let offset_stamp = TimeOffsetAsXHHXMMAsStructString(String::from("-05:00")); // 5 hours ahead of UTC
为什么使用语义名称?
当你使用语义名称,例如清晰的描述和有意义的命名约定时,你帮助开发者理解你的代码,帮助编译器提供可靠性,并帮助工具提供可检查性。
假设你的代码有这个函数
fn f(year: i16, month: i16) {
println!("Year {} Month {}", year, month)
}
开发者可以这样使用你的代码
# use typeables::{year_as_common_era::*, month_of_year::*};
# fn f(year: i16, month: i16) {
# println!("Year {} Month {}", year, month)
# }
let year = 2022;
let month = 12;
f(year, month); // right
// f(month, year); // wrong, yet will compile and be a bug
你可以通过添加类型别名使你的代码更清晰
# use typeables::{year_as_common_era::*, month_of_year::*};
fn f(year: YearAsTypeI16, month: MonthAsTypeI16) {
println!("Year {} Month {}", year, month)
}
你可以通过使用结构元组使你的代码更强大
# use typeables::{year_as_common_era::*, month_of_year::*};
fn f(year: YearAsStructI16, month: MonthAsStructI16) {
println!("Year {} Month {}", year.0, month.0)
}
开发者可以这样使用你的代码
# use typeables::{year_as_common_era::*, month_of_year::*};
# fn f(year: YearAsStructI16, month: MonthAsStructI16) {
# println!("Year {} Month {}", year.0, month.0)
# }
let year = YearAsStructI16(2022);
let month = MonthAsStructI16(12);
f(year, month); // right
// f(month, year); // wrong and won't compile
为什么使用语义名称、表示名称、单位名称和实现名称?
假设你正在编写一个飞机应用程序。
你想要跟踪
-
飞机的高度。
-
表示为“地面以上高度(AGL)”例如飞机起飞或降落时飞机相对于跑道的海拔高度,或“平均海平面(MSL)”例如飞机在全球巡航飞行时的海拔高度。
-
测量单位为“米”这是国际单位制,或为“英尺”这是美国单位制。
-
实现为16位有符号整数,因为某些罕见地区(如加利福尼亚的死亡谷)的高度可能是负数,并且你的应用程序可能需要与需要16位有符号整数的旧代码集成。
你可以使用这种命名约定
-
语义名称“高度”
-
表示名称“地面以上”或“平均海平面”
-
单位名称“米”或“英尺”
-
原始名称“I16”。
代码如下所示
# use typeables::altitude::*;
pub struct AltitudeAsAboveGroundLevelAsMetreAsStructI16(pub i16);
pub struct AltitudeAsAboveGroundLevelAsFootAsStructI16(pub i16);
pub struct AltitudeAsMeanSeaLevelAsMetreAsStructI16(pub i16);
pub struct AltitudeAsMeanSeaLevelAsFootAsStructI16(pub i16);
假设你的应用程序还需要跟踪
-
机场高度。
-
表示为“地面以上高度(AGL)”例如机场建筑相对于机场跑道的海拔高度,或“平均海平面(MSG)”例如机场跑道的全球海拔高度。
-
等等。
代码如下所示
# use typeables::elevation::*;
pub struct ElevationAsAboveGroundLevelAsMetreAsStructI16(pub i16);
pub struct ElevationAsAboveGroundLevelAsFootAsStructI16(pub i16);
pub struct ElevationAsMeanSeaLevelAsMetreAsStructI16(pub i16);
pub struct ElevationAsMeanSeaLevelAsFootAsStructI16(pub i16);
命名约定清晰明了,完全描述性
-
开发者可以更好地理解你的代码及其使用方式。
-
编译器可以提供更强的编译时保证。
-
调试器可以提供更清晰的运行时诊断。
-
编辑器可以提供更好的自动完成和自动建议。
请使用单词而不是缩写。
语义名称的示例。
-
请使用“纬度”而不是“Lat”。
-
请使用“经度”而不是“Lon”、“Lng”或“Long”。
表示名称的示例。
-
请使用“十进制度”而不是“DD”。
-
请使用“度分秒”而不是“DMS”。
单位名称的示例。
-
请使用“米”而不是“M”。
-
请使用“秒”而不是“S”。
实现名称的示例。
-
请使用“TypeString”而不是“TS”。
-
请使用“StructString”而不是“SS”。
优先使用单数而不是复数。
表示名称的示例。
-
请使用“十进制度”而不是“十进度”。
-
请使用“度分秒”而不是“度分秒”。
单位名称的示例。
-
请使用“米”而不是“米”。
-
请使用“秒”而不是“秒”。
命名规范。
结构元组的命名规范。
pub struct FooAsStructI8(pub i8);
pub struct FooAsStructI16(pub i16);
pub struct FooAsStructI32(pub i32);
pub struct FooAsStructI64(pub i64);
pub struct FooAsStructI128(pub i128);
pub struct FooAsStructISize(pub isize);
pub struct FooAsStructU8(pub u8);
pub struct FooAsStructU16(pub u16);
pub struct FooAsStructU32(pub u32);
pub struct FooAsStructU64(pub u64);
pub struct FooAsStructU128(pub u128);
pub struct FooAsStructUSize(pub usize);
pub struct FooAsStructF32(pub f32);
pub struct FooAsStructF64(pub f64);
pub struct FooAsStructStr(&'static String);
pub struct FooAsStructString(pub String);
类型别名的命名规范。
pub type FooAsTypeI8 = i8;
pub type FooAsTypeI16 = i16;
pub type FooAsTypeI32 = i32;
pub type FooAsTypeI64 = i64;
pub type FooAsTypeI128 = i128;
pub type FooAsTypeISize = isize;
pub type FooAsTypeU8 = u8;
pub type FooAsTypeU16 = u16;
pub type FooAsTypeU32 = u32;
pub type FooAsTypeU64 = u64;
pub type FooAsTypeU128 = u128;
pub type FooAsTypeUSize = usize;
pub type FooAsTypeF32 = f32;
pub type FooAsTypeF64 = f64;
pub type FooAsTypeStr = str;
pub type FooAsTypeString = String;
比较。
我们建议您查看Rust crate uom
(单位度量)和Rust书籍中newtype
模式的示例。
与uom的比较。
总的来说。
-
uom倾向于高级工作,例如自动归一化和转换。
-
Typeables倾向于底层工作,例如精确表示和原语。
量、单位与原语。
-
uom故意倾向于处理概念量(长度、质量、时间等),而不是度量单位(米、克、秒等)和实现原语(pub i8、u16、f32等)。
-
Typeables倾向于处理显式的度量单位和显式的实现原语。当您需要“长度”的概念、单位“米”和原语“f32”时,您将编写“LengthAsMetreAsTypeF32”。
归一化与精确度。
-
uom故意将值归一化为其基本单位,例如将1克归一化为0.001千克,并故意放弃表示能力(由于不精确的转换)和精度能力(由于位限制)。
-
Typeables倾向于精确性,永不归一化。当您需要“质量”的概念、单位“克”和128位无符号整数精度的原语“u128”时,您将编写“GramAsTypeI128”。
与Rust“新类型习语”或“新类型模式”的比较。
总的来说。
-
Rust的“新类型习语”或“新类型模式”正是Typeables使用结构元组所做的事情。我们非常喜欢这个习语。
-
Typeables还提供了类型别名。在实践中,我们发现这是帮助大型代码库的专业开发者的重要方式,因为开发者可以将类型别名作为对开发者和工具的提示逐步引入,然后稍后可以逐步引入结构元组。
自行实现与使用Typeables crate。
-
您当然可以自行实现新类型模式,并可以使用自己的类型名称,甚至可以使用Typeables的类型名称。
-
Typeables crate很有用,因为它提供了一系列定义,因此您可以使用此crate,并获得所有类型的好处,同时您的工具可以使用crate信息,例如用于编辑器的自动完成和自动建议。
实现。
类型别名都是针对Rust原语和标准,如字符串(使用str
和String
)和数字(使用i64
、u64
、f64
等)。
开销。
Typeables具有零或接近零的运行时开销。
-
类型别名具有零运行时开销,因为类型别名是在编译时替换的。
-
结构元组具有接近零的运行时开销,因为结构元组是一个具有字段的包装器。
打字。
Typeables故意具有冗长的语法。
-
我们使用具有自动完成和自动建议的编辑器,因此打字既容易又快。
-
我们喜欢使用长名称以提高底层清晰度。
-
typeables定义了许多类型别名和结构体元组。通常这些在开发过程中运行得很快,因为它们很简单。在生产过程中,这些甚至运行得更快,因为Rust编译器可以优化这些,并且消除任何不需要的。
宏
Typeables的源代码不使用宏。
-
总的来说,我们喜欢宏。
-
然而,在实践中我们发现,宏似乎会干扰我们的某些工具。
-
例如,宏似乎不与一些检查Typeables crate以进行自动完成和自动建议的编辑器兼容。