Type Aliases
A type alias declares an alternative name (a synonym) for an existing type using the type keyword (type AliasName = ExistingType;). A type alias does not create a distinct type—it is 100% interchangeable with the underlying type it refers to.
Syntax and Basic Usage
By convention, type aliases use UpperCamelCase:
type Kilometers = i32;
type Point2D = (f64, f64);
fn distance_from_origin(pt: Point2D) -> f64 {
(pt.0 * pt.0 + pt.1 * pt.1).sqrt()
}
fn main() {
let local_trip: Kilometers = 15;
let raw_integer: i32 = 25;
// Because `Kilometers` is just an alias for `i32`, they can be added directly without casting!
let total: Kilometers = local_trip + raw_integer;
let location: Point2D = (3.0, 4.0);
println!("Total distance: {total} km, hypotenuse: {}", distance_from_origin(location));
}
Total distance: 40 km, hypotenuse: 5
Type Alias vs Newtype Wrapper (struct Meters(i32);)
Because a type alias is a transparent synonym rather than a distinct type, the compiler will not stop you from accidentally passing a Seconds alias to a function expecting a Kilometers alias if both alias i32.
When you want compile-time enforcement that prevents mixing two quantities with the same underlying representation, use a single-field tuple struct (newtype) instead of a type alias:
| Feature | Type Alias (type Meters = f64;) |
Newtype Struct (struct Meters(f64);) |
|---|---|---|
Creates a distinct type in rustc? |
No (Meters and f64 are the exact same type) |
Yes (Meters and f64 cannot be mixed without explicit .0 unwrap) |
| Inherits all methods and operators of inner type? | Yes (automatically supports +, -, .abs(), etc.) |
No (only implements traits you explicitly #[derive] or impl) |
Can implement new traits on it (Orphan Rule)? |
No (impl MyTrait for Meters is just impl MyTrait for f64) |
Yes (Meters is a local type owned by your crate) |
| Runtime memory overhead | 0 bytes |
0 bytes |
type AliasMeters = u32;
type AliasSeconds = u32;
struct NewtypeMeters(u32);
struct NewtypeSeconds(u32);
fn main() {
let m: AliasMeters = 100;
let s: AliasSeconds = 10;
// Allowed by the compiler (both are `u32`), even though adding meters + seconds is a logic bug!
let _accidental_sum = m + s;
let nm = NewtypeMeters(100);
let ns = NewtypeSeconds(10);
// `nm + ns` would fail to compile because `NewtypeMeters` and `NewtypeSeconds` are distinct types!
println!("Newtypes: {} m in {} s", nm.0, ns.0);
}
Newtypes: 100 m in 10 s
Primary Use Cases for Type Aliases
1. Shortening Long, Repetitive Types
Complex nested types—such as boxed closures, thread-safe shared maps, or multi-element tuples—become much easier to read when given a descriptive alias:
use std::collections::HashMap;
type UserId = u64;
type HeadersMap = HashMap<String, Vec<String>>;
type TransformFn = fn(i32) -> i32;
fn apply_all(initial: i32, steps: &[TransformFn]) -> i32 {
let mut current = initial;
for step in steps {
current = step(current);
}
current
}
fn main() {
let mut headers: HeadersMap = HashMap::new();
headers.insert(String::from("Accept"), vec![String::from("application/json")]);
let user: UserId = 42;
let result = apply_all(5, &[|x| x + 1, |x| x * 10]);
println!("user = {user}, result = {result}, headers = {headers:?}");
}
user = 42, result = 60, headers = {"Accept": ["application/json"]}
2. Partial Generic Specialization (type Result<T> = std::result::Result<T, MyError>;)
A type alias can declare its own generic type parameters <T> while fixing other type parameters to a concrete type. This pattern is used throughout the standard library (such as std::io::Result<T> and std::fmt::Result) so functions within a module do not have to repeat the error type on every signature:
std::io::Result<T> is defined as type Result<T> = std::result::Result<T, std::io::Error>;
std::fmt::Result is defined as type Result = std::result::Result<(), std::fmt::Error>;
#[derive(Debug)]
enum ParseError {
EmptyInput,
InvalidDigit,
}
// Generic type alias that fixes the `Err` type to `ParseError`:
type ParseResult<T> = std::result::Result<T, ParseError>;
fn parse_single_digit(s: &str) -> ParseResult<u8> {
let first = s.chars().next().ok_or(ParseError::EmptyInput)?;
let digit = first.to_digit(10).ok_or(ParseError::InvalidDigit)?;
Ok(digit as u8)
}
fn main() {
println!("'7' -> {:?}", parse_single_digit("7"));
println!("'' -> {:?}", parse_single_digit(""));
println!("'x' -> {:?}", parse_single_digit("x"));
}
'7' -> Ok(7)
'' -> Err(EmptyInput)
'x' -> Err(InvalidDigit)
3. Referencing Struct and Enum Constructors via Type Aliases
You can also use a type alias to construct struct literals (Alias { ... }) or access enum variants (Alias::Variant):
#[derive(Debug)]
enum ConnectionState {
Disconnected,
Connected(u16),
}
type State = ConnectionState;
fn main() {
let current = State::Connected(443);
println!("State: {current:?}");
}
State: Connected(443)