Files
docs-rust/ch20/ch20-03-advanced-types.html
2026-06-22 21:27:36 +05:30

353 lines
22 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Advanced Types</title>
</head>
<body>
<h2 id="advanced-types"><a class="header" href="#advanced-types">Advanced Types</a></h2>
<p>The Rust type system has some features that weve so far mentioned but havent
yet discussed. Well start by discussing newtypes in general as we examine why
they are useful as types. Then, well move on to type aliases, a feature
similar to newtypes but with slightly different semantics. Well also discuss
the <code>!</code> type and dynamically sized types.</p>
<!-- Old headings. Do not remove or links may break. -->
<p><a id="using-the-newtype-pattern-for-type-safety-and-abstraction"></a></p>
<h3 id="type-safety-and-abstraction-with-the-newtype-pattern"><a class="header" href="#type-safety-and-abstraction-with-the-newtype-pattern">Type Safety and Abstraction with the Newtype Pattern</a></h3>
<p>This section assumes youve read the earlier section <a href="ch20-02-advanced-traits.html#implementing-external-traits-with-the-newtype-pattern">“Implementing External
Traits with the Newtype Pattern”</a><!-- ignore -->. The newtype pattern
is also useful for tasks beyond those weve discussed so far, including
statically enforcing that values are never confused and indicating the units of
a value. You saw an example of using newtypes to indicate units in Listing
20-16: Recall that the <code>Millimeters</code> and <code>Meters</code> structs wrapped <code>u32</code> values
in a newtype. If we wrote a function with a parameter of type <code>Millimeters</code>, we
wouldnt be able to compile a program that accidentally tried to call that
function with a value of type <code>Meters</code> or a plain <code>u32</code>.</p>
<p>We can also use the newtype pattern to abstract away some implementation
details of a type: The new type can expose a public API that is different from
the API of the private inner type.</p>
<p>Newtypes can also hide internal implementation. For example, we could provide a
<code>People</code> type to wrap a <code>HashMap&lt;i32, String&gt;</code> that stores a persons ID
associated with their name. Code using <code>People</code> would only interact with the
public API we provide, such as a method to add a name string to the <code>People</code>
collection; that code wouldnt need to know that we assign an <code>i32</code> ID to names
internally. The newtype pattern is a lightweight way to achieve encapsulation
to hide implementation details, which we discussed in the <a href="../ch18/ch18-01-what-is-oo.html#encapsulation-that-hides-implementation-details">“Encapsulation that
Hides Implementation
Details”</a><!-- ignore -->
section in Chapter 18.</p>
<!-- Old headings. Do not remove or links may break. -->
<p><a id="creating-type-synonyms-with-type-aliases"></a></p>
<h3 id="type-synonyms-and-type-aliases"><a class="header" href="#type-synonyms-and-type-aliases">Type Synonyms and Type Aliases</a></h3>
<p>Rust provides the ability to declare a <em>type alias</em> to give an existing type
another name. For this we use the <code>type</code> keyword. For example, we can create
the alias <code>Kilometers</code> to <code>i32</code> like so:</p>
<pre class="playground"><code class="language-rust edition2024"><span class="boring">fn main() {
</span> type Kilometers = i32;
<span class="boring">
</span><span class="boring"> let x: i32 = 5;
</span><span class="boring"> let y: Kilometers = 5;
</span><span class="boring">
</span><span class="boring"> println!("x + y = {}", x + y);
</span><span class="boring">}</span></code></pre>
<p>Now the alias <code>Kilometers</code> is a <em>synonym</em> for <code>i32</code>; unlike the <code>Millimeters</code>
and <code>Meters</code> types we created in Listing 20-16, <code>Kilometers</code> is not a separate,
new type. Values that have the type <code>Kilometers</code> will be treated the same as
values of type <code>i32</code>:</p>
<pre class="playground"><code class="language-rust edition2024"><span class="boring">fn main() {
</span> type Kilometers = i32;
let x: i32 = 5;
let y: Kilometers = 5;
println!("x + y = {}", x + y);
<span class="boring">}</span></code></pre>
<p>Because <code>Kilometers</code> and <code>i32</code> are the same type, we can add values of both
types and can pass <code>Kilometers</code> values to functions that take <code>i32</code>
parameters. However, using this method, we dont get the type-checking benefits
that we get from the newtype pattern discussed earlier. In other words, if we
mix up <code>Kilometers</code> and <code>i32</code> values somewhere, the compiler will not give us
an error.</p>
<p>The main use case for type synonyms is to reduce repetition. For example, we
might have a lengthy type like this:</p>
<pre><code class="language-rust ignore">Box&lt;dyn Fn() + Send + 'static&gt;</code></pre>
<p>Writing this lengthy type in function signatures and as type annotations all
over the code can be tiresome and error-prone. Imagine having a project full of
code like that in Listing 20-25.</p>
<figure class="listing" id="listing-20-25">
<pre class="playground"><code class="language-rust edition2024"><span class="boring">fn main() {
</span> let f: Box&lt;dyn Fn() + Send + 'static&gt; = Box::new(|| println!("hi"));
fn takes_long_type(f: Box&lt;dyn Fn() + Send + 'static&gt;) {
// --snip--
}
fn returns_long_type() -&gt; Box&lt;dyn Fn() + Send + 'static&gt; {
// --snip--
<span class="boring"> Box::new(|| ())
</span> }
<span class="boring">}</span></code></pre>
<figcaption><a href="#listing-20-25">Listing 20-25</a>: Using a long type in many places</figcaption>
</figure>
<p>A type alias makes this code more manageable by reducing the repetition. In
Listing 20-26, weve introduced an alias named <code>Thunk</code> for the verbose type and
can replace all uses of the type with the shorter alias <code>Thunk</code>.</p>
<figure class="listing" id="listing-20-26">
<pre class="playground"><code class="language-rust edition2024"><span class="boring">fn main() {
</span> type Thunk = Box&lt;dyn Fn() + Send + 'static&gt;;
let f: Thunk = Box::new(|| println!("hi"));
fn takes_long_type(f: Thunk) {
// --snip--
}
fn returns_long_type() -&gt; Thunk {
// --snip--
<span class="boring"> Box::new(|| ())
</span> }
<span class="boring">}</span></code></pre>
<figcaption><a href="#listing-20-26">Listing 20-26</a>: Introducing a type alias, <code>Thunk</code>, to reduce repetition</figcaption>
</figure>
<p>This code is much easier to read and write! Choosing a meaningful name for a
type alias can help communicate your intent as well (<em>thunk</em> is a word for code
to be evaluated at a later time, so its an appropriate name for a closure that
gets stored).</p>
<p>Type aliases are also commonly used with the <code>Result&lt;T, E&gt;</code> type for reducing
repetition. Consider the <code>std::io</code> module in the standard library. I/O
operations often return a <code>Result&lt;T, E&gt;</code> to handle situations when operations
fail to work. This library has a <code>std::io::Error</code> struct that represents all
possible I/O errors. Many of the functions in <code>std::io</code> will be returning
<code>Result&lt;T, E&gt;</code> where the <code>E</code> is <code>std::io::Error</code>, such as these functions in
the <code>Write</code> trait:</p>
<pre><code class="language-rust noplayground">use std::fmt;
use std::io::Error;
pub trait Write {
fn write(&amp;mut self, buf: &amp;[u8]) -&gt; Result&lt;usize, Error&gt;;
fn flush(&amp;mut self) -&gt; Result&lt;(), Error&gt;;
fn write_all(&amp;mut self, buf: &amp;[u8]) -&gt; Result&lt;(), Error&gt;;
fn write_fmt(&amp;mut self, fmt: fmt::Arguments) -&gt; Result&lt;(), Error&gt;;
}</code></pre>
<p>The <code>Result&lt;..., Error&gt;</code> is repeated a lot. As such, <code>std::io</code> has this type
alias declaration:</p>
<pre><code class="language-rust noplayground"><span class="boring">use std::fmt;
</span><span class="boring">
</span>type Result&lt;T&gt; = std::result::Result&lt;T, std::io::Error&gt;;
<span class="boring">
</span><span class="boring">pub trait Write {
</span><span class="boring"> fn write(&amp;mut self, buf: &amp;[u8]) -&gt; Result&lt;usize&gt;;
</span><span class="boring"> fn flush(&amp;mut self) -&gt; Result&lt;()&gt;;
</span><span class="boring">
</span><span class="boring"> fn write_all(&amp;mut self, buf: &amp;[u8]) -&gt; Result&lt;()&gt;;
</span><span class="boring"> fn write_fmt(&amp;mut self, fmt: fmt::Arguments) -&gt; Result&lt;()&gt;;
</span><span class="boring">}</span></code></pre>
<p>Because this declaration is in the <code>std::io</code> module, we can use the fully
qualified alias <code>std::io::Result&lt;T&gt;</code>; that is, a <code>Result&lt;T, E&gt;</code> with the <code>E</code>
filled in as <code>std::io::Error</code>. The <code>Write</code> trait function signatures end up
looking like this:</p>
<pre><code class="language-rust noplayground"><span class="boring">use std::fmt;
</span><span class="boring">
</span><span class="boring">type Result&lt;T&gt; = std::result::Result&lt;T, std::io::Error&gt;;
</span><span class="boring">
</span>pub trait Write {
fn write(&amp;mut self, buf: &amp;[u8]) -&gt; Result&lt;usize&gt;;
fn flush(&amp;mut self) -&gt; Result&lt;()&gt;;
fn write_all(&amp;mut self, buf: &amp;[u8]) -&gt; Result&lt;()&gt;;
fn write_fmt(&amp;mut self, fmt: fmt::Arguments) -&gt; Result&lt;()&gt;;
}</code></pre>
<p>The type alias helps in two ways: It makes code easier to write <em>and</em> it gives
us a consistent interface across all of <code>std::io</code>. Because its an alias, its
just another <code>Result&lt;T, E&gt;</code>, which means we can use any methods that work on
<code>Result&lt;T, E&gt;</code> with it, as well as special syntax like the <code>?</code> operator.</p>
<h3 id="the-never-type-that-never-returns"><a class="header" href="#the-never-type-that-never-returns">The Never Type That Never Returns</a></h3>
<p>Rust has a special type named <code>!</code> thats known in type theory lingo as the
<em>empty type</em> because it has no values. We prefer to call it the <em>never type</em>
because it stands in the place of the return type when a function will never
return. Here is an example:</p>
<pre><code class="language-rust noplayground">fn bar() -&gt; ! {
// --snip--
<span class="boring"> panic!();
</span>}</code></pre>
<p>This code is read as “the function <code>bar</code> returns never.” Functions that return
never are called <em>diverging functions</em>. We cant create values of the type <code>!</code>,
so <code>bar</code> can never possibly return.</p>
<p>But what use is a type you can never create values for? Recall the code from
Listing 2-5, part of the number-guessing game; weve reproduced a bit of it
here in Listing 20-27.</p>
<figure class="listing" id="listing-20-27">
<pre><code class="language-rust ignore"><span class="boring">use std::cmp::Ordering;
</span><span class="boring">use std::io;
</span><span class="boring">
</span><span class="boring">use rand::Rng;
</span><span class="boring">
</span><span class="boring">fn main() {
</span><span class="boring"> println!("Guess the number!");
</span><span class="boring">
</span><span class="boring"> let secret_number = rand::thread_rng().gen_range(1..=100);
</span><span class="boring">
</span><span class="boring"> println!("The secret number is: {secret_number}");
</span><span class="boring">
</span><span class="boring"> loop {
</span><span class="boring"> println!("Please input your guess.");
</span><span class="boring">
</span><span class="boring"> let mut guess = String::new();
</span><span class="boring">
</span><span class="boring"> // --snip--
</span><span class="boring">
</span><span class="boring"> io::stdin()
</span><span class="boring"> .read_line(&amp;mut guess)
</span><span class="boring"> .expect("Failed to read line");
</span><span class="boring">
</span> let guess: u32 = match guess.trim().parse() {
Ok(num) =&gt; num,
Err(_) =&gt; continue,
};
<span class="boring">
</span><span class="boring"> println!("You guessed: {guess}");
</span><span class="boring">
</span><span class="boring"> // --snip--
</span><span class="boring">
</span><span class="boring"> match guess.cmp(&amp;secret_number) {
</span><span class="boring"> Ordering::Less =&gt; println!("Too small!"),
</span><span class="boring"> Ordering::Greater =&gt; println!("Too big!"),
</span><span class="boring"> Ordering::Equal =&gt; {
</span><span class="boring"> println!("You win!");
</span><span class="boring"> break;
</span><span class="boring"> }
</span><span class="boring"> }
</span><span class="boring"> }
</span><span class="boring">}</span></code></pre>
<figcaption><a href="#listing-20-27">Listing 20-27</a>: A <code>match</code> with an arm that ends in <code>continue</code></figcaption>
</figure>
<p>At the time, we skipped over some details in this code. In <a href="../ch06/ch06-02-match.html#the-match-control-flow-construct">“The <code>match</code>
Control Flow Construct”</a><!-- ignore -->
section in Chapter 6, we discussed that <code>match</code> arms must all return the same
type. So, for example, the following code doesnt work:</p>
<pre><code class="language-rust ignore does_not_compile"><span class="boring">fn main() {
</span><span class="boring"> let guess = "3";
</span> let guess = match guess.trim().parse() {
Ok(_) =&gt; 5,
Err(_) =&gt; "hello",
};
<span class="boring">}</span></code></pre>
<p>The type of <code>guess</code> in this code would have to be an integer <em>and</em> a string,
and Rust requires that <code>guess</code> have only one type. So, what does <code>continue</code>
return? How were we allowed to return a <code>u32</code> from one arm and have another arm
that ends with <code>continue</code> in Listing 20-27?</p>
<p>As you might have guessed, <code>continue</code> has a <code>!</code> value. That is, when Rust
computes the type of <code>guess</code>, it looks at both match arms, the former with a
value of <code>u32</code> and the latter with a <code>!</code> value. Because <code>!</code> can never have a
value, Rust decides that the type of <code>guess</code> is <code>u32</code>.</p>
<p>The formal way of describing this behavior is that expressions of type <code>!</code> can
be coerced into any other type. Were allowed to end this <code>match</code> arm with
<code>continue</code> because <code>continue</code> doesnt return a value; instead, it moves control
back to the top of the loop, so in the <code>Err</code> case, we never assign a value to
<code>guess</code>.</p>
<p>The never type is useful with the <code>panic!</code> macro as well. Recall the <code>unwrap</code>
function that we call on <code>Option&lt;T&gt;</code> values to produce a value or panic with
this definition:</p>
<pre><code class="language-rust ignore"><span class="boring">enum Option&lt;T&gt; {
</span><span class="boring"> Some(T),
</span><span class="boring"> None,
</span><span class="boring">}
</span><span class="boring">
</span><span class="boring">use crate::Option::*;
</span><span class="boring">
</span>impl&lt;T&gt; Option&lt;T&gt; {
pub fn unwrap(self) -&gt; T {
match self {
Some(val) =&gt; val,
None =&gt; panic!("called `Option::unwrap()` on a `None` value"),
}
}
}</code></pre>
<p>In this code, the same thing happens as in the <code>match</code> in Listing 20-27: Rust
sees that <code>val</code> has the type <code>T</code> and <code>panic!</code> has the type <code>!</code>, so the result
of the overall <code>match</code> expression is <code>T</code>. This code works because <code>panic!</code>
doesnt produce a value; it ends the program. In the <code>None</code> case, we wont be
returning a value from <code>unwrap</code>, so this code is valid.</p>
<p>One final expression that has the type <code>!</code> is a loop:</p>
<pre><code class="language-rust ignore"><span class="boring">fn main() {
</span> print!("forever ");
loop {
print!("and ever ");
}
<span class="boring">}</span></code></pre>
<p>Here, the loop never ends, so <code>!</code> is the value of the expression. However, this
wouldnt be true if we included a <code>break</code>, because the loop would terminate
when it got to the <code>break</code>.</p>
<h3 id="dynamically-sized-types-and-the-sized-trait"><a class="header" href="#dynamically-sized-types-and-the-sized-trait">Dynamically Sized Types and the <code>Sized</code> Trait</a></h3>
<p>Rust needs to know certain details about its types, such as how much space to
allocate for a value of a particular type. This leaves one corner of its type
system a little confusing at first: the concept of <em>dynamically sized types</em>.
Sometimes referred to as <em>DSTs</em> or <em>unsized types</em>, these types let us write
code using values whose size we can know only at runtime.</p>
<p>Lets dig into the details of a dynamically sized type called <code>str</code>, which
weve been using throughout the book. Thats right, not <code>&amp;str</code>, but <code>str</code> on
its own, is a DST. In many cases, such as when storing text entered by a user,
we cant know how long the string is until runtime. That means we cant create
a variable of type <code>str</code>, nor can we take an argument of type <code>str</code>. Consider
the following code, which does not work:</p>
<pre><code class="language-rust ignore does_not_compile"><span class="boring">fn main() {
</span> let s1: str = "Hello there!";
let s2: str = "How's it going?";
<span class="boring">}</span></code></pre>
<p>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. If Rust
allowed us to write this code, these two <code>str</code> values would need to take up the
same amount of space. But they have different lengths: <code>s1</code> needs 12 bytes of
storage and <code>s2</code> needs 15. This is why its not possible to create a variable
holding a dynamically sized type.</p>
<p>So, what do we do? In this case, you already know the answer: We make the type
of <code>s1</code> and <code>s2</code> string slice (<code>&amp;str</code>) rather than <code>str</code>. Recall from the
<a href="../ch04/ch04-03-slices.html#string-slices">“String Slices”</a><!-- ignore --> section in Chapter 4 that the
slice data structure only stores the starting position and the length of the
slice. So, although <code>&amp;T</code> is a single value that stores the memory address of
where the <code>T</code> is located, a string slice is <em>two</em> values: the address of the
<code>str</code> and its length. As such, we can know the size of a string slice value at
compile time: Its twice the length of a <code>usize</code>. That is, we always know the
size of a string slice, 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.</p>
<p>We can combine <code>str</code> with all kinds of pointers: for example, <code>Box&lt;str&gt;</code> or
<code>Rc&lt;str&gt;</code>. In fact, youve seen this before but with a different dynamically
sized type: traits. Every trait is a dynamically sized type we can refer to by
using the name of the trait. In the <a href="../ch18/ch18-02-trait-objects.html#using-trait-objects-to-abstract-over-shared-behavior">“Using Trait Objects to Abstract over
Shared Behavior”</a><!--
ignore --> section in Chapter 18, we mentioned that to use traits as trait
objects, we must put them behind a pointer, such as <code>&amp;dyn Trait</code> or <code>Box&lt;dyn Trait&gt;</code> (<code>Rc&lt;dyn Trait&gt;</code> would work too).</p>
<p>To work with DSTs, Rust provides the <code>Sized</code> trait to determine whether or not
a types size is known at compile time. This trait is automatically implemented
for everything whose size is known at compile time. In addition, Rust
implicitly adds a bound on <code>Sized</code> to every generic function. That is, a
generic function definition like this:</p>
<pre><code class="language-rust ignore">fn generic&lt;T&gt;(t: T) {
// --snip--
}</code></pre>
<p>is actually treated as though we had written this:</p>
<pre><code class="language-rust ignore">fn generic&lt;T: Sized&gt;(t: T) {
// --snip--
}</code></pre>
<p>By default, generic functions will work only on types that have a known size at
compile time. However, you can use the following special syntax to relax this
restriction:</p>
<pre><code class="language-rust ignore">fn generic&lt;T: ?Sized&gt;(t: &amp;T) {
// --snip--
}</code></pre>
<p>A trait bound on <code>?Sized</code> means “<code>T</code> may or may not be <code>Sized</code>,” and this
notation overrides the default that generic types must have a known size at
compile time. The <code>?Trait</code> syntax with this meaning is only available for
<code>Sized</code>, not any other traits.</p>
<p>Also note that we switched the type of the <code>t</code> parameter from <code>T</code> to <code>&amp;T</code>.
Because the type might not be <code>Sized</code>, we need to use it behind some kind of
pointer. In this case, weve chosen a reference.</p>
<p>Next, well talk about functions and closures!</p>
</body>
</html>