{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Enums" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* Enums are a powerful feature in Rust that allow you to define a type by enumerating its possible variants.\n", "\n", "* Enums can have data associated with each variant, making them more flexible than simple enums in other languages.\n", "\n", "* Pattern matching with enums allows you to concisely handle different cases, making your code more expressive and safe." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## C-Style Enums" ] }, { "cell_type": "markdown", "metadata": { "vscode": { "languageId": "rust" } }, "source": [ "* The simplest enums are just a list of variants without any associated data.\n", "* They are also called \"fieldless\" or \"unit\" variants.\n", "* They work similar to enums in C/C++" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "direction = North\n" ] } ], "source": [ "#[derive(Debug)]\n", "enum Compass {\n", " North,\n", " East,\n", " South,\n", " West\n", "}\n", "\n", "let direction = Compass::North;\n", "\n", "println!(\"direction = {:?}\", direction);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* In memory, values of C-style enums are stored as integers. " ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "use std::mem::size_of;\n", "\n", "assert!(size_of::() == 1);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Importing constructors" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "use self::Compass::*; // self - when enum type is defined in current module; otherwise name of a module\n", "\n", "let direction = North;" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Using Integral Values" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "#[derive(Debug)]\n", "enum HttpStatus {\n", " Ok = 200,\n", " NotModified = 304,\n", " NotFound = 404\n", "}\n", "\n", "assert!(size_of::() == 2);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Casting to Integer" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* \"Fieldless\" or \"unit\" variants can be converted to integral value using the `as` keyword." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "let ok_code = HttpStatus::Ok as i32;\n", "assert!(ok_code == 200);\n", "\n", "assert!(HttpStatus::NotFound as i32 == 404);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Casting from Integer" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* Converting from integral value requires pattern matching." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "fn http_status_from_u32(status: u32) -> Option {\n", " match status {\n", " 200 => Some(HttpStatus::Ok),\n", " 304 => Some(HttpStatus::NotModified),\n", " 404 => Some(HttpStatus::NotFound),\n", " _ => None\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Some(NotFound)" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "http_status_from_u32(404)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "None" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "http_status_from_u32(1024)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Deriving From Common Traits\n", "\n", "* As with structs, the compiler will implement features like the `==` operator for you, but you have to ask." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "#[derive(Copy, Clone, Debug, PartialEq)]\n", "enum TimeUnit {\n", " Seconds, Minutes, Hours, Days, Months, Years\n", "}" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "assert!(TimeUnit::Seconds == TimeUnit::Seconds);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Implementing Methods for Enums" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* We can add methods to enums using `impl` blocks" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "impl TimeUnit {\n", " /// Return the plural noun for this time unit.\n", " fn plural(self) -> &'static str {\n", " match self {\n", " TimeUnit::Seconds => \"seconds\",\n", " TimeUnit::Minutes => \"minutes\",\n", " TimeUnit::Hours => \"hours\",\n", " TimeUnit::Days => \"days\",\n", " TimeUnit::Months => \"months\",\n", " TimeUnit::Years => \"years\"\n", " }\n", " }\n", "\n", " /// Return the singular noun for this time unit.\n", " fn singular(self) -> &'static str {\n", " self.plural().trim_right_matches('s')\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"days\"" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "let time_unit = TimeUnit::Days;\n", "\n", "time_unit.plural()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Enums With Data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* The power of Rust enums lies in their ability to store additional data\n", "* This allows to create complex data types that can hold different kinds of information" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "#[derive(Clone, Debug, PartialEq)]\n", "enum Message {\n", " Quit, // no data associated\n", " Move{ x: i32, y: i32 }, // named fields - like a struct\n", " Write(String), // single String\n", " ChangeColor(i32, i32, i32) // tuple struct of three values\n", "}" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "msg = Quit\n", "msg = Move { x: 100, y: 200 }\n", "msg = Write(\"TEXT\")\n", "msg = ChangeColor(255, 0, 255)\n" ] } ], "source": [ "let mut msg = Message::Quit;\n", "println!(\"msg = {:?}\", msg);\n", "\n", "msg = Message::Move{ x: 100, y: 200};\n", "println!(\"msg = {:?}\", msg);\n", "\n", "msg = Message::Write(String::from(\"TEXT\"));\n", "println!(\"msg = {:?}\", msg);\n", "\n", "msg = Message::ChangeColor(255, 0, 255);\n", "println!(\"msg = {:?}\", msg);" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "impl Message {\n", " fn call(&self) {\n", " match *self {\n", " Message::Quit => println!(\"Quit\"),\n", " Message::Move{ x, y } => println!(\"Move to x={}, y={}\", x, y),\n", " Message::Write(ref text) => println!(\"Write '{}'\", text),\n", " Message::ChangeColor(r, g, b) => println!(\"Change color to r={}, g={}, b={}\", r, g, b)\n", " }\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Change color to r=255, g=0, b=255\n" ] } ], "source": [ "msg.call();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Generic Enums" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* Enums can be parametrized with type parameters to create generic enums." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### `Option`" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "mod explain {\n", " enum Option {\n", " None,\n", " Some(T)\n", " }\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### `Result`" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "mod explain{\n", " enum Result { \n", " Ok(T), \n", " Err(E) \n", " }\n", "} " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Real-World Example - BinaryTree" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "// An ordered collection of `T`s.\n", "enum BinaryTree {\n", " Empty,\n", " NonEmpty(Box>)\n", "}\n", "\n", "// A part of a BinaryTree.\n", "struct TreeNode {\n", " element: T,\n", " left: BinaryTree,\n", " right: BinaryTree\n", "}" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "use self::BinaryTree::*;\n", "\n", "let jupyter_tree = NonEmpty(Box::new(TreeNode {\n", " element: \"Jupyter\",\n", " left: Empty,\n", " right: Empty\n", "}));\n", "\n", "let mercury_tree = NonEmpty(Box::new(TreeNode {\n", " element: \"Mercury\",\n", " left: Empty,\n", " right: Empty\n", "}));\n", "\n", "let venus_tree = NonEmpty(Box::new(TreeNode {\n", " element: \"Venus\",\n", " left: Empty,\n", " right: Empty\n", "}));\n", "\n", "let mars_tree = NonEmpty(Box::new(TreeNode {\n", " element: \"Mars\",\n", " left: jupyter_tree,\n", " right: mercury_tree\n", "}));\n", "\n", "let uranus_tree = NonEmpty(Box::new(TreeNode {\n", " element: \"Uranus\",\n", " left: Empty,\n", " right: venus_tree\n", "}));\n", "\n", "// the root\n", "let saturn_tree = NonEmpty(Box::new(TreeNode {\n", " element: \"Saturn\",\n", " left: mars_tree,\n", " right: uranus_tree\n", "}));" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Pattern Matching\n", "\n", "* Pattern matching allows to compare a value against a series of patterns and execute code based on which pattern matches.\n", "* Patterns can include literals, variable names, wildcards, and more complex structures.\n", "* Patterns are commonly used with the `match` expression in Rust.\n", "* Match arms consist of a pattern and the code to execute if the pattern matches.\n", "* `match` expression is **infallible** and must cover all possible cases." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "enum RoughTime {\n", " InThePast(TimeUnit, u32),\n", " JustNow,\n", " InTheFuture(TimeUnit, u32)\n", "}" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [], "source": [ "fn rough_time_to_english(rt: RoughTime) -> String {\n", " match rt {\n", " RoughTime::InThePast(units, 1) => format!(\"{} ago\", units.singular()),\n", " RoughTime::InThePast(units, count) => format!(\"{} {} ago\", count, units.plural()),\n", " RoughTime::JustNow => format!(\"just now\"),\n", " RoughTime::InTheFuture(units, 1) => format!(\"{} from now\", units.singular()), \n", " RoughTime::InTheFuture(units, count) => format!(\"{} {} from now\", count, units.plural()) \n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"second ago\"" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "rough_time_to_english(RoughTime::InThePast(TimeUnit::Seconds, 1))" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"42 seconds ago\"" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "rough_time_to_english(RoughTime::InThePast(TimeUnit::Seconds, 42))" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"just now\"" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "rough_time_to_english(RoughTime::JustNow)" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"42 seconds from now\"" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "rough_time_to_english(RoughTime::InTheFuture(TimeUnit::Seconds, 42))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "| Pattern type | Example | Notes |\n", "| ----------------------- | ---------------------- | -------------------------------------------------------------------------- |\n", "| Literal | `100` | Matches an exact value; the name of a const is also allowed |\n", "| | `\"name\"` | |\n", "| Range | `0 ... 100` | Matches any value in range, including the end value |\n", "| | `'a' ... 'k'` | |\n", "| Wildcard | `_` | Matches any value and ignores it |\n", "| Variable | `name` | Like _ but moves or copies the value into a new local variable |\n", "| | `mut count` | |\n", "| ref variable | `ref field` | Borrows a reference to the matched value instead of moving or copying it |\n", "| | `ref mut field` | |\n", "| Binding with subpattern | `val @ 0 ... 99` | Matches the pattern to the right of @, using the variable name to the left |\n", "| | `ref circle @` | |\n", "| | `Shape::Circle { .. }` | |\n", "| Enum pattern\t | `Some(value)` | |\n", "| | `None` | |\n", "| | `Message::Ok` | |\n", "| Tuple pattern\t | `(key, value)` | |\n", "| | `(r, g, b)` | |\n", "| Struct pattern\t | `Color(r, g, b)` | |\n", "| | `Point { x, y }` | |\n", "| | `Card { suit: Clubs, rank: n }` | |\n", "| | `Account { id, name, .. }` | | \n", "| Reference\t | `&value` | Matches only reference values |\n", "| | `&(k, v)`\t | |\n", "| Multiple patterns\t | `'a' \\| 'A'` | In match only (not valid in let, etc.) |\n", "| Guard expression\t | `x if x * x <= r2` | In match only (not valid in let, etc.) |" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Literals, Variables and Wildcards in Patterns" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"4 clicks\"" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "let no_of_clicks = 4;\n", "\n", "match no_of_clicks {\n", " 0 => format!(\"No one clicked...\"),\n", " 1 => format!(\"Just one click...\"),\n", " n => format!(\"{} clicks\", n)\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* Rust requires every match expression to handle all possible values, a wildcard is often required at the end\n", "* This ensures that the match is exhaustive and prevents runtime errors." ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"Not Ok\"" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "let http_code = HttpStatus::NotFound;\n", "\n", "match http_code {\n", " HttpStatus::Ok => \"Ok\",\n", " _ => \"Not Ok\"\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Tuple Patterns" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [], "source": [ "fn describe_point(x: i32, y:i32) -> &'static str {\n", " use std::cmp::Ordering::*;\n", "\n", " match (x.cmp(&0), y.cmp(&0)) {\n", " (Equal, Equal) => \"at the origin\",\n", " (_, Equal) => \"on the x axis\",\n", " (Equal, _) => \"on the y axis\",\n", " (Greater, Greater) => \"in the first quadrant\",\n", " (Less, Greater) => \"in the second quadrant\",\n", " _ => \"somewhere else\"\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"on the y axis\"" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "describe_point(0, 42)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Struct Patterns" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"straight up 100 meters\"" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#[derive(Debug, PartialEq)]\n", "struct Point {\n", " x: i32, \n", " y: i32\n", "}\n", "\n", "let location = Point{x:0, y:100};\n", "\n", "match location {\n", " Point{x: 0, y: height} => format!(\"straight up {} meters\", height),\n", " Point{x: x, y: y } => format!(\"at ({},{})\", x, y)\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* Patterns like `Point { x: x, y: y }` are common when matching structs, and the redundant names are visual clutter, so Rust has a shorthand for this: `Point {x, y}` - this pattern still stores a point’s `x` field in a new local `x` and its `y` field in a new local `y`." ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"straight up 100 meters\"" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "match location {\n", " Point{x: 0, y} => format!(\"straight up {} meters\", y),\n", " Point{x, y} => format!(\"at ({},{})\", x, y)\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* Matching larger structs" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [], "source": [ "#[derive(Debug)]\n", "struct Person {\n", " id: u64,\n", " first_name: String,\n", " last_name: String,\n", " age: u8,\n", " salary: f32,\n", " job: String\n", "}" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"Jan Kowalski\"" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "let person = Person{id: 665, first_name: \"Jan\".to_string(), last_name: \"Kowalski\".to_string(), age: 32, salary:9000.99, job: \"Rust Developer\".to_string() };\n", "\n", "match person {\n", " Person{id: _, first_name, last_name, age: _, salary: _, job: _} => format!(\"{} {}\", first_name, last_name)\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* We can skip uninteresting fields with `..`" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"Jan Kowalski\"" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "let person = Person{id: 665, first_name: \"Jan\".to_string(), last_name: \"Kowalski\".to_string(), age: 32, salary:9000.99, job: \"Rust Developer\".to_string() };\n", "\n", "match person {\n", " Person{first_name, last_name, ..} => format!(\"{} {}\", first_name, last_name)\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Reference Patterns" ] }, { "cell_type": "markdown", "metadata": { "vscode": { "languageId": "rust" } }, "source": [ "#### `ref` Patterns\n", "\n", "* `ref` patterns borrow parts of a matched value" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [], "source": [ "fn print_names(person: &Person) {\n", " print!(\"{} {}\", person.first_name, person.last_name); \n", "}" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "ename": "Error", "evalue": "use of deprecated method `core::str::::trim_right_matches`: superseded by `trim_end_matches`", "output_type": "error", "traceback": [ "\u001b[31m[deprecated] Error:\u001b[0m use of deprecated method `core::str::::trim_right_matches`: superseded by `trim_end_matches`" ] }, { "ename": "Error", "evalue": "borrow of partially moved value: `person`", "output_type": "error", "traceback": [ "\u001b[31m[E0382] Error:\u001b[0m borrow of partially moved value: `person`", " \u001b[38;5;246m╭\u001b[0m\u001b[38;5;246m─\u001b[0m\u001b[38;5;246m[\u001b[0m command_38:1:1 \u001b[38;5;246m]\u001b[0m", " \u001b[38;5;246m│\u001b[0m", " \u001b[38;5;246m4 │\u001b[0m \u001b[38;5;249m \u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249mP\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249mr\u001b[0m\u001b[38;5;249ms\u001b[0m\u001b[38;5;249mo\u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249m{\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;54mj\u001b[0m\u001b[38;5;54mo\u001b[0m\u001b[38;5;54mb\u001b[0m\u001b[38;5;249m,\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249m.\u001b[0m\u001b[38;5;249m.\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249m}\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249m=\u001b[0m\u001b[38;5;249m>\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249m{\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249mp\u001b[0m\u001b[38;5;249mr\u001b[0m\u001b[38;5;249mi\u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249m_\u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249ma\u001b[0m\u001b[38;5;249mm\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249ms\u001b[0m\u001b[38;5;249m(\u001b[0m\u001b[38;5;100m&\u001b[0m\u001b[38;5;100mp\u001b[0m\u001b[38;5;100me\u001b[0m\u001b[38;5;100mr\u001b[0m\u001b[38;5;100ms\u001b[0m\u001b[38;5;100mo\u001b[0m\u001b[38;5;100mn\u001b[0m\u001b[38;5;249m)\u001b[0m\u001b[38;5;249m;\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249mp\u001b[0m\u001b[38;5;249mr\u001b[0m\u001b[38;5;249mi\u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249ml\u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249m!\u001b[0m\u001b[38;5;249m(\u001b[0m\u001b[38;5;249m\"\u001b[0m\u001b[38;5;249mW\u001b[0m\u001b[38;5;249mo\u001b[0m\u001b[38;5;249mr\u001b[0m\u001b[38;5;249mk\u001b[0m\u001b[38;5;249ms\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249ma\u001b[0m\u001b[38;5;249ms\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249m{\u001b[0m\u001b[38;5;249m}\u001b[0m\u001b[38;5;249m\"\u001b[0m\u001b[38;5;249m,\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249mj\u001b[0m\u001b[38;5;249mo\u001b[0m\u001b[38;5;249mb\u001b[0m\u001b[38;5;249m)\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249m}\u001b[0m", " \u001b[38;5;240m │\u001b[0m \u001b[38;5;68m┬\u001b[0m\u001b[38;5;54m┬\u001b[0m\u001b[38;5;54m─\u001b[0m \u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m┬\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m ", " \u001b[38;5;240m │\u001b[0m \u001b[38;5;68m╰\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m\u001b[38;5;68m─\u001b[0m help: borrow this binding in the pattern to avoid moving the value: `ref `", " \u001b[38;5;240m │\u001b[0m \u001b[38;5;54m│\u001b[0m \u001b[38;5;100m│\u001b[0m ", " \u001b[38;5;240m │\u001b[0m \u001b[38;5;54m╰\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m\u001b[38;5;54m─\u001b[0m value partially moved here", " \u001b[38;5;240m │\u001b[0m \u001b[38;5;100m│\u001b[0m ", " \u001b[38;5;240m │\u001b[0m \u001b[38;5;100m╰\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m value borrowed here after partial move", "\u001b[38;5;246m───╯\u001b[0m" ] } ], "source": [ "let person = Person{ id: 665, first_name: \"Jan\".to_string(), last_name: \"Kowalski\".to_string(), age: 32, salary:9000.99, job: \"Rust Developer\".to_string() };\n", "\n", "match person {\n", " Person{ job, .. } => { print_names(&person); println!(\"Works as {}\", job) }\n", "}" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Jan Kowalski Works as Rust Developer\n" ] }, { "data": { "text/plain": [ "()" ] }, "execution_count": 39, "metadata": {}, "output_type": "execute_result" } ], "source": [ "let person = Person{ id: 665, first_name: \"Jan\".to_string(), last_name: \"Kowalski\".to_string(), age: 32, salary:9000.99, job: \"Rust Developer\".to_string() };\n", "\n", "match person {\n", " Person{ ref job, .. } => { print_names(&person); println!(\" Works as {}\", job) }\n", "}" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Person { id: 665, first_name: \"JAN\", last_name: \"KOWALSKI\", age: 32, salary: 9000.99, job: \"Rust Developer\" }" ] }, "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fn to_upper(text: &mut String) {\n", " *text = text.to_uppercase();\n", "}\n", "\n", "let mut person = Person{ id: 665, first_name: \"Jan\".to_string(), last_name: \"Kowalski\".to_string(), age: 32, salary:9000.99, job: \"Rust Developer\".to_string() };\n", "\n", "match person {\n", " Person{ref mut first_name, ref mut last_name, ..} => { to_upper(first_name); to_upper(last_name) }\n", "}\n", "\n", "person" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### & Pattern\n", "\n", "* A pattern starting with `&` matches a reference." ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"x=100, y=200\"" ] }, "execution_count": 41, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#[derive(Debug, PartialEq)]\n", "struct ColorCircle { pos: Point, r: u32, color: (u8, u8, u8) }\n", "\n", "impl ColorCircle {\n", " fn center(&mut self) -> &mut Point {\n", " return &mut self.pos;\n", " }\n", "}\n", "\n", "let mut c = ColorCircle{pos: Point{x: 100, y: 200}, r: 50, color: (255, 0, 0)};\n", "\n", "match c.center() {\n", " &mut Point{x, y} => { format!(\"x={}, y={}\", x, y) },\n", "}" ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Point { x: 10, y: 200 }" ] }, "execution_count": 42, "metadata": {}, "output_type": "execute_result" } ], "source": [ "match c.center() {\n", " &mut Point{ref mut x, ..} => { *x = 10; },\n", "}\n", "\n", "c.center()" ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "item peeked: Item(\"one\")\n" ] }, { "data": { "text/plain": [ "()" ] }, "execution_count": 43, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#[derive(Debug)]\n", "struct Item(String);\n", "\n", "let items = [Item(\"one\".to_string()), Item(\"two\".to_string()), Item(\"three\".to_string())];\n", "\n", "{\n", " let mut iter = items.iter().peekable();\n", "\n", " match iter.peek() {\n", " Some(&item) => println!(\"item peeked: {:?}\", item),\n", " None => println!(\"no item\")\n", " }\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Matching Multiple Possibilities" ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "true" ] }, "execution_count": 44, "metadata": {}, "output_type": "execute_result" } ], "source": [ "let text = String::from(\"\\nBC\");\n", "\n", "{\n", " let mut chars = text.chars().peekable();\n", "\n", " let at_end = match chars.peek() {\n", " Some(&'\\r') | Some(&'\\n') | None => true,\n", " _ => false\n", " };\n", "\n", " at_end\n", "}" ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "lowercase letter: a\n", "uppercase letter: B\n", "digit: 1\n", "digit: 2\n", "whitespace\n", "other character\n", "special character: &\n", "special character: ^\n", "special character: %\n", "special character: $\n", "special character: #\n", "whitespace\n", "digit: 3\n", "digit: 4\n", "lowercase letter: c\n", "lowercase letter: d\n", "other character\n", "newline\n" ] }, { "data": { "text/plain": [ "()" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "let text = String::from(\"aB12 -&^%$# 34cd\\r\\n\");\n", "\n", "for c in text.chars() {\n", " match c {\n", " 'a'..='z' => println!(\"lowercase letter: {}\", c),\n", " 'A'..='Z' => println!(\"uppercase letter: {}\", c),\n", " '0'..='9' => println!(\"digit: {}\", c),\n", " ' ' | '\\t' => println!(\"whitespace\"),\n", " '&' | '^' | '%' | '$' | '#' => println!(\"special character: {}\", c),\n", " '\\n' => println!(\"newline\"),\n", " _ => println!(\"other character\")\n", " }\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Pattern Guards" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* Pattern guards allow to add additional conditional checks to patterns in match arms." ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"Inside the sphere: x=0.3, y=0.4, z=0.5\"" ] }, "execution_count": 46, "metadata": {}, "output_type": "execute_result" } ], "source": [ "struct Point3D {\n", " x: f64,\n", " y: f64,\n", " z: f64\n", "}\n", "\n", "impl Point3D {\n", " fn new(x: f64, y: f64, z: f64) -> Point3D {\n", " Point3D{x: x, y: y, z: z}\n", " }\n", "\n", " fn distance_to_origin(&self) -> f64 {\n", " (self.x.powf(2.0) + self.y.powf(2.0) + self.z.powf(2.0)).sqrt()\n", " }\n", "}\n", "\n", "let point = Point3D::new(0.3, 0.4, 0.5);\n", "\n", "match point {\n", " Point3D{x, y, z} if point.distance_to_origin() <= 1.0 => format!(\"Inside the sphere: x={}, y={}, z={}\", x, y, z),\n", " Point3D{x, y, z} => format!(\"Outside the sphere: x={}, y={}, z={}\", x, y, z)\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### `@` Patterns\n", "\n", "* Finally, `x @` pattern matches exactly like the given pattern, but on success, instead of creating variables for parts of the matched value, it creates a single variable `x` and moves or copies the whole value into it" ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Matched: 5\n" ] }, { "data": { "text/plain": [ "()" ] }, "execution_count": 47, "metadata": {}, "output_type": "execute_result" } ], "source": [ "let number = Some(5);\n", "\n", "match number {\n", " Some(n @ 1..=5) => println!(\"Matched: {}\", n),\n", " Some(n) => println!(\"Other: {}\", n),\n", " None => println!(\"No value\"),\n", "}" ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "x is in range 1 to 5: 5; y is 10\n" ] }, { "data": { "text/plain": [ "()" ] }, "execution_count": 48, "metadata": {}, "output_type": "execute_result" } ], "source": [ "let point = Point { x: 5, y: 10 };\n", "\n", "match point {\n", " Point { x: n @ 1..=5, y } => println!(\"x is in range 1 to 5: {}; y is {}\", n, y),\n", " Point { x, y } => println!(\"x is {}; y is {}\", x, y),\n", "}" ] }, { "cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [], "source": [ "#[derive(Debug, PartialEq)]\n", "struct Circle{ center: Point, radius: u32 }\n", "\n", "#[derive(Debug, PartialEq)]\n", "struct Rectangle{ top_left: Point, width: u32, height: u32 }\n", "\n", "#[derive(Debug, PartialEq)]\n", "enum Shape {\n", " Circle(Circle),\n", " Rectangle(Rectangle)\n", "}\n", "\n", "fn inflate(shape: &mut Shape, factor: u32) {\n", " match shape {\n", " Shape::Circle(circle @ Circle{..}) => {\n", " circle.radius *= factor;\n", " },\n", " Shape::Rectangle(rect @ Rectangle{..}) => {\n", " rect.width *= factor; \n", " rect.height *= factor; \n", " }\n", " }\n", "}\n", "\n", "let mut s = Shape::Circle(Circle{center: Point{x: 100, y: 200}, radius: 50});\n", "\n", "inflate(&mut s, 2);\n", "\n", "assert!(s == Shape::Circle(Circle{center: Point{x: 100, y: 200}, radius: 100}));" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Where Patterns Can Be Applied\n", "\n", "#### Irrefutable patterns\n", "\n", "* Patterns that always match are special in Rust. \n", "* They’re called **irrefutable patterns**\n", "* They’re the only patterns allowed places shown below" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Unpack a struct into three new local variables" ] }, { "cell_type": "code", "execution_count": 50, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "x=1, y=2\n" ] }, { "data": { "text/plain": [ "()" ] }, "execution_count": 50, "metadata": {}, "output_type": "execute_result" } ], "source": [ "let point = Point3D::new(1.0, 2.0, 3.0);\n", "\n", "let Point3D{x, y, ..} = point; // unpacking\n", "\n", "println!(\"x={}, y={}\", x, y)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Unpack a function argument that's a tuple" ] }, { "cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "5.0" ] }, "execution_count": 51, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fn distance_to((x, y): (f64, f64)) -> f64 { \n", " (x.powf(2.0) + y.powf(2.0)).sqrt() \n", "}\n", "\n", "distance_to((3.0, 4.0))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Iterate over keys and values of a HashMap" ] }, { "cell_type": "code", "execution_count": 52, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "three: 3\n", "one: 1\n", "two: 2\n" ] }, { "data": { "text/plain": [ "()" ] }, "execution_count": 52, "metadata": {}, "output_type": "execute_result" } ], "source": [ "use std::collections::HashMap;\n", "\n", "let mut dictionary = HashMap::new();\n", "dictionary.insert(\"one\", 1);\n", "dictionary.insert(\"two\", 2);\n", "dictionary.insert(\"three\", 3);\n", "\n", "for (key, value) in &dictionary {\n", " println!(\"{}: {}\", key, value);\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Refutable Patterns\n", "\n", "* A **refutable pattern** is one that might not match, like `Ok(x)`, which doesn’t match an error result, or `'0' ... '9'`, which doesn’t match the character `'Q'`\n", "\n", "* Refutable patterns are also allowed in `if let` and `while let` expressions, which can be used to..." ] }, { "cell_type": "code", "execution_count": 53, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "32 years ago\n" ] }, { "data": { "text/plain": [ "()" ] }, "execution_count": 53, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fn date_of_birth() -> RoughTime {\n", " RoughTime::InThePast(TimeUnit::Years, 32)\n", "}\n", "\n", "if let RoughTime::InThePast(units, count) = date_of_birth() {\n", " println!(\"{} years ago\", count);\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Real-World Example - BinaryTree - 2" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "// An ordered collection of `T`s.\n", "enum BinaryTree {\n", " Empty,\n", " NonEmpty(Box>)\n", "}\n", "\n", "// A part of a BinaryTree.\n", "struct TreeNode {\n", " element: T,\n", " left: BinaryTree,\n", " right: BinaryTree\n", "}" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "impl BinaryTree {\n", " fn add(&mut self, value: T) {\n", " match *self {\n", " BinaryTree::Empty => {\n", " *self = BinaryTree::NonEmpty(Box::new(TreeNode {\n", " element: value,\n", " left: BinaryTree::Empty,\n", " right: BinaryTree::Empty\n", " }));\n", " },\n", " BinaryTree::NonEmpty(ref mut node) => {\n", " if value <= node.element {\n", " node.left.add(value);\n", " } else {\n", " node.right.add(value);\n", " }\n", " }\n", " }\n", " }\n", "\n", " fn preorder<'a>(&'a self, values: &mut Vec<&'a T>) {\n", " match *self {\n", " BinaryTree::Empty => {},\n", " BinaryTree::NonEmpty(ref node) => {\n", " values.push(&node.element);\n", " node.left.preorder(values);\n", " node.right.preorder(values);\n", " }\n", " }\n", " }\n", "\n", " fn postorder<'a>(&'a self, values: &mut Vec<&'a T>) {\n", " match *self {\n", " BinaryTree::Empty => {},\n", " BinaryTree::NonEmpty(ref node) => {\n", " node.left.postorder(values);\n", " node.right.postorder(values);\n", " values.push(&node.element);\n", " }\n", " }\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[\"Givry\", \"Beaune\", \"Mercurey\", \"Rully\", \"Pommard\"]" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "let mut tree = BinaryTree::Empty;\n", "\n", "{\n", " tree.add(\"Givry\".to_string());\n", " tree.add(\"Mercurey\".to_string());\n", " tree.add(\"Rully\".to_string());\n", " tree.add(\"Beaune\".to_string());\n", " tree.add(\"Pommard\".to_string());\n", "\n", " let mut values: Vec<&String> = Vec::new();\n", " tree.preorder(&mut values);\n", "\n", " values\n", "}" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[\"Cabernet Sauvignon\", \"Solaris\", \"Pinot Noir\", \"Merlot\", \"Tannat\", \"Chardonnay\"]" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "let mut another_tree = BinaryTree::Empty;\n", "\n", "{\n", " another_tree.add(\"Chardonnay\");\n", " another_tree.add(\"Tannat\");\n", " another_tree.add(\"Cabernet Sauvignon\");\n", " another_tree.add(\"Merlot\");\n", " another_tree.add(\"Pinot Noir\");\n", " another_tree.add(\"Solaris\");\n", "\n", " let mut values = Vec::new();\n", " another_tree.postorder(&mut values);\n", " values\n", "}" ] } ], "metadata": { "kernelspec": { "display_name": "Rust", "language": "rust", "name": "rust" }, "language_info": { "codemirror_mode": "rust", "file_extension": ".rs", "mimetype": "text/rust", "name": "rust", "pygment_lexer": "rust", "version": "" } }, "nbformat": 4, "nbformat_minor": 2 }