Typeables: Rust crate of type aliases and struct tuples

Typeables is a Rust crate of semantic types, such as unit types (e.g. meter for length, second for time), content types (e.g. email address, phone number), media types (e.g. JPEG image, MPEG movie), locale types (e.g. "en" for English, "zh" for Chinese), etc.

Units:

Calendaring:

Geolocation:

Content:

Grammar:

Introduction

Typeables is based on the Rust pattern of "New Type". This uses a Rust struct tuple as a wrapper of another type (or types), in order to provide encapsulation.

Example:

```rust pub struct MetreAsF64(pub f64); // This is a "New Type" struct tuple.

let length = MetreAsF64(1.2); // 1.2 meters as floating-point 64-bit. ```

Typeables helps you write clearer code and stronger code, because you can be more-precise about your variable types and your function inputs and outputs.

Example to calculate rectangular area:

```rust 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 helps you create better domain driven design, stronger compile-time checking, and crisper run-time diagnostics.

What is a struct tuple?

A struct tuple is akin to a wrapper for another type such as:

rust pub struct Year(pub i16);

A struct tuple can make your code safer because it provides encapsulation:

```rust

pub struct Year(pub i16);

let x = Year(2022); ```

What is a type alias?

A type alias is akin to a nickname for another type such as:

rust pub type Year = i16;

A type alias can make your code clearer because it expresses your intent:

```rust

pub type Year = i16;

let x: Year = 2022; ```

What does Typeables do?

Typeables provides many concept types, each implemented as a struct tuple and a type alias, and

Example variable:

```rust use ::typeables::year::*;

let x = YearAsStructI16(2022); // Year as struct tuple ```

Example function:

```rust use ::typeables::year::*;

fn f(x: YearAsStructI16) { // Year as struct tuple println!("Year {}", x.0) // Use the zero field } ```

How do I refactor code to use Typeables?

Typeables helps you refactor from weaker-type code to stronger-type code.

Suppose you start with typical code:

```rust fn f(x: i16) { println!("{}", x) }

fn main() { let x = 2022; f(x) } ```

Step 1. Refactor to a Typeables type alias. This is annotation.

```rust use ::typeables::year::*;

fn f(x: YearAsTypeI16) { println!("{}", x) }

fn main() { let x = 1 as YearAsTypeI16; f(x) } ```

Step 2. Refactor to a Typealias struct tuple. This is encapsulation.

```rust use ::typeables::year::*;

fn f(x: YearAsStructI16) { println!("{}", x.0) }

fn main() { let x = YearAsStructI16(2022); f(x) } ```

Semantics

Semantic examples

Calendar examples:

```rust

use ::typeables::year::*;

use ::typeables::month::*;

let year = YearAsStructI16(2022); let month = MonthAsStructI8(12); ```

Geolocation examples of New York City Grand Central Terminal:

```rust

use ::typeables::{latitude::, longitude::};

let latitude = LatitudeAsDecimalDegreeAsStructF32(40.75); let longitude = LongitudeAsDecimalDegreeAsStructF32(-73.97); ```

Date-time format examples of the NASA launch of the Mars Perseverance Rover:

```rust

use ::typeables::datetime::*;

let datestamp = DateAsYYYYXMMXDDAsStructString(String::from("2020-07-30")); // Year 2020 on July 30th let timestamp = TimeAsHHXMMXSSAsStructString(String::from("07:50:00")); // 7:50 in the morning let offset_stamp = TimeOffsetAsXHHXMMAsStructString(String::from("-05:00")); // 5 hours ahead of UTC ```

Why use semantic names?

When you use semantic names, such as clear descriptions and purposeful naming conventions, then you help developers understand your code, and help compilers provide reliability, and help tools provide inspectability.

Suppose your code has this function:

rust fn f(year: i16, month: i16) { println!("Year {} Month {}", year, month) }

A developer can use your code like this:

```rust

use ::typeables::{year::, month::};

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 ```

You can make your code clearer by adding a type alias:

```rust

use ::typeables::{year::, month::};

fn f(year: YearAsTypeI16, month: MonthAsTypeI16) { println!("Year {} Month {}", year, month) } ```

You can make your code stronger by using a struct tuple:

```rust

use ::typeables::{year::, month::};

fn f(year: YearAsStructI16, month: MonthAsStructI16) { println!("Year {} Month {}", year.0, month.0) } ```

A developer can use your code like this:

```rust

use ::typeables::{year::, month::};

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 ```

Why use semantic names, representation names, unit names, and implementation names?

Suppose you're writing an application for aircraft.

You want to keep track of:

You can use this naming convention:

The code looks like this:

```rust

use ::typeables::altitude::*;

pub struct AltitudeAsAboveGroundLevelAsMetreAsStructI16(pub i16); pub struct AltitudeAsAboveGroundLevelAsFootAsStructI16(pub i16); pub struct AltitudeAsMeanSeaLevelAsMetreAsStructI16(pub i16); pub struct AltitudeAsMeanSeaLevelAsFootAsStructI16(pub i16); ```

Suppose your app also needs to keep track of:

The code looks like this:

```rust

use ::typeables::elevation::*;

pub struct ElevationAsAboveGroundLevelAsMetreAsStructI16(pub i16); pub struct ElevationAsAboveGroundLevelAsFootAsStructI16(pub i16); pub struct ElevationAsMeanSeaLevelAsMetreAsStructI16(pub i16); pub struct ElevationAsMeanSeaLevelAsFootAsStructI16(pub i16); ```

The naming convention is crystal clear and fully descriptive:

Use words rather than abbreviations

Examples of semantic names:

Examples of representation names:

Examples of unit names:

Examples of implementation names:

Prefer singular over plural

Examples of representation names:

Examples of unit names:

Naming conventions

Naming convention for struct tuples:

```rust 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); ```

Naming convention for type aliass:

```rust 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; ```

Comparisons

We recommend looking at the Rust crate uom (unit of measure) and the Rust book examples of the newtype pattern.

Comparison with uom

Broadly speaking:

Quantities v. units v. primitives:

Normalization v. exactness:

Comparison with Rust "New Type Idiom" a.k.a. "New Type Pattern"

Broadly speaking:

Roll your own versus using Typeables crate:

Implementation

The type aliases are all for Rust primitives and standards such as strings (using str and String) and numbers (using i64, u64, f64, et al.).

Overhead

Typeables has zero or near-zero runtime overhead:

Typing

Typeables is deliberately verbose.

Macros

The Typeables source code does not use macros.