Zero-runtime-cost type information
The key insight: If you can encode invariants in the type system that vanish at runtime, you get compile-time safety with zero-cost abstractions.
std::marker::PhantomData tracks phantom types// WITHOUT phantom types (runtime checks or confusion)
struct Distance {
value: f64,
unit: Unit, // Runtime enum or string - overhead!
}
enum Unit {
Meters,
Kilometers,
Miles,
}
fn calculate_speed(distance: Distance, time: f64) -> f64 {
// Hope the distance is in the right units!
// Runtime unit checking needed
match distance.unit {
Unit::Meters => distance.value / time,
Unit::Kilometers => (distance.value * 1000.0) / time,
Unit::Miles => (distance.value * 1609.34) / time,
}
}
// WITH phantom types (compile-time safety, zero cost)
use std::marker::PhantomData;
struct Meters;
struct Kilometers;
struct Distance<Unit> {
value: f64,
_unit: PhantomData<Unit>, // Zero size at runtime!
}
fn calculate_speed(distance: Distance<Meters>, time: f64) -> f64 {
distance.value / time // Type system guarantees units are correct!
}
// Won't compile - type mismatch caught at compile time:
// let km_distance = Distance::<Kilometers> { value: 5.0, _unit: PhantomData };
// calculate_speed(km_distance, 10.0); // ERROR: expected Distance<Meters>
The benefit: No runtime unit storage, no runtime checks, impossible to pass wrong units.
---
Scientific code with mixed units causes catastrophic bugs. NASA's Mars Climate Orbiter crashed because software mixed metric and imperial units - a $327 million mistake that phantom types prevent.
use std::marker::PhantomData;
use std::ops::{Add, Sub, Mul, Div};
// Unit markers (zero-sized types)
struct Meters;
struct Kilometers;
struct Miles;
struct Feet;
// Generic measurement with phantom type parameter
#[derive(Debug, Clone, Copy)]
struct Distance<Unit> {
value: f64,
_unit: PhantomData<Unit>,
}
impl<Unit> Distance<Unit> {
fn new(value: f64) -> Self {
Distance {
value,
_unit: PhantomData,
}
}
fn value(&self) -> f64 {
self.value
}
}
// Type-safe conversions between units
impl Distance<Meters> {
fn to_kilometers(self) -> Distance<Kilometers> {
Distance::new(self.value / 1000.0)
}
fn to_miles(self) -> Distance<Miles> {
Distance::new(self.value / 1609.34)
}
fn to_feet(self) -> Distance<Feet> {
Distance::new(self.value * 3.28084)
}
}
impl Distance<Kilometers> {
fn to_meters(self) -> Distance<Meters> {
Distance::new(self.value * 1000.0)
}
fn to_miles(self) -> Distance<Miles> {
Distance::new(self.value * 0.621371)
}
}
impl Distance<Miles> {
fn to_meters(self) -> Distance<Meters> {
Distance::new(self.value * 1609.34)
}
fn to_kilometers(self) -> Distance<Kilometers> {
Distance::new(self.value * 1.60934)
}
}
// Can only add/subtract same units
impl<Unit> Add for Distance<Unit> {
type Output = Distance<Unit>;
fn add(self, rhs: Self) -> Self::Output {
Distance::new(self.value + rhs.value)
}
}
impl<Unit> Sub for Distance<Unit> {
type Output = Distance<Unit>;
fn sub(self, rhs: Self) -> Self::Output {
Distance::new(self.value - rhs.value)
}
}
// Can multiply/divide by scalars
impl<Unit> Mul<f64> for Distance<Unit> {
type Output = Distance<Unit>;
fn mul(self, rhs: f64) -> Self::Output {
Distance::new(self.value * rhs)
}
}
impl<Unit> Div<f64> for Distance<Unit> {
type Output = Distance<Unit>;
fn div(self, rhs: f64) -> Self::Output {
Distance::new(self.value / rhs)
}
}
// Temperature with similar pattern
struct Celsius;
struct Fahrenheit;
struct Kelvin;
#[derive(Debug, Clone, Copy)]
struct Temperature<Unit> {
value: f64,
_unit: PhantomData<Unit>,
}
impl<Unit> Temperature<Unit> {
fn new(value: f64) -> Self {
Temperature {
value,
_unit: PhantomData,
}
}
}
impl Temperature<Celsius> {
fn to_fahrenheit(self) -> Temperature<Fahrenheit> {
Temperature::new(self.value * 9.0 / 5.0 + 32.0)
}
fn to_kelvin(self) -> Temperature<Kelvin> {
Temperature::new(self.value + 273.15)
}
}
impl Temperature<Fahrenheit> {
fn to_celsius(self) -> Temperature<Celsius> {
Temperature::new((self.value - 32.0) * 5.0 / 9.0)
}
}
// Usage in scientific calculations
fn calculate_orbital_speed(altitude: Distance<Kilometers>) -> Distance<Kilometers> {
let earth_radius = Distance::<Kilometers>::new(6371.0);
let orbital_radius = earth_radius + altitude;
let gravitational_parameter = 398600.4418; // km³/s²
Distance::new((gravitational_parameter / orbital_radius.value()).sqrt())
}
fn nasa_example() {
let spacecraft_altitude = Distance::<Miles>::new(250.0);
// Type system enforces correct conversion!
let altitude_km = spacecraft_altitude.to_kilometers();
let orbital_speed = calculate_orbital_speed(altitude_km);
println!("Orbital speed: {:.2} km/s", orbital_speed.value());
// This won't compile - prevents Mars Orbiter disaster:
// let orbital_speed = calculate_orbital_speed(spacecraft_altitude);
// ERROR: expected Distance<Kilometers>, found Distance<Miles>
// Temperature calculations
let room_temp = Temperature::<Fahrenheit>::new(72.0);
let celsius = room_temp.to_celsius();
println!("Room temperature: {:.1}°C", celsius.value);
// Can't accidentally mix temperatures:
// let result = room_temp + celsius;
// ERROR: no implementation for `Temperature<Fahrenheit> + Temperature<Celsius>`
}
Zero-cost proof:
// Memory layout proof
fn memory_layout_proof() {
use std::mem::{size_of, align_of};
// Phantom types add ZERO runtime overhead
assert_eq!(size_of::<Distance<Meters>>(), size_of::<f64>());
assert_eq!(size_of::<Distance<Kilometers>>(), size_of::<f64>());
assert_eq!(size_of::<Temperature<Celsius>>(), size_of::<f64>());
// PhantomData itself is zero-sized
assert_eq!(size_of::<PhantomData<Meters>>(), 0);
assert_eq!(align_of::<PhantomData<Meters>>(), 1);
println!("Size of Distance<Meters>: {} bytes", size_of::<Distance<Meters>>());
println!("Size of f64: {} bytes", size_of::<f64>());
println!("Phantom type overhead: ZERO bytes!");
}
---
In large applications with many database tables, passing wrong ID types causes subtle bugs. Passing a UserId where a PostId is expected leads to cryptic "not found" errors or worse - data corruption.
use std::marker::PhantomData;
use std::fmt;
// Entity markers
struct User;
struct Post;
struct Comment;
struct Organization;
struct Team;
// Generic ID with phantom type
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct Id<Entity> {
value: u64,
_entity: PhantomData<Entity>,
}
impl<Entity> Id<Entity> {
fn new(value: u64) -> Self {
Id {
value,
_entity: PhantomData,
}
}
fn value(&self) -> u64 {
self.value
}
}
impl<Entity> fmt::Display for Id<Entity> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.value)
}
}
// Database models with type-safe IDs
struct UserModel {
id: Id<User>,
name: String,
org_id: Id<Organization>,
}
struct PostModel {
id: Id<Post>,
author_id: Id<User>,
content: String,
}
struct CommentModel {
id: Id<Comment>,
post_id: Id<Post>,
author_id: Id<User>,
text: String,
}
// Type-safe database operations
trait Repository<Entity> {
type Model;
fn find(&self, id: Id<Entity>) -> Option<Self::Model>;
fn delete(&mut self, id: Id<Entity>) -> bool;
}
struct UserRepository;
impl Repository<User> for UserRepository {
type Model = UserModel;
fn find(&self, id: Id<User>) -> Option<UserModel> {
// Database query using id.value()
println!("SELECT * FROM users WHERE id = {}", id.value());
None // Simplified
}
fn delete(&mut self, id: Id<User>) -> bool {
println!("DELETE FROM users WHERE id = {}", id.value());
true
}
}
struct PostRepository;
impl Repository<Post> for PostRepository {
type Model = PostModel;
fn find(&self, id: Id<Post>) -> Option<PostModel> {
println!("SELECT * FROM posts WHERE id = {}", id.value());
None
}
fn delete(&mut self, id: Id<Post>) -> bool {
println!("DELETE FROM posts WHERE id = {}", id.value());
true
}
}
// Type-safe foreign key relationships
struct PostService {
post_repo: PostRepository,
user_repo: UserRepository,
}
impl PostService {
fn get_post_with_author(&self, post_id: Id<Post>) -> Option<(PostModel, UserModel)> {
let post = self.post_repo.find(post_id)?;
let author = self.user_repo.find(post.author_id)?;
Some((post, author))
}
fn delete_user_posts(&mut self, user_id: Id<User>) {
// Type system ensures we use correct ID types
println!("Finding posts by user {}", user_id);
// self.post_repo.delete(user_id); // ERROR: expected Id<Post>, found Id<User>
}
fn transfer_post_ownership(&mut self, post_id: Id<Post>, new_owner: Id<User>) {
println!("Transferring post {} to user {}", post_id, new_owner);
// Type safety prevents mixing up parameters!
}
}
// Generic query builder with type-safe IDs
struct QueryBuilder<Entity> {
table: &'static str,
conditions: Vec<String>,
_entity: PhantomData<Entity>,
}
impl<Entity> QueryBuilder<Entity> {
fn new(table: &'static str) -> Self {
QueryBuilder {
table,
conditions: Vec::new(),
_entity: PhantomData,
}
}
fn where_id(mut self, id: Id<Entity>) -> Self {
self.conditions.push(format!("id = {}", id.value()));
self
}
fn build(self) -> String {
let mut query = format!("SELECT * FROM {}", self.table);
if !self.conditions.is_empty() {
query.push_str(" WHERE ");
query.push_str(&self.conditions.join(" AND "));
}
query
}
}
fn database_example() {
let user_id = Id::<User>::new(42);
let post_id = Id::<Post>::new(123);
let comment_id = Id::<Comment>::new(456);
let user_repo = UserRepository;
let post_repo = PostRepository;
// Type-safe queries
user_repo.find(user_id);
post_repo.find(post_id);
// Won't compile - prevents ID mixups:
// user_repo.find(post_id); // ERROR: expected Id<User>, found Id<Post>
// post_repo.delete(user_id); // ERROR: expected Id<Post>, found Id<User>
// Type-safe query builder
let query = QueryBuilder::<User>::new("users")
.where_id(user_id)
.build();
println!("Query: {}", query);
// Won't compile:
// let bad_query = QueryBuilder::<User>::new("users")
// .where_id(post_id) // ERROR: expected Id<User>, found Id<Post>
// .build();
// Foreign key safety
let service = PostService { post_repo, user_repo };
service.get_post_with_author(post_id);
// Memory efficiency proof
println!("\nMemory efficiency:");
println!("Size of Id<User>: {} bytes", std::mem::size_of::<Id<User>>());
println!("Size of Id<Post>: {} bytes", std::mem::size_of::<Id<Post>>());
println!("Size of u64: {} bytes", std::mem::size_of::<u64>());
println!("Phantom type overhead: ZERO bytes!");
}
Real-world impact: This pattern prevents entire classes of bugs in large codebases. At scale, ID mixups can cause data leaks or corruption. Type-safe IDs make these bugs impossible.
---
Security vulnerabilities often come from using unvalidated data. Email injection, SQL injection, XSS - all happen when unvalidated strings are used in security-sensitive contexts.
use std::marker::PhantomData;
// Validation state markers
struct Validated;
struct Unvalidated;
// Generic email with validation state
struct Email<State = Unvalidated> {
address: String,
_state: PhantomData<State>,
}
#[derive(Debug)]
enum ValidationError {
MissingAtSign,
EmptyLocalPart,
EmptyDomain,
InvalidCharacters,
}
impl Email<Unvalidated> {
fn new(address: String) -> Self {
Email {
address,
_state: PhantomData,
}
}
fn validate(self) -> Result<Email<Validated>, ValidationError> {
// Validation logic
if !self.address.contains('@') {
return Err(ValidationError::MissingAtSign);
}
let parts: Vec<&str> = self.address.split('@').collect();
if parts.len() != 2 {
return Err(ValidationError::MissingAtSign);
}
if parts[0].is_empty() {
return Err(ValidationError::EmptyLocalPart);
}
if parts[1].is_empty() {
return Err(ValidationError::EmptyDomain);
}
// More validation...
Ok(Email {
address: self.address,
_state: PhantomData,
})
}
}
impl Email<Validated> {
fn address(&self) -> &str {
&self.address
}
fn domain(&self) -> &str {
self.address.split('@').nth(1).unwrap() // Safe - validation guarantees this
}
}
// Only validated emails can be sent
fn send_email(email: Email<Validated>, body: &str) {
println!("Sending email to: {}", email.address());
println!("Body: {}", body);
}
// Sanitized HTML
struct Sanitized;
struct Unsanitized;
struct HtmlString<State = Unsanitized> {
content: String,
_state: PhantomData<State>,
}
impl HtmlString<Unsanitized> {
fn new(content: String) -> Self {
HtmlString {
content,
_state: PhantomData,
}
}
fn sanitize(self) -> HtmlString<Sanitized> {
let sanitized = self.content
.replace('<', "<")
.replace('>', ">")
.replace('&', "&")
.replace('"', """)
.replace('\'', "'");
HtmlString {
content: sanitized,
_state: PhantomData,
}
}
}
impl HtmlString<Sanitized> {
fn content(&self) -> &str {
&self.content
}
}
// Only sanitized HTML can be rendered
fn render_html(html: HtmlString<Sanitized>) -> String {
format!("<div>{}</div>", html.content())
}
// SQL safe strings
struct SqlSafe;
struct SqlUnsafe;
struct SqlString<State = SqlUnsafe> {
value: String,
_state: PhantomData<State>,
}
impl SqlString<SqlUnsafe> {
fn new(value: String) -> Self {
SqlString {
value,
_state: PhantomData,
}
}
fn escape(self) -> SqlString<SqlSafe> {
let escaped = self.value
.replace('\\', "\\\\")
.replace('\'', "''")
.replace('"', "\\\"")
.replace('\0', "\\0");
SqlString {
value: escaped,
_state: PhantomData,
}
}
}
impl SqlString<SqlSafe> {
fn value(&self) -> &str {
&self.value
}
}
// Only escaped strings can be used in queries
fn execute_query(table: &str, value: SqlString<SqlSafe>) {
let query = format!("SELECT * FROM {} WHERE name = '{}'", table, value.value());
println!("Executing: {}", query);
}
// URL validation
struct ValidUrl;
struct InvalidUrl;
struct Url<State = InvalidUrl> {
url: String,
_state: PhantomData<State>,
}
impl Url<InvalidUrl> {
fn new(url: String) -> Self {
Url {
url,
_state: PhantomData,
}
}
fn validate(self) -> Result<Url<ValidUrl>, String> {
if self.url.starts_with("http://") || self.url.starts_with("https://") {
Ok(Url {
url: self.url,
_state: PhantomData,
})
} else {
Err("URL must start with http:// or https://".to_string())
}
}
}
impl Url<ValidUrl> {
fn as_str(&self) -> &str {
&self.url
}
}
fn fetch_url(url: Url<ValidUrl>) -> Result<String, std::io::Error> {
println!("Fetching: {}", url.as_str());
Ok("Response body".to_string())
}
fn security_example() {
// Email validation
let raw_email = Email::new("user@example.com".to_string());
match raw_email.validate() {
Ok(valid_email) => {
send_email(valid_email, "Hello!");
// Won't compile with unvalidated email:
// let bad_email = Email::new("notanemail".to_string());
// send_email(bad_email, "Hi"); // ERROR: expected Email<Validated>
}
Err(e) => println!("Invalid email: {:?}", e),
}
// HTML sanitization prevents XSS
let user_input = HtmlString::new("<script>alert('XSS')</script>".to_string());
let safe_html = user_input.sanitize();
let rendered = render_html(safe_html);
println!("Safe HTML: {}", rendered);
// Won't compile - prevents XSS:
// let unsafe_html = HtmlString::new("<script>...".to_string());
// render_html(unsafe_html); // ERROR: expected HtmlString<Sanitized>
// SQL injection prevention
let user_input = SqlString::new("'; DROP TABLE users; --".to_string());
let safe_sql = user_input.escape();
execute_query("users", safe_sql);
// Won't compile - prevents SQL injection:
// let unsafe_sql = SqlString::new("malicious".to_string());
// execute_query("users", unsafe_sql); // ERROR: expected SqlString<SqlSafe>
// URL validation
let url = Url::new("https://example.com".to_string());
if let Ok(valid_url) = url.validate() {
fetch_url(valid_url).ok();
}
}
Security impact: Phantom types implement the "parse, don't validate" principle. Once data is validated, the type system guarantees it stays validated. No re-validation needed, no validation bypass bugs possible.
---
APIs evolve over time. Supporting multiple protocol versions while preventing version mixups requires careful management. Accidentally using v1 parsing on v2 data causes bugs.
use std::marker::PhantomData;
// Protocol version markers
struct V1;
struct V2;
struct V3;
// Generic protocol message
#[derive(Debug)]
struct Message<Version> {
payload: Vec<u8>,
_version: PhantomData<Version>,
}
impl<V> Message<V> {
fn payload(&self) -> &[u8] {
&self.payload
}
}
// Version-specific parsing
impl Message<V1> {
fn parse(data: Vec<u8>) -> Result<Self, String> {
if data.len() < 4 {
return Err("V1 message too short".to_string());
}
Ok(Message {
payload: data,
_version: PhantomData,
})
}
fn get_field(&self) -> u32 {
// V1-specific field layout
u32::from_be_bytes([self.payload[0], self.payload[1],
self.payload[2], self.payload[3]])
}
// Migration to V2
fn upgrade(self) -> Message<V2> {
let mut new_payload = vec![0x02]; // V2 marker
new_payload.extend_from_slice(&self.payload);
Message {
payload: new_payload,
_version: PhantomData,
}
}
}
impl Message<V2> {
fn parse(data: Vec<u8>) -> Result<Self, String> {
if data.len() < 5 || data[0] != 0x02 {
return Err("Invalid V2 message".to_string());
}
Ok(Message {
payload: data,
_version: PhantomData,
})
}
fn get_field(&self) -> u32 {
// V2-specific field layout (offset by version byte)
u32::from_be_bytes([self.payload[1], self.payload[2],
self.payload[3], self.payload[4]])
}
fn get_extended_field(&self) -> Option<u16> {
// V2 adds new field
if self.payload.len() >= 7 {
Some(u16::from_be_bytes([self.payload[5], self.payload[6]]))
} else {
None
}
}
fn upgrade(self) -> Message<V3> {
let mut new_payload = vec![0x03]; // V3 marker
new_payload.extend_from_slice(&self.payload[1..]); // Skip old version marker
Message {
payload: new_payload,
_version: PhantomData,
}
}
}
impl Message<V3> {
fn parse(data: Vec<u8>) -> Result<Self, String> {
if data.len() < 8 || data[0] != 0x03 {
return Err("Invalid V3 message".to_string());
}
Ok(Message {
payload: data,
_version: PhantomData,
})
}
fn get_field(&self) -> u32 {
u32::from_be_bytes([self.payload[1], self.payload[2],
self.payload[3], self.payload[4]])
}
fn get_extended_field(&self) -> u16 {
u16::from_be_bytes([self.payload[5], self.payload[6]])
}
fn get_timestamp(&self) -> u64 {
// V3 adds timestamp
u64::from_be_bytes([
self.payload[7], self.payload[8], self.payload[9], self.payload[10],
self.payload[11], self.payload[12], self.payload[13], self.payload[14],
])
}
}
// Version-specific handlers
trait MessageHandler<Version> {
fn handle(&self, message: Message<Version>);
}
struct V1Handler;
impl MessageHandler<V1> for V1Handler {
fn handle(&self, message: Message<V1>) {
println!("V1 Handler - Field: {}", message.get_field());
}
}
struct V2Handler;
impl MessageHandler<V2> for V2Handler {
fn handle(&self, message: Message<V2>) {
println!("V2 Handler - Field: {}, Extended: {:?}",
message.get_field(), message.get_extended_field());
}
}
struct V3Handler;
impl MessageHandler<V3> for V3Handler {
fn handle(&self, message: Message<V3>) {
println!("V3 Handler - Field: {}, Extended: {}, Timestamp: {}",
message.get_field(), message.get_extended_field(), message.get_timestamp());
}
}
// HTTP request versioning
struct HttpRequest<Version> {
path: String,
headers: Vec<(String, String)>,
body: Vec<u8>,
_version: PhantomData<Version>,
}
impl HttpRequest<V1> {
fn new(path: String) -> Self {
HttpRequest {
path,
headers: Vec::new(),
body: Vec::new(),
_version: PhantomData,
}
}
fn add_header(mut self, key: String, value: String) -> Self {
self.headers.push((key, value));
self
}
}
impl HttpRequest<V2> {
fn new(path: String) -> Self {
HttpRequest {
path,
headers: vec![("X-API-Version".to_string(), "2".to_string())],
body: Vec::new(),
_version: PhantomData,
}
}
fn add_header(mut self, key: String, value: String) -> Self {
self.headers.push((key, value));
self
}
fn with_json_body(mut self, json: Vec<u8>) -> Self {
self.body = json;
self.headers.push(("Content-Type".to_string(), "application/json".to_string()));
self
}
}
// Version-specific endpoints
fn handle_v1_request(req: HttpRequest<V1>) {
println!("Handling V1 request to: {}", req.path);
println!("Headers: {} (V1 doesn't require version header)", req.headers.len());
}
fn handle_v2_request(req: HttpRequest<V2>) {
println!("Handling V2 request to: {}", req.path);
println!("Headers: {} (includes version header)", req.headers.len());
println!("Body size: {} bytes", req.body.len());
}
fn protocol_example() {
// Parse different versions
let v1_data = vec![0x00, 0x00, 0x00, 0x42];
let v1_msg = Message::<V1>::parse(v1_data).unwrap();
println!("V1 field: {}", v1_msg.get_field());
// Type-safe upgrade path
let v2_msg = v1_msg.upgrade();
println!("Upgraded to V2 - Extended field: {:?}", v2_msg.get_extended_field());
let v3_msg = v2_msg.upgrade();
println!("Upgraded to V3 - Timestamp: {}", v3_msg.get_timestamp());
// Won't compile - prevents version confusion:
// let v2_handler = V2Handler;
// let v1_msg = Message::<V1>::parse(vec![1, 2, 3, 4]).unwrap();
// v2_handler.handle(v1_msg); // ERROR: expected Message<V2>, found Message<V1>
// HTTP versioning
let v1_req = HttpRequest::<V1>::new("/users".to_string())
.add_header("Accept".to_string(), "application/json".to_string());
handle_v1_request(v1_req);
let v2_req = HttpRequest::<V2>::new("/users".to_string())
.with_json_body(b"{}".to_vec());
handle_v2_request(v2_req);
// Won't compile:
// handle_v2_request(v1_req); // ERROR: expected HttpRequest<V2>
}
---
In systems programming, tracking whether a buffer is owned or borrowed is critical. Freeing borrowed memory causes crashes. Phantom types can track this at compile time.
use std::marker::PhantomData;
use std::ptr;
// Ownership state markers
struct Owned;
struct Borrowed;
// Buffer with ownership tracking
struct Buffer<Ownership> {
data: *mut u8,
len: usize,
capacity: usize,
_ownership: PhantomData<Ownership>,
}
impl Buffer<Owned> {
fn new(capacity: usize) -> Self {
let layout = std::alloc::Layout::from_size_align(capacity, 1).unwrap();
let data = unsafe { std::alloc::alloc(layout) };
Buffer {
data,
len: 0,
capacity,
_ownership: PhantomData,
}
}
fn from_vec(mut vec: Vec<u8>) -> Self {
let data = vec.as_mut_ptr();
let len = vec.len();
let capacity = vec.capacity();
std::mem::forget(vec); // Don't drop the Vec
Buffer {
data,
len,
capacity,
_ownership: PhantomData,
}
}
fn as_borrowed(&self) -> Buffer<Borrowed> {
Buffer {
data: self.data,
len: self.len,
capacity: self.capacity,
_ownership: PhantomData,
}
}
fn write(&mut self, byte: u8) {
if self.len < self.capacity {
unsafe {
ptr::write(self.data.add(self.len), byte);
}
self.len += 1;
}
}
}
impl<O> Buffer<O> {
fn read(&self, index: usize) -> Option<u8> {
if index < self.len {
unsafe { Some(ptr::read(self.data.add(index))) }
} else {
None
}
}
fn len(&self) -> usize {
self.len
}
fn as_slice(&self) -> &[u8] {
unsafe { std::slice::from_raw_parts(self.data, self.len) }
}
}
// Only owned buffers can be freed
impl Drop for Buffer<Owned> {
fn drop(&mut self) {
if !self.data.is_null() && self.capacity > 0 {
unsafe {
let layout = std::alloc::Layout::from_size_align(self.capacity, 1).unwrap();
std::alloc::dealloc(self.data, layout);
}
}
}
}
// Borrowed buffers don't free memory - no Drop impl!
// File handle with ownership
struct FileDescriptor<Ownership> {
fd: i32,
_ownership: PhantomData<Ownership>,
}
impl FileDescriptor<Owned> {
fn open(path: &str) -> std::io::Result<Self> {
// Simplified - would actually open file
println!("Opening file: {}", path);
Ok(FileDescriptor {
fd: 42, // Mock file descriptor
_ownership: PhantomData,
})
}
fn as_borrowed(&self) -> FileDescriptor<Borrowed> {
FileDescriptor {
fd: self.fd,
_ownership: PhantomData,
}
}
}
impl<O> FileDescriptor<O> {
fn read(&self, buf: &mut [u8]) -> std::io::Result<usize> {
println!("Reading from fd {}", self.fd);
Ok(0)
}
fn write(&self, buf: &[u8]) -> std::io::Result<usize> {
println!("Writing {} bytes to fd {}", buf.len(), self.fd);
Ok(buf.len())
}
}
// Only owned file descriptors are closed
impl Drop for FileDescriptor<Owned> {
fn drop(&mut self) {
println!("Closing file descriptor: {}", self.fd);
// Would actually close fd here
}
}
// Resource pool with ownership tracking
struct ResourceHandle<Resource, Ownership> {
id: usize,
_resource: PhantomData<Resource>,
_ownership: PhantomData<Ownership>,
}
struct Database;
struct Connection;
struct ConnectionPool {
connections: Vec<ResourceHandle<Connection, Owned>>,
}
impl ConnectionPool {
fn new(size: usize) -> Self {
let connections = (0..size)
.map(|id| ResourceHandle {
id,
_resource: PhantomData,
_ownership: PhantomData,
})
.collect();
ConnectionPool { connections }
}
fn acquire(&mut self) -> Option<ResourceHandle<Connection, Borrowed>> {
self.connections.pop().map(|owned| ResourceHandle {
id: owned.id,
_resource: PhantomData,
_ownership: PhantomData,
})
}
fn release(&mut self, borrowed: ResourceHandle<Connection, Borrowed>) {
// Convert borrowed back to owned for pool storage
self.connections.push(ResourceHandle {
id: borrowed.id,
_resource: PhantomData,
_ownership: PhantomData,
});
}
}
fn ownership_example() {
// Owned buffer - will be freed
let mut owned_buffer = Buffer::<Owned>::new(1024);
owned_buffer.write(42);
owned_buffer.write(43);
println!("Owned buffer length: {}", owned_buffer.len());
// Borrowed buffer - won't be freed
{
let borrowed = owned_buffer.as_borrowed();
println!("Borrowed buffer length: {}", borrowed.len());
println!("Read from borrowed: {:?}", borrowed.read(0));
// Can't write to borrowed buffer (no write method)
// borrowed.write(44); // ERROR: method not found
} // borrowed dropped here - doesn't free memory
// Original owned buffer still valid
println!("Owned buffer still valid: {:?}", owned_buffer.read(0));
// File descriptor ownership
let owned_fd = FileDescriptor::<Owned>::open("/tmp/test.txt").unwrap();
{
let borrowed_fd = owned_fd.as_borrowed();
borrowed_fd.write(b"Hello").ok();
} // borrowed_fd dropped - doesn't close file
owned_fd.write(b"World").ok();
// owned_fd dropped here - closes file
// Connection pool
let mut pool = ConnectionPool::new(5);
let conn1 = pool.acquire().unwrap();
let conn2 = pool.acquire().unwrap();
println!("Acquired connection: {}", conn1.id);
// Return to pool
pool.release(conn1);
pool.release(conn2);
}
---
use std::marker::PhantomData;
// PhantomData is defined in std as:
pub struct PhantomData<T: ?Sized>;
// It's a zero-sized type (ZST) that acts as if it owns a T
// but doesn't actually store any data
// Compiler treats PhantomData<T> as if the struct owns T
// This affects variance, drop check, and auto traits
use std::mem::{size_of, align_of};
struct NoPhantom {
value: u64,
}
struct WithPhantom<T> {
value: u64,
_phantom: PhantomData<T>,
}
fn zst_proof() {
// PhantomData adds ZERO bytes
assert_eq!(size_of::<PhantomData<String>>(), 0);
assert_eq!(size_of::<PhantomData<Vec<u8>>>(), 0);
assert_eq!(size_of::<PhantomData<[u8; 1000]>>(), 0);
// Structs with PhantomData have same size as without
assert_eq!(size_of::<NoPhantom>(), 8);
assert_eq!(size_of::<WithPhantom<String>>(), 8);
assert_eq!(size_of::<WithPhantom<Vec<u8>>>(), 8);
// Alignment is minimal
assert_eq!(align_of::<PhantomData<String>>(), 1);
println!("PhantomData overhead: ZERO bytes!");
}
// Assembly comparison
fn process_no_phantom(x: NoPhantom) -> u64 {
x.value * 2
}
fn process_with_phantom(x: WithPhantom<String>) -> u64 {
x.value * 2
}
// Both functions compile to IDENTICAL assembly:
// mov rax, rdi
// add rax, rdi
// ret
Variance controls how subtyping works with generics. PhantomData affects variance.
use std::marker::PhantomData;
// Covariant: If Dog <: Animal, then Container<Dog> <: Container<Animal>
struct Covariant<T> {
_marker: PhantomData<T>,
}
// Invariant: No subtyping relationship regardless of T's relationship
struct Invariant<T> {
_marker: PhantomData<fn(T) -> T>,
}
// Contravariant: If Dog <: Animal, then Container<Animal> <: Container<Dog>
struct Contravariant<T> {
_marker: PhantomData<fn(T)>,
}
// Practical example: lifetime variance
struct LifetimeCovariant<'a> {
// Covariant in 'a
_marker: PhantomData<&'a ()>,
}
struct LifetimeInvariant<'a> {
// Invariant in 'a
_marker: PhantomData<&'a mut ()>,
}
fn variance_example() {
// Covariant: 'static can be used where 'a is expected
let static_ref: &'static str = "hello";
let covariant: LifetimeCovariant<'static> = LifetimeCovariant {
_marker: PhantomData,
};
// This works because LifetimeCovariant is covariant
let _shorter: LifetimeCovariant<'_> = covariant;
// Invariant lifetime prevents similar coercion
let invariant: LifetimeInvariant<'static> = LifetimeInvariant {
_marker: PhantomData,
};
// This doesn't work - invariant prevents coercion
// let _shorter: LifetimeInvariant<'_> = invariant; // ERROR
}
PhantomData affects drop check - Rust's mechanism to prevent use-after-free.
use std::marker::PhantomData;
// Without PhantomData - UNSOUND
struct WithoutPhantom<T> {
ptr: *const T,
}
impl<T> Drop for WithoutPhantom<T> {
fn drop(&mut self) {
// DANGER: T might be dropped before this runs!
// unsafe { ptr::read(self.ptr); } // Potential use-after-free
}
}
// With PhantomData - SOUND
struct WithPhantom<T> {
ptr: *const T,
_marker: PhantomData<T>, // Tells drop checker we "own" T
}
impl<T> Drop for WithPhantom<T> {
fn drop(&mut self) {
// Safe: drop checker ensures T outlives self
// unsafe { ptr::read(self.ptr); }
}
}
// Real example: Vec-like structure
struct MyVec<T> {
ptr: *mut T,
len: usize,
cap: usize,
_marker: PhantomData<T>, // Critical for soundness!
}
impl<T> Drop for MyVec<T> {
fn drop(&mut self) {
// PhantomData ensures this is sound
unsafe {
// Drop all elements
for i in 0..self.len {
ptr::drop_in_place(self.ptr.add(i));
}
// Deallocate buffer
if self.cap > 0 {
let layout = std::alloc::Layout::array::<T>(self.cap).unwrap();
std::alloc::dealloc(self.ptr as *mut u8, layout);
}
}
}
}
Phantom types enable type-level computation.
use std::marker::PhantomData;
// Type-level booleans
struct True;
struct False;
// Type-level logic
trait And<Rhs> {
type Output;
}
impl And<True> for True {
type Output = True;
}
impl And<False> for True {
type Output = False;
}
impl And<True> for False {
type Output = False;
}
impl And<False> for False {
type Output = False;
}
// Type-level numbers (Peano numbers)
struct Zero;
struct Succ<N>(PhantomData<N>);
type One = Succ<Zero>;
type Two = Succ<One>;
type Three = Succ<Two>;
// Type-level addition
trait Add<Rhs> {
type Output;
}
impl<N> Add<Zero> for N {
type Output = N;
}
impl<N, M> Add<Succ<M>> for N
where
N: Add<M>,
{
type Output = Succ<<N as Add<M>>::Output>;
}
// Array with type-level length
struct TypedArray<T, N> {
data: Vec<T>,
_len: PhantomData<N>,
}
impl<T> TypedArray<T, Zero> {
fn new() -> Self {
TypedArray {
data: Vec::new(),
_len: PhantomData,
}
}
}
impl<T, N> TypedArray<T, N> {
fn push(self, item: T) -> TypedArray<T, Succ<N>> {
let mut data = self.data;
data.push(item);
TypedArray {
data,
_len: PhantomData,
}
}
}
fn type_level_programming() {
// Array with length encoded in type
let arr = TypedArray::<i32, Zero>::new()
.push(1) // TypedArray<i32, One>
.push(2) // TypedArray<i32, Two>
.push(3); // TypedArray<i32, Three>
// Type system knows exact length!
// This enables compile-time bounds checking
}
// Source code
struct Distance<Unit> {
value: f64,
_unit: PhantomData<Unit>,
}
fn double_meters(d: Distance<Meters>) -> Distance<Meters> {
Distance {
value: d.value * 2.0,
_unit: PhantomData,
}
}
// After monomorphization (conceptually):
// Compiler generates code as if PhantomData doesn't exist
struct Distance_Meters {
value: f64,
// _unit field completely erased
}
fn double_meters_monomorphized(d: Distance_Meters) -> Distance_Meters {
Distance_Meters {
value: d.value * 2.0,
// _unit construction erased
}
}
// Final assembly (x86_64):
// double_meters:
// movsd xmm1, qword ptr [rip + .LCPI0_0] ; Load 2.0
// mulsd xmm0, xmm1 ; Multiply
// ret
//
// No overhead from phantom type!
---
Id vs IdEmail vs EmailRequest vs Requestenum or runtime checks are too expensivetype UserId = u64 might be enoughTypeId or enums instead---
// ❌ WRONG: Trying to store runtime information
struct Config<Mode> {
enabled: bool,
_mode: PhantomData<Mode>,
}
fn get_mode<M>(config: &Config<M>) -> &str {
// Can't do this - M doesn't exist at runtime!
// std::any::type_name::<M>() // Doesn't help - just a string
"unknown"
}
// ✅ RIGHT: Use enum for runtime information
enum Mode {
Development,
Production,
}
struct Config {
enabled: bool,
mode: Mode,
}
fn get_mode(config: &Config) -> &str {
match config.mode {
Mode::Development => "development",
Mode::Production => "production",
}
}
// ❌ WRONG: Incorrect variance can cause unsoundness
struct MutRef<'a, T> {
reference: &'a mut T,
// Missing PhantomData - wrong variance!
}
// ✅ RIGHT: Correct variance with PhantomData
struct MutRef<'a, T> {
reference: &'a mut T,
_marker: PhantomData<&'a mut T>, // Invariant in T
}
// ❌ WRONG: Forgetting PhantomData in unsafe code
struct Wrapper<T> {
ptr: *const T,
// Missing PhantomData - drop check issues!
}
impl<T> Drop for Wrapper<T> {
fn drop(&mut self) {
// UNSOUND: T might be dropped already!
unsafe { ptr::read(self.ptr); }
}
}
// ✅ RIGHT: PhantomData for drop check soundness
struct Wrapper<T> {
ptr: *const T,
_marker: PhantomData<T>,
}
// ❌ WRONG: Over-complicated for simple case
struct Username<Validated, MinLength, MaxLength> {
value: String,
_v: PhantomData<Validated>,
_min: PhantomData<MinLength>,
_max: PhantomData<MaxLength>,
}
// ✅ RIGHT: Simple validation is enough
struct Username {
value: String,
}
impl Username {
fn new(value: String) -> Result<Self, ValidationError> {
if value.len() < 3 || value.len() > 20 {
return Err(ValidationError::InvalidLength);
}
Ok(Username { value })
}
}
// ❌ WRONG: Phantom lifetime without considering drop
struct Container<'a, T> {
ptr: *const T,
_marker: PhantomData<&'a T>,
}
// Problem: Drop might run after 'a ends
impl<'a, T> Drop for Container<'a, T> {
fn drop(&mut self) {
// Might be unsound if we access *ptr here
}
}
// ✅ RIGHT: Use #[may_dangle] if you understand implications
unsafe impl<#[may_dangle] 'a, T> Drop for Container<'a, T> {
fn drop(&mut self) {
// Safe if we promise not to access T through 'a
}
}
---
// Phantom types affect compile time, not runtime
// Small impact: Simple phantom type
struct Id<T> {
value: u64,
_marker: PhantomData<T>,
}
// Moderate impact: Multiple phantom parameters
struct Cache<K, V, Strategy> {
data: HashMap<u64, Vec<u8>>,
_key: PhantomData<K>,
_value: PhantomData<V>,
_strategy: PhantomData<Strategy>,
}
// Higher impact: Type-level computation
struct Matrix<Rows, Cols>
where
Rows: Add<Rows>,
Cols: Add<Cols>,
{
data: Vec<f64>,
_rows: PhantomData<Rows>,
_cols: PhantomData<Cols>,
}
// Compilation time increases with:
// 1. Number of monomorphizations (different type parameters)
// 2. Complexity of trait bounds on phantom types
// 3. Type-level computations involving phantom types
use std::marker::PhantomData;
use std::time::Instant;
struct PlainId {
value: u64,
}
struct TypedId<T> {
value: u64,
_marker: PhantomData<T>,
}
struct User;
fn benchmark_plain(iterations: usize) -> u128 {
let start = Instant::now();
let mut sum = 0u64;
for i in 0..iterations {
let id = PlainId { value: i as u64 };
sum += id.value;
}
let elapsed = start.elapsed().as_nanos();
println!("Plain ID sum: {}", sum);
elapsed
}
fn benchmark_typed(iterations: usize) -> u128 {
let start = Instant::now();
let mut sum = 0u64;
for i in 0..iterations {
let id = TypedId::<User> { value: i as u64, _marker: PhantomData };
sum += id.value;
}
let elapsed = start.elapsed().as_nanos();
println!("Typed ID sum: {}", sum);
elapsed
}
fn performance_comparison() {
const ITERATIONS: usize = 10_000_000;
println!("Running {} iterations...", ITERATIONS);
let plain_time = benchmark_plain(ITERATIONS);
let typed_time = benchmark_typed(ITERATIONS);
println!("\nResults:");
println!("Plain ID: {} ns", plain_time);
println!("Typed ID: {} ns", typed_time);
println!("Difference: {} ns ({:.2}%)",
(typed_time as i128 - plain_time as i128).abs(),
((typed_time as f64 - plain_time as f64) / plain_time as f64 * 100.0).abs());
// Difference is typically < 1% (within measurement noise)
}
use std::mem::{size_of, align_of};
struct NoPhantom {
id: u64,
name: String,
}
struct WithOnePhantom<T> {
id: u64,
name: String,
_marker: PhantomData<T>,
}
struct WithManyPhantoms<A, B, C, D> {
id: u64,
name: String,
_a: PhantomData<A>,
_b: PhantomData<B>,
_c: PhantomData<C>,
_d: PhantomData<D>,
}
fn memory_analysis() {
println!("Memory Layout Analysis:");
println!("=======================");
// Size comparison
println!("\nSize (bytes):");
println!(" NoPhantom: {}", size_of::<NoPhantom>());
println!(" WithOnePhantom: {}", size_of::<WithOnePhantom<String>>());
println!(" WithManyPhantoms: {}", size_of::<WithManyPhantoms<u8, u16, u32, u64>>());
// All should be identical!
assert_eq!(size_of::<NoPhantom>(), size_of::<WithOnePhantom<String>>());
assert_eq!(size_of::<NoPhantom>(), size_of::<WithManyPhantoms<u8, u16, u32, u64>>());
// Alignment comparison
println!("\nAlignment (bytes):");
println!(" NoPhantom: {}", align_of::<NoPhantom>());
println!(" WithOnePhantom: {}", align_of::<WithOnePhantom<String>>());
println!(" WithManyPhantoms: {}", align_of::<WithManyPhantoms<u8, u16, u32, u64>>());
println!("\n✅ Zero overhead confirmed!");
}
// Phantom types can increase binary size through monomorphization
// Small impact: Few instantiations
fn process_user_id(id: Id<User>) -> u64 {
id.value()
}
// Larger impact: Many instantiations
fn process_id<T>(id: Id<T>) -> u64 {
id.value()
}
// Each unique T generates a new copy of the function
// process_id::<User>, process_id::<Post>, process_id::<Comment>, etc.
// To minimize binary size:
// 1. Use generic implementations sparingly
// 2. Factor out non-generic code
// 3. Use dynamic dispatch when binary size matters more than performance
fn process_id_optimized<T>(id: Id<T>) -> u64 {
// Factor out generic-independent code
process_id_impl(id.value())
}
fn process_id_impl(value: u64) -> u64 {
// Non-generic implementation (single copy in binary)
value * 2
}
---
Create a currency system with phantom types that prevents mixing currencies.
// TODO: Implement a currency system with:
// - Currency markers: USD, EUR, GBP, JPY
// - Money<Currency> type with phantom currency
// - Can add/subtract same currency
// - Cannot add different currencies (compile error)
// - Conversion methods between currencies
// - Display formatting with currency symbols
// Starter code:
use std::marker::PhantomData;
struct USD;
struct EUR;
struct GBP;
struct Money<Currency> {
// Your code here
}
// Implement methods:
// - new(amount: f64) -> Self
// - amount(&self) -> f64
// - add(self, other: Self) -> Self
// - subtract(self, other: Self) -> Self
// Implement conversions:
// - Money<USD>::to_eur() -> Money<EUR>
// - Money<EUR>::to_usd() -> Money<USD>
// etc.
fn test_currency() {
let usd = Money::<USD>::new(100.0);
let eur = Money::<EUR>::new(85.0);
// Should compile:
let total_usd = usd.add(Money::<USD>::new(50.0));
// Should NOT compile:
// let mixed = usd.add(eur); // ERROR
// Should compile after conversion:
let eur_converted = usd.to_eur();
let total_eur = eur.add(eur_converted);
}
Build a file permission system using phantom types.
// TODO: Implement a type-safe file permission system with:
// - Permission markers: ReadOnly, WriteOnly, ReadWrite
// - File<Permission> type
// - Only ReadWrite and ReadOnly can read
// - Only ReadWrite and WriteOnly can write
// - open_readonly, open_writeonly, open_readwrite methods
// - upgrade/downgrade permission methods
// - Ensure compile-time safety for all operations
// Starter code:
use std::marker::PhantomData;
struct ReadOnly;
struct WriteOnly;
struct ReadWrite;
struct File<Permission> {
path: String,
// Your code here
}
// Implement methods for specific permissions:
// impl File<ReadOnly> { fn read(&self) -> String }
// impl File<WriteOnly> { fn write(&mut self, data: &str) }
// impl File<ReadWrite> { fn read(&self) -> String, fn write(&mut self, data: &str) }
// Implement permission upgrades:
// impl File<ReadOnly> { fn upgrade_to_readwrite(self) -> File<ReadWrite> }
fn test_permissions() {
let read_file = File::<ReadOnly>::open("data.txt");
let data = read_file.read();
// read_file.write("data"); // Should NOT compile
let mut write_file = File::<WriteOnly>::open("output.txt");
write_file.write("hello");
// let data = write_file.read(); // Should NOT compile
let mut rw_file = File::<ReadWrite>::open("both.txt");
let data = rw_file.read();
rw_file.write("new data");
}
Create a type-level query builder that prevents invalid SQL at compile time.
// TODO: Implement a type-safe SQL query builder with:
// - Phantom types tracking query state (NoSelect, HasSelect, NoFrom, HasFrom, etc.)
// - Query<Select, From, Where, OrderBy> with phantom parameters
// - Methods that transition between states
// - Can only build() when query is complete (HasSelect + HasFrom)
// - Prevent calling select() twice, from() twice, etc.
// - Type-safe column references
// - Type-safe WHERE clause builder
// Starter code:
use std::marker::PhantomData;
// State markers
struct NoSelect;
struct HasSelect;
struct NoFrom;
struct HasFrom;
struct NoWhere;
struct HasWhere;
struct Query<Select, From, Where> {
// Your code here
}
impl Query<NoSelect, NoFrom, NoWhere> {
fn new() -> Self {
// Initial query state
todo!()
}
}
// Implement state transitions:
// impl<F, W> Query<NoSelect, F, W> {
// fn select(self, columns: &[&str]) -> Query<HasSelect, F, W>
// }
// impl<S, W> Query<S, NoFrom, W> {
// fn from(self, table: &str) -> Query<S, HasFrom, W>
// }
// impl<S, F> Query<S, F, NoWhere> {
// fn where_clause(self, condition: &str) -> Query<S, F, HasWhere>
// }
// Only complete queries can be built:
// impl Query<HasSelect, HasFrom, W> {
// fn build(self) -> String
// }
fn test_query_builder() {
// Should compile:
let query = Query::new()
.select(&["id", "name"])
.from("users")
.where_clause("age > 18")
.build();
// Should NOT compile (no select):
// let bad = Query::new().from("users").build(); // ERROR
// Should NOT compile (no from):
// let bad = Query::new().select(&["id"]).build(); // ERROR
// Should NOT compile (select twice):
// let bad = Query::new().select(&["id"]).select(&["name"]); // ERROR
}
---
The standard library uses PhantomData extensively:
// Vec uses PhantomData for drop check
pub struct Vec<T> {
buf: RawVec<T>,
len: usize,
}
pub struct RawVec<T> {
ptr: Unique<T>,
cap: usize,
}
pub struct Unique<T: ?Sized> {
pointer: *const T,
_marker: PhantomData<T>, // Critical for soundness!
}
// tokio uses phantom lifetimes for safety
pub struct JoinHandle<T> {
raw: RawTask,
_p: PhantomData<fn() -> T>,
}
// Ensures proper lifetime relationships in async code
pub struct ReadHalf<'a> {
inner: Arc<Inner>,
_phantom: PhantomData<&'a mut Inner>,
}
// diesel uses phantom types for query building
pub struct SelectStatement<
From,
Select = DefaultSelectClause,
Distinct = NoDistinctClause,
Where = NoWhereClause,
> {
select: Select,
from: From,
distinct: Distinct,
where_clause: Where,
_marker: PhantomData<(From, Select, Distinct, Where)>,
}
// Type-safe query construction:
users::table
.select(users::name)
.filter(users::age.gt(18))
// Compile error if you try invalid SQL!
// serde uses PhantomData in deserializers
pub struct Deserializer<'de> {
input: &'de str,
_marker: PhantomData<&'de ()>,
}
// Zero-copy deserialization tracking
pub struct StrDeserializer<'a, E> {
value: &'a str,
marker: PhantomData<E>,
}
// hyper tracks HTTP versions with phantom types
pub struct Request<T = Body> {
head: RequestHead,
body: T,
}
pub struct Response<T = Body> {
head: ResponseHead,
body: T,
}
// Version markers in type system
pub struct Http1;
pub struct Http2;
pub struct Connection<T, B> {
protocol: Protocol,
_marker: PhantomData<fn(T, B)>,
}
---
---
Phantom types are one of Rust's most powerful zero-cost abstraction techniques:
The key insight: Information in types costs nothing at runtime but provides immense safety benefits. Phantom types let you encode invariants that the compiler enforces but that leave zero trace in the final binary.
Use phantom types when you need type safety without runtime cost - units of measurement, type-safe IDs, validation states, protocol versioning, and ownership tracking. The pattern is especially powerful in systems programming where every byte and every cycle matters, but safety cannot be compromised.
Remember the NASA Mars Orbiter: a $327 million crash because of unit confusion. Phantom types make such bugs impossible - at zero runtime cost. That's the power of Rust's type system.
Run this code in the official Rust Playground