422 lines
22 KiB
HTML
422 lines
22 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>Generic Data Types</title>
|
||
</head>
|
||
<body>
|
||
<h2 id="generic-data-types"><a class="header" href="#generic-data-types">Generic Data Types</a></h2>
|
||
<p>We use generics to create definitions for items like function signatures or
|
||
structs, which we can then use with many different concrete data types. Let’s
|
||
first look at how to define functions, structs, enums, and methods using
|
||
generics. Then, we’ll discuss how generics affect code performance.</p>
|
||
<h3 id="in-function-definitions"><a class="header" href="#in-function-definitions">In Function Definitions</a></h3>
|
||
<p>When defining a function that uses generics, we place the generics in the
|
||
signature of the function where we would usually specify the data types of the
|
||
parameters and return value. Doing so makes our code more flexible and provides
|
||
more functionality to callers of our function while preventing code duplication.</p>
|
||
<p>Continuing with our <code>largest</code> function, Listing 10-4 shows two functions that
|
||
both find the largest value in a slice. We’ll then combine these into a single
|
||
function that uses generics.</p>
|
||
<figure class="listing" id="listing-10-4">
|
||
<span class="file-name">Filename: src/main.rs</span>
|
||
<pre class="playground"><code class="language-rust edition2024">fn largest_i32(list: &[i32]) -> &i32 {
|
||
let mut largest = &list[0];
|
||
|
||
for item in list {
|
||
if item > largest {
|
||
largest = item;
|
||
}
|
||
}
|
||
|
||
largest
|
||
}
|
||
|
||
fn largest_char(list: &[char]) -> &char {
|
||
let mut largest = &list[0];
|
||
|
||
for item in list {
|
||
if item > largest {
|
||
largest = item;
|
||
}
|
||
}
|
||
|
||
largest
|
||
}
|
||
|
||
fn main() {
|
||
let number_list = vec![34, 50, 25, 100, 65];
|
||
|
||
let result = largest_i32(&number_list);
|
||
println!("The largest number is {result}");
|
||
<span class="boring"> assert_eq!(*result, 100);
|
||
</span>
|
||
let char_list = vec!['y', 'm', 'a', 'q'];
|
||
|
||
let result = largest_char(&char_list);
|
||
println!("The largest char is {result}");
|
||
<span class="boring"> assert_eq!(*result, 'y');
|
||
</span>}</code></pre>
|
||
<figcaption><a href="#listing-10-4">Listing 10-4</a>: Two functions that differ only in their names and in the types in their signatures</figcaption>
|
||
</figure>
|
||
<p>The <code>largest_i32</code> function is the one we extracted in Listing 10-3 that finds
|
||
the largest <code>i32</code> in a slice. The <code>largest_char</code> function finds the largest
|
||
<code>char</code> in a slice. The function bodies have the same code, so let’s eliminate
|
||
the duplication by introducing a generic type parameter in a single function.</p>
|
||
<p>To parameterize the types in a new single function, we need to name the type
|
||
parameter, just as we do for the value parameters to a function. You can use
|
||
any identifier as a type parameter name. But we’ll use <code>T</code> because, by
|
||
convention, type parameter names in Rust are short, often just one letter, and
|
||
Rust’s type-naming convention is UpperCamelCase. Short for <em>type</em>, <code>T</code> is the
|
||
default choice of most Rust programmers.</p>
|
||
<p>When we use a parameter in the body of the function, we have to declare the
|
||
parameter name in the signature so that the compiler knows what that name
|
||
means. Similarly, when we use a type parameter name in a function signature, we
|
||
have to declare the type parameter name before we use it. To define the generic
|
||
<code>largest</code> function, we place type name declarations inside angle brackets,
|
||
<code><></code>, between the name of the function and the parameter list, like this:</p>
|
||
<pre><code class="language-rust ignore">fn largest<T>(list: &[T]) -> &T {</code></pre>
|
||
<p>We read this definition as “The function <code>largest</code> is generic over some type
|
||
<code>T</code>.” This function has one parameter named <code>list</code>, which is a slice of values
|
||
of type <code>T</code>. The <code>largest</code> function will return a reference to a value of the
|
||
same type <code>T</code>.</p>
|
||
<p>Listing 10-5 shows the combined <code>largest</code> function definition using the generic
|
||
data type in its signature. The listing also shows how we can call the function
|
||
with either a slice of <code>i32</code> values or <code>char</code> values. Note that this code won’t
|
||
compile yet.</p>
|
||
<figure class="listing" id="listing-10-5">
|
||
<span class="file-name">Filename: src/main.rs</span>
|
||
<pre><code class="language-rust ignore does_not_compile">fn largest<T>(list: &[T]) -> &T {
|
||
let mut largest = &list[0];
|
||
|
||
for item in list {
|
||
if item > largest {
|
||
largest = item;
|
||
}
|
||
}
|
||
|
||
largest
|
||
}
|
||
|
||
fn main() {
|
||
let number_list = vec![34, 50, 25, 100, 65];
|
||
|
||
let result = largest(&number_list);
|
||
println!("The largest number is {result}");
|
||
|
||
let char_list = vec!['y', 'm', 'a', 'q'];
|
||
|
||
let result = largest(&char_list);
|
||
println!("The largest char is {result}");
|
||
}</code></pre>
|
||
<figcaption><a href="#listing-10-5">Listing 10-5</a>: The <code>largest</code> function using generic type parameters; this doesn’t compile yet</figcaption>
|
||
</figure>
|
||
<p>If we compile this code right now, we’ll get this error:</p>
|
||
<pre><code class="language-console">$ cargo run
|
||
Compiling chapter10 v0.1.0 (file:///projects/chapter10)
|
||
error[E0369]: binary operation `>` cannot be applied to type `&T`
|
||
--> src/main.rs:5:17
|
||
|
|
||
5 | if item > largest {
|
||
| ---- ^ ------- &T
|
||
| |
|
||
| &T
|
||
|
|
||
help: consider restricting type parameter `T` with trait `PartialOrd`
|
||
|
|
||
1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
|
||
| ++++++++++++++++++++++
|
||
|
||
For more information about this error, try `rustc --explain E0369`.
|
||
error: could not compile `chapter10` (bin "chapter10") due to 1 previous error
|
||
</code></pre>
|
||
<p>The help text mentions <code>std::cmp::PartialOrd</code>, which is a trait, and we’re
|
||
going to talk about traits in the next section. For now, know that this error
|
||
states that the body of <code>largest</code> won’t work for all possible types that <code>T</code>
|
||
could be. Because we want to compare values of type <code>T</code> in the body, we can
|
||
only use types whose values can be ordered. To enable comparisons, the standard
|
||
library has the <code>std::cmp::PartialOrd</code> trait that you can implement on types
|
||
(see Appendix C for more on this trait). To fix Listing 10-5, we can follow the
|
||
help text’s suggestion and restrict the types valid for <code>T</code> to only those that
|
||
implement <code>PartialOrd</code>. The listing will then compile, because the standard
|
||
library implements <code>PartialOrd</code> on both <code>i32</code> and <code>char</code>.</p>
|
||
<h3 id="in-struct-definitions"><a class="header" href="#in-struct-definitions">In Struct Definitions</a></h3>
|
||
<p>We can also define structs to use a generic type parameter in one or more
|
||
fields using the <code><></code> syntax. Listing 10-6 defines a <code>Point<T></code> struct to hold
|
||
<code>x</code> and <code>y</code> coordinate values of any type.</p>
|
||
<figure class="listing" id="listing-10-6">
|
||
<span class="file-name">Filename: src/main.rs</span>
|
||
<pre class="playground"><code class="language-rust edition2024">struct Point<T> {
|
||
x: T,
|
||
y: T,
|
||
}
|
||
|
||
fn main() {
|
||
let integer = Point { x: 5, y: 10 };
|
||
let float = Point { x: 1.0, y: 4.0 };
|
||
}</code></pre>
|
||
<figcaption><a href="#listing-10-6">Listing 10-6</a>: A <code>Point<T></code> struct that holds <code>x</code> and <code>y</code> values of type <code>T</code></figcaption>
|
||
</figure>
|
||
<p>The syntax for using generics in struct definitions is similar to that used in
|
||
function definitions. First, we declare the name of the type parameter inside
|
||
angle brackets just after the name of the struct. Then, we use the generic type
|
||
in the struct definition where we would otherwise specify concrete data types.</p>
|
||
<p>Note that because we’ve used only one generic type to define <code>Point<T></code>, this
|
||
definition says that the <code>Point<T></code> struct is generic over some type <code>T</code>, and
|
||
the fields <code>x</code> and <code>y</code> are <em>both</em> that same type, whatever that type may be. If
|
||
we create an instance of a <code>Point<T></code> that has values of different types, as in
|
||
Listing 10-7, our code won’t compile.</p>
|
||
<figure class="listing" id="listing-10-7">
|
||
<span class="file-name">Filename: src/main.rs</span>
|
||
<pre><code class="language-rust ignore does_not_compile">struct Point<T> {
|
||
x: T,
|
||
y: T,
|
||
}
|
||
|
||
fn main() {
|
||
let wont_work = Point { x: 5, y: 4.0 };
|
||
}</code></pre>
|
||
<figcaption><a href="#listing-10-7">Listing 10-7</a>: The fields <code>x</code> and <code>y</code> must be the same type because both have the same generic data type <code>T</code>.</figcaption>
|
||
</figure>
|
||
<p>In this example, when we assign the integer value <code>5</code> to <code>x</code>, we let the
|
||
compiler know that the generic type <code>T</code> will be an integer for this instance of
|
||
<code>Point<T></code>. Then, when we specify <code>4.0</code> for <code>y</code>, which we’ve defined to have
|
||
the same type as <code>x</code>, we’ll get a type mismatch error like this:</p>
|
||
<pre><code class="language-console">$ cargo run
|
||
Compiling chapter10 v0.1.0 (file:///projects/chapter10)
|
||
error[E0308]: mismatched types
|
||
--> src/main.rs:7:38
|
||
|
|
||
7 | let wont_work = Point { x: 5, y: 4.0 };
|
||
| ^^^ expected integer, found floating-point number
|
||
|
||
For more information about this error, try `rustc --explain E0308`.
|
||
error: could not compile `chapter10` (bin "chapter10") due to 1 previous error
|
||
</code></pre>
|
||
<p>To define a <code>Point</code> struct where <code>x</code> and <code>y</code> are both generics but could have
|
||
different types, we can use multiple generic type parameters. For example, in
|
||
Listing 10-8, we change the definition of <code>Point</code> to be generic over types <code>T</code>
|
||
and <code>U</code> where <code>x</code> is of type <code>T</code> and <code>y</code> is of type <code>U</code>.</p>
|
||
<figure class="listing" id="listing-10-8">
|
||
<span class="file-name">Filename: src/main.rs</span>
|
||
<pre class="playground"><code class="language-rust edition2024">struct Point<T, U> {
|
||
x: T,
|
||
y: U,
|
||
}
|
||
|
||
fn main() {
|
||
let both_integer = Point { x: 5, y: 10 };
|
||
let both_float = Point { x: 1.0, y: 4.0 };
|
||
let integer_and_float = Point { x: 5, y: 4.0 };
|
||
}</code></pre>
|
||
<figcaption><a href="#listing-10-8">Listing 10-8</a>: A <code>Point<T, U></code> generic over two types so that <code>x</code> and <code>y</code> can be values of different types</figcaption>
|
||
</figure>
|
||
<p>Now all the instances of <code>Point</code> shown are allowed! You can use as many generic
|
||
type parameters in a definition as you want, but using more than a few makes
|
||
your code hard to read. If you’re finding you need lots of generic types in
|
||
your code, it could indicate that your code needs restructuring into smaller
|
||
pieces.</p>
|
||
<h3 id="in-enum-definitions"><a class="header" href="#in-enum-definitions">In Enum Definitions</a></h3>
|
||
<p>As we did with structs, we can define enums to hold generic data types in their
|
||
variants. Let’s take another look at the <code>Option<T></code> enum that the standard
|
||
library provides, which we used in Chapter 6:</p>
|
||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>enum Option<T> {
|
||
Some(T),
|
||
None,
|
||
}
|
||
<span class="boring">}</span></code></pre>
|
||
<p>This definition should now make more sense to you. As you can see, the
|
||
<code>Option<T></code> enum is generic over type <code>T</code> and has two variants: <code>Some</code>, which
|
||
holds one value of type <code>T</code>, and a <code>None</code> variant that doesn’t hold any value.
|
||
By using the <code>Option<T></code> enum, we can express the abstract concept of an
|
||
optional value, and because <code>Option<T></code> is generic, we can use this abstraction
|
||
no matter what the type of the optional value is.</p>
|
||
<p>Enums can use multiple generic types as well. The definition of the <code>Result</code>
|
||
enum that we used in Chapter 9 is one example:</p>
|
||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>enum Result<T, E> {
|
||
Ok(T),
|
||
Err(E),
|
||
}
|
||
<span class="boring">}</span></code></pre>
|
||
<p>The <code>Result</code> enum is generic over two types, <code>T</code> and <code>E</code>, and has two variants:
|
||
<code>Ok</code>, which holds a value of type <code>T</code>, and <code>Err</code>, which holds a value of type
|
||
<code>E</code>. This definition makes it convenient to use the <code>Result</code> enum anywhere we
|
||
have an operation that might succeed (return a value of some type <code>T</code>) or fail
|
||
(return an error of some type <code>E</code>). In fact, this is what we used to open a
|
||
file in Listing 9-3, where <code>T</code> was filled in with the type <code>std::fs::File</code> when
|
||
the file was opened successfully and <code>E</code> was filled in with the type
|
||
<code>std::io::Error</code> when there were problems opening the file.</p>
|
||
<p>When you recognize situations in your code with multiple struct or enum
|
||
definitions that differ only in the types of the values they hold, you can
|
||
avoid duplication by using generic types instead.</p>
|
||
<h3 id="in-method-definitions"><a class="header" href="#in-method-definitions">In Method Definitions</a></h3>
|
||
<p>We can implement methods on structs and enums (as we did in Chapter 5) and use
|
||
generic types in their definitions too. Listing 10-9 shows the <code>Point<T></code>
|
||
struct we defined in Listing 10-6 with a method named <code>x</code> implemented on it.</p>
|
||
<figure class="listing" id="listing-10-9">
|
||
<span class="file-name">Filename: src/main.rs</span>
|
||
<pre class="playground"><code class="language-rust edition2024">struct Point<T> {
|
||
x: T,
|
||
y: T,
|
||
}
|
||
|
||
impl<T> Point<T> {
|
||
fn x(&self) -> &T {
|
||
&self.x
|
||
}
|
||
}
|
||
|
||
fn main() {
|
||
let p = Point { x: 5, y: 10 };
|
||
|
||
println!("p.x = {}", p.x());
|
||
}</code></pre>
|
||
<figcaption><a href="#listing-10-9">Listing 10-9</a>: Implementing a method named <code>x</code> on the <code>Point<T></code> struct that will return a reference to the <code>x</code> field of type <code>T</code></figcaption>
|
||
</figure>
|
||
<p>Here, we’ve defined a method named <code>x</code> on <code>Point<T></code> that returns a reference
|
||
to the data in the field <code>x</code>.</p>
|
||
<p>Note that we have to declare <code>T</code> just after <code>impl</code> so that we can use <code>T</code> to
|
||
specify that we’re implementing methods on the type <code>Point<T></code>. By declaring
|
||
<code>T</code> as a generic type after <code>impl</code>, Rust can identify that the type in the
|
||
angle brackets in <code>Point</code> is a generic type rather than a concrete type. We
|
||
could have chosen a different name for this generic parameter than the generic
|
||
parameter declared in the struct definition, but using the same name is
|
||
conventional. If you write a method within an <code>impl</code> that declares a generic
|
||
type, that method will be defined on any instance of the type, no matter what
|
||
concrete type ends up substituting for the generic type.</p>
|
||
<p>We can also specify constraints on generic types when defining methods on the
|
||
type. We could, for example, implement methods only on <code>Point<f32></code> instances
|
||
rather than on <code>Point<T></code> instances with any generic type. In Listing 10-10, we
|
||
use the concrete type <code>f32</code>, meaning we don’t declare any types after <code>impl</code>.</p>
|
||
<figure class="listing" id="listing-10-10">
|
||
<span class="file-name">Filename: src/main.rs</span>
|
||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">struct Point<T> {
|
||
</span><span class="boring"> x: T,
|
||
</span><span class="boring"> y: T,
|
||
</span><span class="boring">}
|
||
</span><span class="boring">
|
||
</span><span class="boring">impl<T> Point<T> {
|
||
</span><span class="boring"> fn x(&self) -> &T {
|
||
</span><span class="boring"> &self.x
|
||
</span><span class="boring"> }
|
||
</span><span class="boring">}
|
||
</span><span class="boring">
|
||
</span>impl Point<f32> {
|
||
fn distance_from_origin(&self) -> f32 {
|
||
(self.x.powi(2) + self.y.powi(2)).sqrt()
|
||
}
|
||
}
|
||
<span class="boring">
|
||
</span><span class="boring">fn main() {
|
||
</span><span class="boring"> let p = Point { x: 5, y: 10 };
|
||
</span><span class="boring">
|
||
</span><span class="boring"> println!("p.x = {}", p.x());
|
||
</span><span class="boring">}</span></code></pre>
|
||
<figcaption><a href="#listing-10-10">Listing 10-10</a>: An <code>impl</code> block that only applies to a struct with a particular concrete type for the generic type parameter <code>T</code></figcaption>
|
||
</figure>
|
||
<p>This code means the type <code>Point<f32></code> will have a <code>distance_from_origin</code>
|
||
method; other instances of <code>Point<T></code> where <code>T</code> is not of type <code>f32</code> will not
|
||
have this method defined. The method measures how far our point is from the
|
||
point at coordinates (0.0, 0.0) and uses mathematical operations that are
|
||
available only for floating-point types.</p>
|
||
<p>Generic type parameters in a struct definition aren’t always the same as those
|
||
you use in that same struct’s method signatures. Listing 10-11 uses the generic
|
||
types <code>X1</code> and <code>Y1</code> for the <code>Point</code> struct and <code>X2</code> and <code>Y2</code> for the <code>mixup</code>
|
||
method signature to make the example clearer. The method creates a new <code>Point</code>
|
||
instance with the <code>x</code> value from the <code>self</code> <code>Point</code> (of type <code>X1</code>) and the <code>y</code>
|
||
value from the passed-in <code>Point</code> (of type <code>Y2</code>).</p>
|
||
<figure class="listing" id="listing-10-11">
|
||
<span class="file-name">Filename: src/main.rs</span>
|
||
<pre class="playground"><code class="language-rust edition2024">struct Point<X1, Y1> {
|
||
x: X1,
|
||
y: Y1,
|
||
}
|
||
|
||
impl<X1, Y1> Point<X1, Y1> {
|
||
fn mixup<X2, Y2>(self, other: Point<X2, Y2>) -> Point<X1, Y2> {
|
||
Point {
|
||
x: self.x,
|
||
y: other.y,
|
||
}
|
||
}
|
||
}
|
||
|
||
fn main() {
|
||
let p1 = Point { x: 5, y: 10.4 };
|
||
let p2 = Point { x: "Hello", y: 'c' };
|
||
|
||
let p3 = p1.mixup(p2);
|
||
|
||
println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
|
||
}</code></pre>
|
||
<figcaption><a href="#listing-10-11">Listing 10-11</a>: A method that uses generic types that are different from its struct’s definition</figcaption>
|
||
</figure>
|
||
<p>In <code>main</code>, we’ve defined a <code>Point</code> that has an <code>i32</code> for <code>x</code> (with value <code>5</code>)
|
||
and an <code>f64</code> for <code>y</code> (with value <code>10.4</code>). The <code>p2</code> variable is a <code>Point</code> struct
|
||
that has a string slice for <code>x</code> (with value <code>"Hello"</code>) and a <code>char</code> for <code>y</code>
|
||
(with value <code>c</code>). Calling <code>mixup</code> on <code>p1</code> with the argument <code>p2</code> gives us <code>p3</code>,
|
||
which will have an <code>i32</code> for <code>x</code> because <code>x</code> came from <code>p1</code>. The <code>p3</code> variable
|
||
will have a <code>char</code> for <code>y</code> because <code>y</code> came from <code>p2</code>. The <code>println!</code> macro
|
||
call will print <code>p3.x = 5, p3.y = c</code>.</p>
|
||
<p>The purpose of this example is to demonstrate a situation in which some generic
|
||
parameters are declared with <code>impl</code> and some are declared with the method
|
||
definition. Here, the generic parameters <code>X1</code> and <code>Y1</code> are declared after
|
||
<code>impl</code> because they go with the struct definition. The generic parameters <code>X2</code>
|
||
and <code>Y2</code> are declared after <code>fn mixup</code> because they’re only relevant to the
|
||
method.</p>
|
||
<h3 id="performance-of-code-using-generics"><a class="header" href="#performance-of-code-using-generics">Performance of Code Using Generics</a></h3>
|
||
<p>You might be wondering whether there is a runtime cost when using generic type
|
||
parameters. The good news is that using generic types won’t make your program
|
||
run any slower than it would with concrete types.</p>
|
||
<p>Rust accomplishes this by performing monomorphization of the code using
|
||
generics at compile time. <em>Monomorphization</em> is the process of turning generic
|
||
code into specific code by filling in the concrete types that are used when
|
||
compiled. In this process, the compiler does the opposite of the steps we used
|
||
to create the generic function in Listing 10-5: The compiler looks at all the
|
||
places where generic code is called and generates code for the concrete types
|
||
the generic code is called with.</p>
|
||
<p>Let’s look at how this works by using the standard library’s generic
|
||
<code>Option<T></code> enum:</p>
|
||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>let integer = Some(5);
|
||
let float = Some(5.0);
|
||
<span class="boring">}</span></code></pre>
|
||
<p>When Rust compiles this code, it performs monomorphization. During that
|
||
process, the compiler reads the values that have been used in <code>Option<T></code>
|
||
instances and identifies two kinds of <code>Option<T></code>: One is <code>i32</code> and the other
|
||
is <code>f64</code>. As such, it expands the generic definition of <code>Option<T></code> into two
|
||
definitions specialized to <code>i32</code> and <code>f64</code>, thereby replacing the generic
|
||
definition with the specific ones.</p>
|
||
<p>The monomorphized version of the code looks similar to the following (the
|
||
compiler uses different names than what we’re using here for illustration):</p>
|
||
<figure class="listing">
|
||
<span class="file-name">Filename: src/main.rs</span>
|
||
<pre class="playground"><code class="language-rust edition2024">enum Option_i32 {
|
||
Some(i32),
|
||
None,
|
||
}
|
||
|
||
enum Option_f64 {
|
||
Some(f64),
|
||
None,
|
||
}
|
||
|
||
fn main() {
|
||
let integer = Option_i32::Some(5);
|
||
let float = Option_f64::Some(5.0);
|
||
}</code></pre>
|
||
</figure>
|
||
<p>The generic <code>Option<T></code> is replaced with the specific definitions created by
|
||
the compiler. Because Rust compiles generic code into code that specifies the
|
||
type in each instance, we pay no runtime cost for using generics. When the code
|
||
runs, it performs just as it would if we had duplicated each definition by
|
||
hand. The process of monomorphization makes Rust’s generics extremely efficient
|
||
at runtime.</p>
|
||
</body>
|
||
</html>
|