trainings/Rust_2026/rust-bs-2026-09-14/notebooks/05_structs.ipynb

564 lines
13 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Structs"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"* A *struct* assembles several values of assorted types together into a single value, so you can deal with them as a unit. \n",
"* Given a struct, you can read and modify its individual components. \n",
"* A struct can have methods associated with it that operate on its components."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Named-Field Structs"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Declaring a Struct"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"// A rectangle of eight-bit grayscale pixels.\n",
"struct GrayscaleMap {\n",
" pixels: Vec<u8>,\n",
" size: (usize, usize)\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Creating a Struct Value"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"let width = 800;\n",
"let height = 600;\n",
"\n",
"let image = GrayscaleMap {\n",
" pixels: vec![0; width * height],\n",
" size: (width, height)\n",
"};"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"fn create_image(size: (usize, usize)) -> GrayscaleMap {\n",
" let pixels = vec![0; size.0 * size.1];\n",
" GrayscaleMap{pixels, size} // struct expression\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Accessing Struct Fields"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"let image = create_image((800, 600));\n",
"\n",
"assert!(image.size == (800, 600));\n",
"assert!(image.pixels.len() == image.size.0 * image.size.1);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Visibility of Struct"
]
},
{
"cell_type": "markdown",
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"source": [
"* Structs are private by default, visible only in the module where they’re declared. \n",
"* You can make a struct visible outside its module by prefixing its definition with `pub`"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"// A rectangle of eight-bit grayscale pixels.\n",
"pub struct GrayscaleMap {\n",
" pub pixels: Vec<u8>,\n",
" pub size: (usize, usize)\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Tuple-Like Structs"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"* The second kind of struct type is called a tuple-like struct, because it resembles a tuple:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"The type of the variable image was redefined, so was lost.\n"
]
}
],
"source": [
"struct BoundingBox(usize, usize);\n",
"\n",
"let image_bbox = BoundingBox(800, 600);\n",
"\n",
"assert!(image_bbox.0 * image_bbox.1 == 480_000);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"* Individual elements of a tuple-like struct may be public or not:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"pub struct BoundingBox(pub usize, pub usize);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Tuple-Like Struct as Newtypes"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"* Tuple-like structs allow you to create types that behave like tuples but have a name and can implement traits\n",
"* This allows you to create newtypes for stricter type checking\n",
"* This is a workaround for the *orphan rule* - [New Type Pattern](https://doc.rust-lang.org/book/ch20-02-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"The type of the variable image_bbox was redefined, so was lost.\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Distance traveled: 50 kilometers\n"
]
}
],
"source": [
"struct Distance(f64);\n",
"struct Velocity(f64);\n",
"struct Time(f64);\n",
"\n",
"// A newtype for stricter type checking\n",
"fn calculate_distance(velocity: Velocity, time: Time) -> Distance {\n",
" Distance(velocity.0 * time.0)\n",
"}\n",
"\n",
"let velocity = Velocity(10.0);\n",
"let time = Time(5.0);\n",
"let distance = calculate_distance(velocity, time);\n",
"\n",
"println!(\"Distance traveled: {} kilometers\", distance.0);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Unit-Like Structs"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"* The third kind of struct is a little obscure: it declares a struct type with no elements at all:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"struct Onesuch;\n",
"\n",
"let o = Onesuch;"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"* This allows to create types that can act as tags or markers"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Defining Methods - `impl`"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
"pub struct Queue {\n",
" older: Vec<char>,\n",
" younger: Vec<char>\n",
"}\n",
"\n",
"impl Queue {\n",
" pub fn push(&mut self, c: char) {\n",
" self.younger.push(c);\n",
" }\n",
"\n",
" pub fn pop(&mut self) -> Option<char> {\n",
" if self.older.is_empty() {\n",
" if self.younger.is_empty() {\n",
" return None;\n",
" }\n",
"\n",
" use std::mem::swap;\n",
" swap(&mut self.older, &mut self.younger);\n",
" self.older.reverse();\n",
" }\n",
"\n",
" self.older.pop()\n",
" }\n",
"\n",
" pub fn is_empty(&self) -> bool {\n",
" self.older.is_empty() && self.younger.is_empty()\n",
" }\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"let mut q = Queue{ older: Vec::new(), younger: Vec::new() };\n",
"\n",
"q.push('@');\n",
"q.push('$');\n",
"assert!(q.pop() == Some('@'));\n",
"\n",
"q.push('*');\n",
"assert!(q.pop() == Some('$'));\n",
"assert!(q.pop() == Some('*'));\n",
"assert!(q.pop() == None);\n",
"\n",
"assert!(q.is_empty());"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
"impl Queue {\n",
" pub fn split(self) -> (Vec<char>, Vec<char>) {\n",
" (self.older, self.younger)\n",
" }\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
"let mut q = Queue { older: Vec::new(), younger: Vec::new() };\n",
"q.push('Q');\n",
"q.push('U');\n",
"assert!(q.pop() == Some('Q'));\n",
"q.push('X');\n",
"\n",
"let (older, younger) = q.split();\n",
"\n",
"assert_eq!(older, vec!['U']);\n",
"assert_eq!(younger, vec!['X']);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### `new` Method"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"* Constructor function - doesn't take `self` as an argument (**static function**)"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [],
"source": [
"impl Queue {\n",
" pub fn new() -> Self {\n",
" Queue { older: Vec::new(), younger: Vec::new() }\n",
" }\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [],
"source": [
"let mut q = Queue::new();\n",
"\n",
"q.push('a');\n",
"q.push('b');\n",
"q.push('c');\n",
"\n",
"assert_eq!(q.pop(), Some('a'));\n",
"assert_eq!(q.pop(), Some('b'));\n",
"assert_eq!(q.pop(), Some('c'));\n",
"assert_eq!(q.pop(), None);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Generic Structs"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"* Generics allow you to write flexible, reusable functions and types that can work with any data type. Instead of specifying concrete types, you use type parameters, which are placeholders for types that will be specified when the function or type is used.\n",
"* Generics use parameters passed in angle brackets to specify the types they work with. For example, `fn example<T>(param: T) {}` uses `T` as a generic type parameter."
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [],
"source": [
"pub struct Stack<T> {\n",
" items: Vec<T>\n",
"}\n",
"\n",
"impl<T> Stack<T> {\n",
" pub fn new() -> Stack<T> {\n",
" Stack{ items: Vec::new() }\n",
" }\n",
"\n",
" pub fn push(&mut self, item: T) {\n",
" self.items.push(item)\n",
" }\n",
"\n",
" pub fn pop(&mut self) -> Option<T> {\n",
" self.items.pop()\n",
" }\n",
"\n",
" pub fn is_empty(&self) -> bool {\n",
" self.items.is_empty()\n",
" }\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"* For static method calls, you can supply the type parameter explicitly using the turbofish `::<>` notation:"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [],
"source": [
"let s = Stack::<char>::new();"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"* But in practice, Rust can figure it out for you:"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [],
"source": [
"let mut stack_1 = Stack::new();\n",
"let mut stack_2 = Stack::new();\n",
"\n",
"stack_1.push(\"Hello\");\n",
"stack_2.push(3.1415);\n",
"\n",
"stack_1.push(\"World\");\n",
"stack_2.push(2.71);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Deriving Common Traits for Structs"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"* Structs are not copyable or comparable by default\n",
"* You can derive the `Copy` and `Clone` traits for a struct type, but only if all its fields are copyable or clonable\n",
"* You can derive the `PartialEq` and `Eq` traits for a struct type, but only if all its fields are comparable for equality\n",
"* You can derive `Debug` for a struct type, but only if all its fields are printable"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {},
"outputs": [],
"source": [
"#[derive(Copy, Clone, Debug, PartialEq)]\n",
"struct Point {\n",
" x: f64,\n",
" y: f64\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"pt = Point { x: 42.42, y: 1.0 }\n",
"other_pt = Point { x: 0.0, y: 1.0 }\n"
]
}
],
"source": [
"let mut pt = Point{ x: 0.0, y: 1.0 };\n",
"\n",
"let other_pt = pt;\n",
"assert!(pt == other_pt);\n",
"\n",
"pt.x = 42.42;\n",
"assert!(pt != other_pt);\n",
"\n",
"println!(\"pt = {:?}\", pt);\n",
"println!(\"other_pt = {:?}\", other_pt);"
]
}
],
"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
}