[The Rust Programming Language] 19. Advanced Features
19. Advanced Features
19.3 Advanced Types
Using the Newtype Pattern for Type Safety and Abstraction
Creating Type Synonyms with Type Aliases
Rust provides the ability to declare a type alias to give an existing type another name. For this we use the type
keyword.
The main use case for type Alias(synonyms) is to reduce repetition.
1 | let f: Box<dyn Fn() + Send + 'static> = Box::new(|| println!("hi")); |
A type alias makes this code more manageable by reducing the repetition.
1 | type Thunk = Box<dyn Fn() + Send + 'static>; |
Type aliases are also commonly used with the Result<T, E>
type for reducing repetition.
The Result<..., Error>
is repeated a lot. As such, std::io has this type alias declaration:
1 | type Result<T> = std::result::Result<T, std::io::Error>; |
Because this declaration is in the std::io module, we can use the fully qualified alias std::io::Result<T>—that is, a
Result<T, E>with the
Efilled in as
std::io::Error`. The Write trait function signatures end up looking like this:
1 | pub trait Write { |
! The Never Type that Never Returns
Rust has a special type named !
that’s known in type theory lingo as the empty type because it has no values. We prefer to call it the never type because it stands in the place of the return type when a function will never return. Here is an example:
1 | fn bar() -> ! { |
Dynamically Sized Types and the Sized Trait
Rust needs to know how much memory to allocate for any value of a particular type, and all values of a type must use the same amount of memory.
1 | // let s1: str = "Hello there!"; // expected `str`, found `&str` |
If Rust allowed us to write this code, these two str values would need to take up the same amount of space. But they have different lengths: s1 needs 12 bytes of storage and s2 needs 15. This is why it’s not possible to create a variable holding a dynamically sized type.
So although a &T
is a single value that stores the memory address of where the T
is located, a &str
is two values: the address of the str
and its length. As such, we can know the size of a &str
value at compile time: it’s twice the length of a usize
. That is, we always know the size of a &str
, no matter how long the string it refers to is. In general, this is the way in which dynamically sized types are used in Rust: they have an extra bit of metadata that stores the size of the dynamic information. The golden rule of dynamically sized types is that we must always put values of dynamically sized types behind a pointer of some kind.