#tuple #type #struct #alias #time-unit

typeables

Typeables:Rust 类型别名库。由 SixArm.com 提供。

7 个稳定版本

2.3.0 2022 年 3 月 29 日
2.2.0 2022 年 3 月 28 日
1.3.0 2021 年 4 月 13 日

#537Rust 模式


sita 中使用

MIT 或 Apache-2.0 或 GPL-2.0-only

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原语和标准,如字符串(使用strString)和数字(使用i64u64f64等)。

开销。

Typeables具有零或接近零的运行时开销。

  • 类型别名具有零运行时开销,因为类型别名是在编译时替换的。

  • 结构元组具有接近零的运行时开销,因为结构元组是一个具有字段的包装器。

打字。

Typeables故意具有冗长的语法。

  • 我们使用具有自动完成和自动建议的编辑器,因此打字既容易又快。

  • 我们喜欢使用长名称以提高底层清晰度。

  • typeables定义了许多类型别名和结构体元组。通常这些在开发过程中运行得很快,因为它们很简单。在生产过程中,这些甚至运行得更快,因为Rust编译器可以优化这些,并且消除任何不需要的。

Typeables的源代码不使用宏。

  • 总的来说,我们喜欢宏。

  • 然而,在实践中我们发现,宏似乎会干扰我们的某些工具。

  • 例如,宏似乎不与一些检查Typeables crate以进行自动完成和自动建议的编辑器兼容。

无运行时依赖