From 4888172a74137963985087db3d992008a5c7658d Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Fri, 27 Jan 2017 21:02:03 -0500 Subject: [PATCH] Rearrange sections to match the structure change made in the docx This also makes chapter 6 more like the other chapters, with the intro page pretty short. --- src/SUMMARY.md | 2 +- src/ch06-00-enums.md | 273 --------------------- src/ch06-01-defining-an-enum.md | 415 ++++++++++++++++++++++++++++++++ src/ch06-01-option.md | 141 ----------- 4 files changed, 416 insertions(+), 415 deletions(-) create mode 100644 src/ch06-01-defining-an-enum.md delete mode 100644 src/ch06-01-option.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 3fa23b3..f0fda29 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -24,7 +24,7 @@ - [Method Syntax](ch05-01-method-syntax.md) - [Enums](ch06-00-enums.md) - - [Option](ch06-01-option.md) + - [Defining an Enum](ch06-01-defining-an-enum.md) - [Match](ch06-02-match.md) - [`if let`](ch06-03-if-let.md) diff --git a/src/ch06-00-enums.md b/src/ch06-00-enums.md index b0808a1..ef01064 100644 --- a/src/ch06-00-enums.md +++ b/src/ch06-00-enums.md @@ -13,276 +13,3 @@ code. Enums are a feature in many languages, but their capabilities differ per-language. Rust’s enums are most similar to "algebraic data types" in functional languages like F#, OCaml, or Haskell. - -## Defining an Enum - -Let's look at a situation we might want to express in code and see why enums are -sometimes a more appropriate choice than structs for modeling data. Say we need -to work with IP addresses. There are two major standards used for IP addresses -today: version four and version six. These are the only possibilities for an IP -address that our program will come across: we can *enumerate* all possible -values, which is where *enumeration* gets its name. - -Any IP address can be either a version four or a version six address, but not -both at the same time. That property of IP addresses makes the enum data -structure appropriate for this case, since enum values can only be one of the -variants. Both version four and version six addresses are still fundamentally -IP addresses, though, so they should be treated as the same type when the code -is handling situations that apply to any kind of IP address. - -We can express this concept in code by defining an `IpAddrKind` enumeration and -listing the possible kinds an IP address can be, `V4` and `V6`. These are known -as the *variants* of the enum: - -```rust -enum IpAddrKind { - V4, - V6, -} -``` - -This is now a custom data type that we can use elsewhere in our code. - -### Enum Values - - -An `enum` definition is similar to a `struct` definition: it defines a new type -and a template of what instances of that new type will be like. When you want to -use a struct, you create an instance of the struct. When you want to use an -enum, you create an instance of the enum that is one of the variants the enum -allows. - - -When you want to use a struct, you create an instance of the *struct* itself. -When you want to use an enum, you create an instance of one of its *variants*. -Each variant is defined like a struct, and you instantiate both using the same -syntax. - - - -We can create instances of each of the two variants of `IpAddrKind` like this: - -```rust -# enum IpAddrKind { -# V4, -# V6, -# } -# -let four = IpAddrKind::V4; -let six = IpAddrKind::V6; -``` - -Note that the variants of the enum are namespaced under its identifier, and we -use the double colon to separate the two. The reason this is useful is that now -both values `IpAddrKind::V4` and `IpAddrKind::V6` are of the same type: -`IpAddrKind`. We can then, for instance, define a function that takes any -`IpAddrKind`: - -```rust -# enum IpAddrKind { -# V4, -# V6, -# } -# -fn route(ip_type: IpAddrKind) { } -``` - -And we can call this function with either variant: - -```rust -# enum IpAddrKind { -# V4, -# V6, -# } -# -# fn route(ip_type: IpAddrKind) { } -# -route(IpAddrKind::V4); -route(IpAddrKind::V6); -``` - -Enums have more tricks up their sleeves, too. Thinking more about our IP -address type, at the moment we don’t have a way to store the actual *data* of -the IP address; we only know what *kind* it is. Given that you just learned -about structs, you might tackle this problem as in Listing 6-1: - -
- -```rust -enum IpAddrKind { - V4, - V6, -} - -struct IpAddr { - kind: IpAddrKind, - address: String, -} - -let home = IpAddr { - kind: IpAddrKind::V4, - address: String::from("127.0.0.1"), -}; - -let loopback = IpAddr { - kind: IpAddrKind::V6, - address: String::from("::1"), -}; -``` - -
- -Listing 6-1: Storing the data and type of an IP address using a `struct` - -
-
- -Here, we've defined a struct `IpAddr` that has two fields: a `kind` field that -is of type `IpAddrKind` (the enum we defined previously), and an `address` -field of type `String`. We have two instances of this struct. The first, -`home`, has the value `IpAddrKind::V4` as its `kind`, with associated address -data of `127.0.0.1`. The second instance, `loopback`, has the other variant of -`IpAddrKind` as its `kind` value, `V6`, and has address `::1` associated with -it. We’ve used a struct to bundle the `kind` and `address` values together, so -that now the kind is associated with the value itself. - -We can represent the same concept in a more concise way using just an enum, -rather than an enum inside a struct, by putting data directly into each enum -variant. This new definition of the `IpAddr` enum says that both `V4` and `V6` -variants will have associated `String` values: - -```rust -enum IpAddr { - V4(String), - V6(String), -} - -let home = IpAddr::V4(String::from("127.0.0.1")); - -let loopback = IpAddr::V6(String::from("::1")); -``` - -We attach data to each variant of the enum directly, no need for an extra -struct. - -There's another advantage to using an enum over a struct: each variant can -store *different kinds* of data. Version four type IP addresses will always -have four numeric components that will have values between 0 and 255. If we -wanted to store `V4` addresses as four `u8`s but still express `V6` addresses -as `String`s, we wouldn't be able to with a `struct`. Enums handle this case -with ease: - -```rust -enum IpAddr { - V4(u8, u8, u8, u8), - V6(String), -} - -let home = IpAddr::V4(127, 0, 0, 1); - -let loopback = IpAddr::V6(String::from("::1")); -``` - -We've been showing a bunch of different possibilities that we could define in -our code for storing IP addresses of the two different kinds using an enum. It -turns out, though, that wanting to store IP addresses and encode which kind -they are is so common that [the standard library has a definition we can -use!][IpAddr] Let's look at how the standard library defines -`IpAddr`: it has the exact enum and variants that we've defined and used, but -it chose to embed the address data inside the variants in the form of two -different structs, which are defined differently for each variant: - -[IpAddr]: ../std/net/enum.IpAddr.html - -```rust -struct Ipv4Addr { - // details elided -} - -struct Ipv6Addr { - // details elided -} - -enum IpAddr { - V4(Ipv4Addr), - V6(Ipv6Addr), -} -``` - -This illustrates you can put any kind of data inside of an enum variant: -strings, numeric types, structs, and you could even include another enum! Also, -standard library types are often not much more complicated than what you might -come up with. - -Note that even though the standard library contains a definition for `IpAddr`, -we can still choose to create and use our own definition without conflict since -we haven't brought the standard library's definition into our scope. We'll talk -more about importing types in Chapter 7. - -Let's look at another example: here’s an enum with a wide variety of types -embedded in its variants: - -```rust -enum Message { - Quit, - Move { x: i32, y: i32 }, - Write(String), - ChangeColor(i32, i32, i32), -} -``` - -This enum has four variants with different types: - -* `Quit` has no data associated with it at all. -* `Move` includes an anonymous struct inside of it. -* `Write` includes a single `String`. -* `ChangeColor` includes three `i32`s. - -This is similar to different kinds of struct definitions, except without the -`struct` keyword and all grouped together under the `Message` type. The -following structs could hold the same data that the enum variants above hold: - -```rust -struct QuitMessage; // unit struct -struct MoveMessage { - x: i32, - y: i32, -} -struct WriteMessage(String); // tuple struct -struct ChangeColorMessage(i32, i32, i32); // tuple struct -``` - -But if we used the different structs, which each have their own type, we -wouldn't be able to as easily define a function that could take any of these -kinds of messages as we could with the `Message` enum defined above, which is a -single type. - -One more similarity between enums and structs: just as we are able to define -methods on structs using `impl`, we are also able to define methods on enums. -Here's a method, `call`, that we could define on our `Message` enum: - -```rust -# enum Message { -# Quit, -# Move { x: i32, y: i32 }, -# Write(String), -# ChangeColor(i32, i32, i32), -# } -# -impl Message { - fn call(&self) { - // body would be defined here - } -} - -let m = Message::Write(String::from("hello")); -m.call(); -``` - -The body of the method would use `self` to get the value that we called the -method on. In this example, we've created a variable `m` that has the value -`Message::Write("hello")`, and that is what `self` will be in the body of -the `call` method when `m.call()` runs. - -Let's look at another enum in the standard library that is very common and -useful: `Option`. diff --git a/src/ch06-01-defining-an-enum.md b/src/ch06-01-defining-an-enum.md new file mode 100644 index 0000000..da9e069 --- /dev/null +++ b/src/ch06-01-defining-an-enum.md @@ -0,0 +1,415 @@ + +## Defining an Enum + +Let's look at a situation we might want to express in code and see why enums are +sometimes a more appropriate choice than structs for modeling data. Say we need +to work with IP addresses. There are two major standards used for IP addresses +today: version four and version six. These are the only possibilities for an IP +address that our program will come across: we can *enumerate* all possible +values, which is where *enumeration* gets its name. + +Any IP address can be either a version four or a version six address, but not +both at the same time. That property of IP addresses makes the enum data +structure appropriate for this case, since enum values can only be one of the +variants. Both version four and version six addresses are still fundamentally +IP addresses, though, so they should be treated as the same type when the code +is handling situations that apply to any kind of IP address. + +We can express this concept in code by defining an `IpAddrKind` enumeration and +listing the possible kinds an IP address can be, `V4` and `V6`. These are known +as the *variants* of the enum: + +```rust +enum IpAddrKind { + V4, + V6, +} +``` + +This is now a custom data type that we can use elsewhere in our code. + +### Enum Values + + +An `enum` definition is similar to a `struct` definition: it defines a new type +and a template of what instances of that new type will be like. When you want to +use a struct, you create an instance of the struct. When you want to use an +enum, you create an instance of the enum that is one of the variants the enum +allows. + + +When you want to use a struct, you create an instance of the *struct* itself. +When you want to use an enum, you create an instance of one of its *variants*. +Each variant is defined like a struct, and you instantiate both using the same +syntax. + + + +We can create instances of each of the two variants of `IpAddrKind` like this: + +```rust +# enum IpAddrKind { +# V4, +# V6, +# } +# +let four = IpAddrKind::V4; +let six = IpAddrKind::V6; +``` + +Note that the variants of the enum are namespaced under its identifier, and we +use the double colon to separate the two. The reason this is useful is that now +both values `IpAddrKind::V4` and `IpAddrKind::V6` are of the same type: +`IpAddrKind`. We can then, for instance, define a function that takes any +`IpAddrKind`: + +```rust +# enum IpAddrKind { +# V4, +# V6, +# } +# +fn route(ip_type: IpAddrKind) { } +``` + +And we can call this function with either variant: + +```rust +# enum IpAddrKind { +# V4, +# V6, +# } +# +# fn route(ip_type: IpAddrKind) { } +# +route(IpAddrKind::V4); +route(IpAddrKind::V6); +``` + +Enums have more tricks up their sleeves, too. Thinking more about our IP +address type, at the moment we don’t have a way to store the actual *data* of +the IP address; we only know what *kind* it is. Given that you just learned +about structs, you might tackle this problem as in Listing 6-1: + +
+ +```rust +enum IpAddrKind { + V4, + V6, +} + +struct IpAddr { + kind: IpAddrKind, + address: String, +} + +let home = IpAddr { + kind: IpAddrKind::V4, + address: String::from("127.0.0.1"), +}; + +let loopback = IpAddr { + kind: IpAddrKind::V6, + address: String::from("::1"), +}; +``` + +
+ +Listing 6-1: Storing the data and type of an IP address using a `struct` + +
+
+ +Here, we've defined a struct `IpAddr` that has two fields: a `kind` field that +is of type `IpAddrKind` (the enum we defined previously), and an `address` +field of type `String`. We have two instances of this struct. The first, +`home`, has the value `IpAddrKind::V4` as its `kind`, with associated address +data of `127.0.0.1`. The second instance, `loopback`, has the other variant of +`IpAddrKind` as its `kind` value, `V6`, and has address `::1` associated with +it. We’ve used a struct to bundle the `kind` and `address` values together, so +that now the kind is associated with the value itself. + +We can represent the same concept in a more concise way using just an enum, +rather than an enum inside a struct, by putting data directly into each enum +variant. This new definition of the `IpAddr` enum says that both `V4` and `V6` +variants will have associated `String` values: + +```rust +enum IpAddr { + V4(String), + V6(String), +} + +let home = IpAddr::V4(String::from("127.0.0.1")); + +let loopback = IpAddr::V6(String::from("::1")); +``` + +We attach data to each variant of the enum directly, no need for an extra +struct. + +There's another advantage to using an enum over a struct: each variant can +store *different kinds* of data. Version four type IP addresses will always +have four numeric components that will have values between 0 and 255. If we +wanted to store `V4` addresses as four `u8`s but still express `V6` addresses +as `String`s, we wouldn't be able to with a `struct`. Enums handle this case +with ease: + +```rust +enum IpAddr { + V4(u8, u8, u8, u8), + V6(String), +} + +let home = IpAddr::V4(127, 0, 0, 1); + +let loopback = IpAddr::V6(String::from("::1")); +``` + +We've been showing a bunch of different possibilities that we could define in +our code for storing IP addresses of the two different kinds using an enum. It +turns out, though, that wanting to store IP addresses and encode which kind +they are is so common that [the standard library has a definition we can +use!][IpAddr] Let's look at how the standard library defines +`IpAddr`: it has the exact enum and variants that we've defined and used, but +it chose to embed the address data inside the variants in the form of two +different structs, which are defined differently for each variant: + +[IpAddr]: ../std/net/enum.IpAddr.html + +```rust +struct Ipv4Addr { + // details elided +} + +struct Ipv6Addr { + // details elided +} + +enum IpAddr { + V4(Ipv4Addr), + V6(Ipv6Addr), +} +``` + +This illustrates you can put any kind of data inside of an enum variant: +strings, numeric types, structs, and you could even include another enum! Also, +standard library types are often not much more complicated than what you might +come up with. + +Note that even though the standard library contains a definition for `IpAddr`, +we can still choose to create and use our own definition without conflict since +we haven't brought the standard library's definition into our scope. We'll talk +more about importing types in Chapter 7. + +Let's look at another example: here’s an enum with a wide variety of types +embedded in its variants: + +```rust +enum Message { + Quit, + Move { x: i32, y: i32 }, + Write(String), + ChangeColor(i32, i32, i32), +} +``` + +This enum has four variants with different types: + +* `Quit` has no data associated with it at all. +* `Move` includes an anonymous struct inside of it. +* `Write` includes a single `String`. +* `ChangeColor` includes three `i32`s. + +This is similar to different kinds of struct definitions, except without the +`struct` keyword and all grouped together under the `Message` type. The +following structs could hold the same data that the enum variants above hold: + +```rust +struct QuitMessage; // unit struct +struct MoveMessage { + x: i32, + y: i32, +} +struct WriteMessage(String); // tuple struct +struct ChangeColorMessage(i32, i32, i32); // tuple struct +``` + +But if we used the different structs, which each have their own type, we +wouldn't be able to as easily define a function that could take any of these +kinds of messages as we could with the `Message` enum defined above, which is a +single type. + +One more similarity between enums and structs: just as we are able to define +methods on structs using `impl`, we are also able to define methods on enums. +Here's a method, `call`, that we could define on our `Message` enum: + +```rust +# enum Message { +# Quit, +# Move { x: i32, y: i32 }, +# Write(String), +# ChangeColor(i32, i32, i32), +# } +# +impl Message { + fn call(&self) { + // body would be defined here + } +} + +let m = Message::Write(String::from("hello")); +m.call(); +``` + +The body of the method would use `self` to get the value that we called the +method on. In this example, we've created a variable `m` that has the value +`Message::Write("hello")`, and that is what `self` will be in the body of +the `call` method when `m.call()` runs. + +Let's look at another enum in the standard library that is very common and +useful: `Option`. + +## The `Option` Enum and its Advantages Over Null Values + +In the previous section, we looked at how the `IpAddr` enum let us use Rust's +type system to encode more information than just the data into our program. +This section is a case study of `Option`, which is another enum defined by the +standard library. The `Option` type is used in many places because it encodes +the very common scenario in which a value could be *something* or it could be +*nothing*. Expressing this concept in terms of the type system means the +compiler can check that you've handled all the cases you should be handling, +which can prevent bugs that are extremely common in other programming languages. + +Programming language design is often thought of in terms of which features you +include, but the features you leave out are important too. Rust does not have +the *null* feature that many other languages have. Null is a value that means +there is no value there. In languages with null, variables can always be in one +of two states: null or not-null. + +The inventor of null has this to say: + +> I call it my billion-dollar mistake. At that time, I was designing the first +> comprehensive type system for references in an object-oriented language. My +> goal was to ensure that all use of references should be absolutely safe, with +> checking performed automatically by the compiler. But I couldn't resist the +> temptation to put in a null reference, simply because it was so easy to +> implement. This has led to innumerable errors, vulnerabilities, and system +> crashes, which have probably caused a billion dollars of pain and damage in +> the last forty years. +> +> - Tony Hoare "Null References: The Billion Dollar Mistake" + +The problem with null values is that if you try to actually use a value that's +null as if it is a not-null value, you'll get an error of some kind. Because +this null or not-null property is pervasive, it's extremely easy to make this +kind of error. + +The concept that null is trying to express is still a useful one, however: a +null is a value which is currently invalid or absent for some reason. + +The problem isn't with the concept itself, but with the particular +implementation. As such, Rust does not have nulls, but it does have an enum +that can encode the concept of a value being present or absent. This enum is +`Option`, and it is [defined by the standard library][option] +as follows: + +[option]: ../std/option/enum.Option.html + +```rust +enum Option { + Some(T), + None, +} +``` + +The `Option` enum is so useful that it's even included in the prelude; you +don't need to import it explicitly. Furthermore, so are its variants: you can +use `Some` and `None` directly, without prefixing them with `Option::`. +`Option` is still just a regular enum, however, and `Some(T)` and `None` are +still values of type `Option`. + +The `` syntax is a feature of Rust we haven't talked about yet. It's a +generic type parameter, and we'll cover generics in more detail in Chapter 10. +For now, all you need to know is that this means the `Some` variant of the +`Option` enum can hold one piece of data of any type. Here are some examples of +using `Option` values to hold number types and string types: + +```rust +let some_number = Some(5); +let some_string = Some("a string"); + +let absent_number: Option = None; +``` + +If we use `None` rather than `Some`, we need to tell Rust what type of +`Option` we have, because the compiler can't infer the type that the `Some` +variant will hold by looking at the `None` variant. + +When we have a `Some` value, we know that there is a value present, and the +value is held within the `Some`. When we have a `None` value, in some sense, +that means the same thing that null does: we do not have a valid value. So why +is this any better than null? + +In short, because `Option` and `T` (where `T` can be any type) are different +types from each other, so the compiler won't let us use an `Option` value as if +it was definitely a valid value. For example, this code won't compile because +it's trying to compare an `Option` to an `i8`: + +```rust,ignore +let x: i8 = 5; +let y: Option = Some(5); + +let sum = x + y; +``` + +If we run this code, we get an error message like this: + +```text +error[E0277]: the trait bound `i8: std::ops::Add>` is not satisfied + --> + | +7 | let sum = x + y; + | ^^^^^ + | +``` + +Intense! What this error message is trying to say is that Rust does not +understand how to add an `Option` and an `i8`, since they're different +types. When we have a value of a type like `i8` in Rust, the compiler will +ensure that we always have a valid value. We can proceed confidently without +having to check for null before using that value. Only when we have an +`Option` (or whatever type of value we're working with) do we have to +worry about possibly not having a value, and the compiler will make sure we +handle that case before using the value. + +In other words, you have to convert an `Option` to a `T` before you can do +`T` stuff with it. This helps catch one of the most common issues with null, +generally: assuming that something isn't null when it actually is. + +This is pretty powerful: in order to have a value that can possibly be null, +you have to explicitly opt in by making the type of that value `Option`. +Then, when you use that value, you are required to explicitly handle the case +when the value is null. Everywhere that a value has a type that isn't an +`Option`, you *can* safely assume that the value isn't null. This was a +deliberate design decision for Rust to limit null's pervasiveness and increase +the safety of Rust code. + +So, how *do* you get the `T` value out of a `Some` variant when you have a +value of type `Option` so that you can use that value? The `Option` enum +has a large number of methods useful in a variety of situations that you can +check out in [its documentation][docs], and becoming familiar +with them will be extremely useful in your journey with Rust. + +[docs]: ../std/option/enum.Option.html + +What we generally want to do in order to use an `Option` value is to have +code that will handle each variant. We want some code that will run only in the +case that we have a `Some(T)` value, and this code *is* allowed to use the +inner `T`. We want some *other* code to run if we have a `None` value, and that +code doesn't have a `T` value available. The `match` expression is a control +flow construct that does just this, when used with enums: it will run different +code depending on which variant of the enum it has, and that code can use the +data inside the matching value. diff --git a/src/ch06-01-option.md b/src/ch06-01-option.md deleted file mode 100644 index c2448a2..0000000 --- a/src/ch06-01-option.md +++ /dev/null @@ -1,141 +0,0 @@ -## The `Option` Enum and its Advantages Over Null Values - -In the previous section, we looked at how the `IpAddr` enum let us use Rust's -type system to encode more information than just the data into our program. -This section is a case study of `Option`, which is another enum defined by the -standard library. The `Option` type is used in many places because it encodes -the very common scenario in which a value could be *something* or it could be -*nothing*. Expressing this concept in terms of the type system means the -compiler can check that you've handled all the cases you should be handling, -which can prevent bugs that are extremely common in other programming languages. - -Programming language design is often thought of in terms of which features you -include, but the features you leave out are important too. Rust does not have -the *null* feature that many other languages have. Null is a value that means -there is no value there. In languages with null, variables can always be in one -of two states: null or not-null. - -The inventor of null has this to say: - -> I call it my billion-dollar mistake. At that time, I was designing the first -> comprehensive type system for references in an object-oriented language. My -> goal was to ensure that all use of references should be absolutely safe, with -> checking performed automatically by the compiler. But I couldn't resist the -> temptation to put in a null reference, simply because it was so easy to -> implement. This has led to innumerable errors, vulnerabilities, and system -> crashes, which have probably caused a billion dollars of pain and damage in -> the last forty years. -> -> - Tony Hoare "Null References: The Billion Dollar Mistake" - -The problem with null values is that if you try to actually use a value that's -null as if it is a not-null value, you'll get an error of some kind. Because -this null or not-null property is pervasive, it's extremely easy to make this -kind of error. - -The concept that null is trying to express is still a useful one, however: a -null is a value which is currently invalid or absent for some reason. - -The problem isn't with the concept itself, but with the particular -implementation. As such, Rust does not have nulls, but it does have an enum -that can encode the concept of a value being present or absent. This enum is -`Option`, and it is [defined by the standard library][option] -as follows: - -[option]: ../std/option/enum.Option.html - -```rust -enum Option { - Some(T), - None, -} -``` - -The `Option` enum is so useful that it's even included in the prelude; you -don't need to import it explicitly. Furthermore, so are its variants: you can -use `Some` and `None` directly, without prefixing them with `Option::`. -`Option` is still just a regular enum, however, and `Some(T)` and `None` are -still values of type `Option`. - -The `` syntax is a feature of Rust we haven't talked about yet. It's a -generic type parameter, and we'll cover generics in more detail in Chapter 10. -For now, all you need to know is that this means the `Some` variant of the -`Option` enum can hold one piece of data of any type. Here are some examples of -using `Option` values to hold number types and string types: - -```rust -let some_number = Some(5); -let some_string = Some("a string"); - -let absent_number: Option = None; -``` - -If we use `None` rather than `Some`, we need to tell Rust what type of -`Option` we have, because the compiler can't infer the type that the `Some` -variant will hold by looking at the `None` variant. - -When we have a `Some` value, we know that there is a value present, and the -value is held within the `Some`. When we have a `None` value, in some sense, -that means the same thing that null does: we do not have a valid value. So why -is this any better than null? - -In short, because `Option` and `T` (where `T` can be any type) are different -types from each other, so the compiler won't let us use an `Option` value as if -it was definitely a valid value. For example, this code won't compile because -it's trying to compare an `Option` to an `i8`: - -```rust,ignore -let x: i8 = 5; -let y: Option = Some(5); - -let sum = x + y; -``` - -If we run this code, we get an error message like this: - -```text -error[E0277]: the trait bound `i8: std::ops::Add>` is not satisfied - --> - | -7 | let sum = x + y; - | ^^^^^ - | -``` - -Intense! What this error message is trying to say is that Rust does not -understand how to add an `Option` and an `i8`, since they're different -types. When we have a value of a type like `i8` in Rust, the compiler will -ensure that we always have a valid value. We can proceed confidently without -having to check for null before using that value. Only when we have an -`Option` (or whatever type of value we're working with) do we have to -worry about possibly not having a value, and the compiler will make sure we -handle that case before using the value. - -In other words, you have to convert an `Option` to a `T` before you can do -`T` stuff with it. This helps catch one of the most common issues with null, -generally: assuming that something isn't null when it actually is. - -This is pretty powerful: in order to have a value that can possibly be null, -you have to explicitly opt in by making the type of that value `Option`. -Then, when you use that value, you are required to explicitly handle the case -when the value is null. Everywhere that a value has a type that isn't an -`Option`, you *can* safely assume that the value isn't null. This was a -deliberate design decision for Rust to limit null's pervasiveness and increase -the safety of Rust code. - -So, how *do* you get the `T` value out of a `Some` variant when you have a -value of type `Option` so that you can use that value? The `Option` enum -has a large number of methods useful in a variety of situations that you can -check out in [its documentation][docs], and becoming familiar -with them will be extremely useful in your journey with Rust. - -[docs]: ../std/option/enum.Option.html - -What we generally want to do in order to use an `Option` value is to have -code that will handle each variant. We want some code that will run only in the -case that we have a `Some(T)` value, and this code *is* allowed to use the -inner `T`. We want some *other* code to run if we have a `None` value, and that -code doesn't have a `T` value available. The `match` expression is a control -flow construct that does just this, when used with enums: it will run different -code depending on which variant of the enum it has, and that code can use the -data inside the matching value.