Advanced Types in Rust
In previous section we learned about Newtype Pattern in the context of implementing a trait on a given type.
In this example we want to implement
Display trait on a
Vector type, however both are defined outisde of our trait. We get around this by defining a new Wrapper type, which a tuple struct containing a vector.
Other uses for Newtype Pattern can be to increase type safety.
For example, we have two functions, one function took an age as a paramter and another function took in an employee id as a parameter. The type of both parameters is
u32. To avoid mixing of age and employee id when calling the function then we can create a new type which wraps an
Like, we can construct tuple struct for
Another use of Newtype Pattern is to abstract away implementation details.
For example, we can create
People type which wraps a hash map of integers to strings.
In an essence, Newtype Pattern is a lightweight way to achieve encapsulation.
Rust also allows to create type aliases to give existing types new names.
The main usecase of type aliases is to reduce repetition:
Instead of writing this type over and over again, we can create type alias:
Type aliases also convey meaning for the type. For example, above here
Thunk here is a word for code that will be evaluated at some later point.
The Never Type is a special type denoted with
! meaning that the the function will never return.
Why this might be useful?
Recall that in Chapter 2, we built a guessing game and we had some code which parsed user input into an integer, something like this:
guessand parse it and return a
u32however if the parsing failed we call
continueto skip this current iteration.
u32, so how one
match arm returns a
u32 and other with
continue has a Never Type. Rust will loook at both arms of this match expression:
Thus. Rust confirms that the return type of this expression is an
If we get an
Err variant in the above case,
continue will not return anything, instead it will move the control back to the top of the loop, never assigning to
guess and only assigning
is useful withpanic!` macro as well.
For example the
Option<T> enum has a method called
unwrap() like this which evaluates
panic! returns a Never Type.
A loop also has Never Type
breakstatement inside of the loop because
breakwill cause the loop to terminate.
print! is another macro with similar function to
println! but without any line ending.
Dynamically Sized Types
Dynamically Sized Types or Usigned Type are types whose size we can only know at runtime.
Example of this is
s2 Rust can't determine the size of these store types at compile time. If we tried to compile this, we'll get the above error.
Instead we need to use borrowed version of
The string slice
&str stores two values:
1. An address pointing to the location of string in memory
2. The length of the string
Both the address value and the length of the string have a type of
usize, we know their size at compile time.
This is how dynamically sized types are used in Rust. They have extra data structure which stores the size of the dynamic information.
The golden rule for DSTs is that we should always put them behind some sort of pointer..
In previous example
str was behind
& (Reference) but
Rc<T> would also have just worked fine.
Traits and Dynamically Sized Types
Traits are also Dynamically Sized Types. Traits object always needs to be behind some sort of pointer.
Rust has a special trait called the size trait to determine whether a type
Sized can be known at compile time or not. The
Sized trait is autmatically implemented for every type whose size is known at compile time.
Rust implicitly adds the
Sized trait bound to every generic function.
Rust will automatically add
Sized trait bound like so:
By default generic functions will only work on types whose size is known at compile time, however we ca use the belo syntax to relax this retriction: