rust: write more readable code with type alias
Type aliases in Rust allow you to assign a new name to an existing type. They are defined using the type
keyword. The syntax is:
type <alias-name> = <type>
For example, you can create a type alias for a complex type like:
type Result<T> = ::std::result::Result<T, ::std::io::Error>;
Now Result<T>
can be used instead of the longer ::std::result::Result<T, ::std::io::Error>
type.
However, type aliases do not create a new type. They simply introduce a new name for an existing type. This means that type aliases share the same underlying type and act exactly the same as the original type.
Use cases for type aliases
Type aliases are useful in several cases:
- Simplifying complex type signatures: As seen in the above example, type aliases can be used to simplify long, complex type signatures by giving them a shorter name.
- Abstraction over concrete types: Type aliases can be used to abstract over concrete types. For example:
type File = ::std::fs::File;
Now File
can be used instead of ::std::fs::File
throughout the codebase. If the concrete type changes in the future, only the type alias definition needs to be updated.
- Readability: Type aliases can be used to improve readability of types by giving them descriptive names. For example:
type UserId = u32;
is more readable than just using u32
directly.
Generic type aliases
Type aliases can also be generic, by using generic type parameters. The syntax is:
type <alias-name><T> = <generic-type-def>
For example, a generic type alias for Vec
would be:
type List<T> = Vec<T>;
Now List<T>
can be used as a generic type alias instead of the concrete Vec<T>
type. For example:
let ints: List<i32> = vec![1, 2, 3];
let strings: List<String> = vec!["Hello".to_string(), "World!".to_string()];
We can also have multi-parameter generic type aliases. For example:
type Grid<T> = Vec<Vec<T>>;
And use it as:
let grid = Grid::<u32>(vec![vec![1, 2, 3], vec![4, 5, 6]]);
Associated type aliases in traits
Traits can have associated type aliases which define placeholder types that are implemented by implementors of the trait. The syntax is:
trait <trait-name> {
type <associated-type-alias> associated type;
}
For example, the Iterator
trait has an associated type Item
which represents the type of elements being iterated over:
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
Types that implement the Iterator
trait need to specify the concrete type for Item
. For example:
struct MyIterator { /* ... */ }
impl Iterator for MyIterator {
type Item = usize;
fn next(&mut self) -> Option<usize> { /* ... */ }
}
Here MyIterator
specifies that for this iterator, the associated type Item
is usize
.
Advanced uses of type aliases
Type aliases have some advanced use cases in Rust:
- Opaque types: Type aliases can be used to define opaque types that hide the concrete underlying type. For example:
type List = impl Node;
fn get_node(id: NodeId) -> Node { ... }
`impl Trait` in type aliases is unstable
see issue #63063 <https://github.com/rust-lang/rust/issues/63063>
Here, List
is an opaque type alias for Node
. The concrete type is abstracted away and users cannot depend on it being a Node
.
- Newtypes: Type aliases can be used to create newtypes that introduce a type level distinction between two types that share the same underlying type. For example:
type Inches = u32;
let len = Inches(10);
Here Inches
is a newtype around u32
that distinguishes Inches
from a raw u32
at the type level, even though they share the same representation.
- Phantom types: A phantom type parameter is one that doesn’t show up at runtime, but is checked statically (and only) at compile time. For example:
use std::marker::PhantomData;
#[derive(PartialEq)] // Allow equality test for this type.
struct PhantomTuple<A, B>(A, PhantomData<B>);
fn main() {
let _tuple1: PhantomTuple<char, f32> = PhantomTuple('Q', PhantomData);
}
Here Phantom<A, B>
is a phantom type that carries the State
information at the type level, even though it has no runtime representation.
Conclusion
Type aliases are a useful feature of Rust to name existing types, abstract over concrete types, improve readability, define generic type aliases, associated types in traits, and enable advanced use cases like opaque types, newtypes and phantom types. They allow giving meaningful names to types and carrying information at the type level. Overall, type aliases are an important tool in Rust’s type system arsenal.