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

1466 lines
42 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": [
"# Traits"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Defining Traits"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"* Traits are Rust’s take on interfaces or abstract base classes\n",
"* They define shared behavior that types can implement\n",
"* They enable polymorphism and code reuse"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [],
"source": [
"trait Shape {\n",
" fn move_by(&mut self, dx: i32, dy: i32);\n",
" fn area(&self) -> f64;\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Implementing Traits\n",
"\n",
"* To implement a trait for a type, we use the `impl` keyword"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [],
"source": [
"#[derive(Debug, Copy, Clone)]\n",
"struct Point {\n",
" x: i32,\n",
" y: i32\n",
"}\n",
"\n",
"#[derive(Debug, Copy, Clone)]\n",
"struct Circle {\n",
" position: Point,\n",
" radius: u32\n",
"}\n",
"\n",
"#[derive(Debug, Copy, Clone)]\n",
"struct Rectangle {\n",
" position: Point,\n",
" width: u32,\n",
" height: u32\n",
"}\n",
"\n",
"impl Shape for Circle {\n",
" fn move_by(&mut self, dx: i32, dy: i32) {\n",
" self.position.x += dx;\n",
" self.position.y += dy;\n",
" }\n",
"\n",
" fn area(&self) -> f64 {\n",
" std::f64::consts::PI * (self.radius as f64) * (self.radius as f64)\n",
" }\n",
"}\n",
"\n",
"impl Shape for Rectangle {\n",
"\n",
" fn move_by(&mut self, dx: i32, dy: i32) {\n",
" self.position.x += dx;\n",
" self.position.y += dy;\n",
" }\n",
"\n",
" fn area(&self) -> f64 {\n",
" (self.width * self.height) as f64\n",
" }\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"* We can implement many traits for a type:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Drawing the circle at Point { x: 0, y: 0 } with radius: 10\n",
"Drawing the rectangle at Point { x: 0, y: 0 } with width: 10 and height: 20\n",
"--- after move:\n",
"Drawing the circle at Point { x: 10, y: 20 } with radius: 10\n",
"Drawing the rectangle at Point { x: 10, y: 20 } with width: 10 and height: 20\n"
]
}
],
"source": [
"trait Drawable {\n",
" fn draw(&self);\n",
"}\n",
"\n",
"impl Drawable for Circle {\n",
" fn draw(&self) {\n",
" println!(\"Drawing the circle at {:?} with radius: {}\", self.position, self.radius);\n",
" }\n",
"}\n",
"\n",
"impl Drawable for Rectangle {\n",
" fn draw(&self) {\n",
" println!(\"Drawing the rectangle at {:?} with width: {} and height: {}\", self.position, self.width, self.height);\n",
" }\n",
"}\n",
"\n",
"fn main() {\n",
" let mut c = Circle { position: Point { x: 0, y: 0 }, radius: 10 };\n",
" let mut r = Rectangle { position: Point { x: 0, y: 0 }, width: 10, height: 20 };\n",
"\n",
" c.draw();\n",
" r.draw();\n",
"\n",
" c.move_by(10, 20);\n",
" r.move_by(10, 20);\n",
"\n",
" println!(\"--- after move:\");\n",
"\n",
" c.draw();\n",
" r.draw();\n",
"}\n",
"\n",
"main();"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Trait Bounds\n",
"\n",
"* Trait bounds specify that a generic can be any type that implements a trait. \n",
"* They work like a contracts at compile time\n",
"\n",
"### impl Trait Syntax\n",
"\n",
"* The `impl Trait` syntax is a shorthand for a longer form, called a **trait bound**"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [],
"source": [
"fn draw_shape(shape: &impl Drawable) {\n",
" shape.draw()\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Drawing the circle at Point { x: 10, y: 20 } with radius: 5\n",
"Drawing the rectangle at Point { x: 40, y: 50 } with width: 10 and height: 5\n"
]
}
],
"source": [
"let mut circle = Circle{position: Point{x: 10, y: 20}, radius: 5};\n",
"let mut rect = Rectangle{position: Point{x: 40, y: 50}, width: 10, height: 5};\n",
"\n",
"draw_shape(&circle);\n",
"draw_shape(&rect);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Trait Bound Syntax"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Drawing the circle at Point { x: 10, y: 20 } with radius: 5\n",
"Drawing the rectangle at Point { x: 40, y: 50 } with width: 10 and height: 5\n"
]
}
],
"source": [
"fn draw_shape<T: Drawable>(shape: &T) {\n",
" shape.draw()\n",
"}\n",
"\n",
"draw_shape(&circle);\n",
"draw_shape(&rect);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Many trait bounds"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"* We can specify multiple trait bounds on a generic type by using the `+` syntax"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [],
"source": [
"fn use_shape(shape: &mut (impl Shape + Drawable)) {\n",
" shape.move_by(10, 15);\n",
" shape.draw();\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [],
"source": [
"fn use_shape<T: Shape + Drawable>(shape: &mut T) {\n",
" shape.move_by(10, 15);\n",
" shape.draw();\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"* We can use a `where` clause to specify trait bounds on generic types"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [],
"source": [
"fn use_shape<T>(shape: &mut T)\n",
"where \n",
" T: Shape + Drawable \n",
"{\n",
" shape.move_by(10, 15);\n",
" shape.draw();\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [],
"source": [
"use_shape(&mut circle);\n",
"use_shape(&mut rect);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Returning Types that Implement Traits"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Drawing the circle at Point { x: 0, y: 0 } with radius: 1\n"
]
}
],
"source": [
"fn create_drawable(id: &str) -> Box<dyn Drawable> {\n",
" match id {\n",
" \"Circle\" => Box::new(Circle{position: Point{x: 0, y: 0}, radius: 1}),\n",
" \"Rectangle\" => Box::new(Rectangle{position: Point{x: 0, y: 0}, width: 2, height: 1}),\n",
" _ => panic!(\"Unknown shape\")\n",
" }\n",
"}\n",
"\n",
"let shape: Box<dyn Drawable> = create_drawable(\"Circle\");\n",
"shape.draw();"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Self in Traits\n",
"\n",
"* `Self` is an alias for the type we’re implementing the trait for"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [],
"source": [
"trait Cloneable {\n",
" fn clone(&self) -> Self; // the type of x.clone() is the same as the type of x, whatever that might be\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"* A trait that uses the `Self` type is incompatible with trait objects\n",
"* Trait objects require dynamic dispatch, but using `Self` in a trait implies static dispatch — the compiler must know the concrete type at compile time.\n"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [
{
"ename": "Error",
"evalue": "the trait `Cloneable` is not dyn compatible",
"output_type": "error",
"traceback": [
"\u001b[31m[E0038] Error:\u001b[0m the trait `Cloneable` is not dyn compatible",
" \u001b[38;5;246m╭\u001b[0m\u001b[38;5;246m─\u001b[0m\u001b[38;5;246m[\u001b[0m command_12:1:1 \u001b[38;5;246m]\u001b[0m",
" \u001b[38;5;246m│\u001b[0m",
" \u001b[38;5;246m1 │\u001b[0m \u001b[38;5;249mf\u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249md\u001b[0m\u001b[38;5;249mo\u001b[0m\u001b[38;5;249m_\u001b[0m\u001b[38;5;249mc\u001b[0m\u001b[38;5;249ml\u001b[0m\u001b[38;5;249mo\u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249m(\u001b[0m\u001b[38;5;249mp\u001b[0m\u001b[38;5;249mr\u001b[0m\u001b[38;5;249mo\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249mo\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249my\u001b[0m\u001b[38;5;249mp\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249m:\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249m&\u001b[0m\u001b[38;5;54md\u001b[0m\u001b[38;5;54my\u001b[0m\u001b[38;5;54mn\u001b[0m\u001b[38;5;54m \u001b[0m\u001b[38;5;54mC\u001b[0m\u001b[38;5;54ml\u001b[0m\u001b[38;5;54mo\u001b[0m\u001b[38;5;54mn\u001b[0m\u001b[38;5;54me\u001b[0m\u001b[38;5;54ma\u001b[0m\u001b[38;5;54mb\u001b[0m\u001b[38;5;54ml\u001b[0m\u001b[38;5;54me\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;249mB\u001b[0m\u001b[38;5;249mo\u001b[0m\u001b[38;5;249mx\u001b[0m\u001b[38;5;249m<\u001b[0m\u001b[38;5;249md\u001b[0m\u001b[38;5;249my\u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249mC\u001b[0m\u001b[38;5;249ml\u001b[0m\u001b[38;5;249mo\u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249ma\u001b[0m\u001b[38;5;249mb\u001b[0m\u001b[38;5;249ml\u001b[0m\u001b[38;5;249me\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;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;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 `Cloneable` is not dyn compatible",
"\u001b[38;5;246m───╯\u001b[0m"
]
}
],
"source": [
"fn do_clone(prototype: &dyn Cloneable) -> Box<dyn Cloneable> {\n",
" let cloned_object = prototype.clone();\n",
" Box::new(cloned_object)\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Trait Objects\n",
"\n",
"* A trait object allows to implement dynamic polymorphism in Rust"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [
{
"ename": "Error",
"evalue": "mismatched types",
"output_type": "error",
"traceback": [
"\u001b[31m[E0308] Error:\u001b[0m mismatched types",
" \u001b[38;5;246m╭\u001b[0m\u001b[38;5;246m─\u001b[0m\u001b[38;5;246m[\u001b[0m command_13:1:1 \u001b[38;5;246m]\u001b[0m",
" \u001b[38;5;246m│\u001b[0m",
" \u001b[38;5;246m3 │\u001b[0m \u001b[38;5;249ml\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249ms\u001b[0m\u001b[38;5;249mh\u001b[0m\u001b[38;5;249ma\u001b[0m\u001b[38;5;249mp\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249m:\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;100md\u001b[0m\u001b[38;5;100my\u001b[0m\u001b[38;5;100mn\u001b[0m\u001b[38;5;100m \u001b[0m\u001b[38;5;100mS\u001b[0m\u001b[38;5;100mh\u001b[0m\u001b[38;5;100ma\u001b[0m\u001b[38;5;100mp\u001b[0m\u001b[38;5;100me\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249m=\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;54mc\u001b[0m\u001b[38;5;54mi\u001b[0m\u001b[38;5;54mr\u001b[0m\u001b[38;5;54mc\u001b[0m\u001b[38;5;54ml\u001b[0m\u001b[38;5;54me\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;249mE\u001b[0m\u001b[38;5;249mR\u001b[0m\u001b[38;5;249mR\u001b[0m\u001b[38;5;249mO\u001b[0m\u001b[38;5;249mR\u001b[0m\u001b[38;5;249m:\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249mS\u001b[0m\u001b[38;5;249mh\u001b[0m\u001b[38;5;249ma\u001b[0m\u001b[38;5;249mp\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249md\u001b[0m\u001b[38;5;249mo\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249ms\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249mo\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249mh\u001b[0m\u001b[38;5;249ma\u001b[0m\u001b[38;5;249mv\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249ma\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249mc\u001b[0m\u001b[38;5;249mo\u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249ms\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249ma\u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249ms\u001b[0m\u001b[38;5;249mi\u001b[0m\u001b[38;5;249mz\u001b[0m\u001b[38;5;249me\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\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\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;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\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;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m\u001b[38;5;100m─\u001b[0m expected due to this",
" \u001b[38;5;240m │\u001b[0m \u001b[38;5;54m│\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 expected `dyn Shape`, found `Circle`",
"\u001b[38;5;246m───╯\u001b[0m"
]
},
{
"ename": "Error",
"evalue": "the size for values of type `dyn Shape` cannot be known at compilation time",
"output_type": "error",
"traceback": [
"\u001b[31m[E0277] Error:\u001b[0m the size for values of type `dyn Shape` cannot be known at compilation time",
" \u001b[38;5;246m╭\u001b[0m\u001b[38;5;246m─\u001b[0m\u001b[38;5;246m[\u001b[0m command_13:1:1 \u001b[38;5;246m]\u001b[0m",
" \u001b[38;5;246m│\u001b[0m",
" \u001b[38;5;246m3 │\u001b[0m \u001b[38;5;249ml\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;54ms\u001b[0m\u001b[38;5;54mh\u001b[0m\u001b[38;5;54ma\u001b[0m\u001b[38;5;54mp\u001b[0m\u001b[38;5;54me\u001b[0m\u001b[38;5;249m:\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249md\u001b[0m\u001b[38;5;249my\u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249mS\u001b[0m\u001b[38;5;249mh\u001b[0m\u001b[38;5;249ma\u001b[0m\u001b[38;5;249mp\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249m=\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249mc\u001b[0m\u001b[38;5;249mi\u001b[0m\u001b[38;5;249mr\u001b[0m\u001b[38;5;249mc\u001b[0m\u001b[38;5;249ml\u001b[0m\u001b[38;5;249me\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;249mE\u001b[0m\u001b[38;5;249mR\u001b[0m\u001b[38;5;249mR\u001b[0m\u001b[38;5;249mO\u001b[0m\u001b[38;5;249mR\u001b[0m\u001b[38;5;249m:\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249mS\u001b[0m\u001b[38;5;249mh\u001b[0m\u001b[38;5;249ma\u001b[0m\u001b[38;5;249mp\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249md\u001b[0m\u001b[38;5;249mo\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249ms\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249mo\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249mh\u001b[0m\u001b[38;5;249ma\u001b[0m\u001b[38;5;249mv\u001b[0m\u001b[38;5;249me\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249ma\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249mc\u001b[0m\u001b[38;5;249mo\u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249ms\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249ma\u001b[0m\u001b[38;5;249mn\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249m \u001b[0m\u001b[38;5;249ms\u001b[0m\u001b[38;5;249mi\u001b[0m\u001b[38;5;249mz\u001b[0m\u001b[38;5;249me\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;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 doesn't have a size known at compile-time",
" \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 help: consider borrowing here: `&`",
"\u001b[38;5;246m───╯\u001b[0m"
]
}
],
"source": [
"let circle = Circle{position: Point{x: 10, y: 20}, radius: 5};\n",
"\n",
"let shape: dyn Shape = circle; // ERROR: Shape does not have a constant size"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### References to Trait"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"* A reference to trait type, like `Shape` is called **trait object**. Like any other reference, a trait object points to some value, it has a lifetime, and it can be either mut or shared.\n",
"* In memory, a trait object is a **fat pointer** consisting of a pointer to the value, plus a pointer to a table representing that value’s type (virtual table).\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Shape area: 78.54\n",
"Shape area: 200.00\n"
]
}
],
"source": [
"fn main() {\n",
" let mut shape: &dyn Shape;\n",
" \n",
" let circle = Circle{position: Point{x: 10, y: 20}, radius: 5};\n",
" shape = &circle;\n",
" println!(\"Shape area: {:.2}\", shape.area());\n",
"\n",
" shape = &Rectangle{position: Point{x: 10, y: 20}, width: 10, height: 20};\n",
" println!(\"Shape area: {:.2}\", shape.area());\n",
"}\n",
"\n",
"main();"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Boxes to Trait\n",
"\n",
"* We can also use a `Box` to store a trait object\n",
"* A `Box<dyn Trait>` is a trait object that points to a value on the heap"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Shape area: 78.54\n",
"Shape area: 200.00\n"
]
}
],
"source": [
"let box_circle: Box<Circle> = Box::new(Circle{position: Point{x: 10, y: 20}, radius: 5});\n",
"let mut shape: Box<dyn Shape> = box_circle; // OK - Box<dyn Shape> is trait object\n",
"println!(\"Shape area: {:.2}\", shape.area());\n",
"\n",
"shape = Box::new(Rectangle{position: Point{x: 10, y: 20}, width: 10, height: 20});\n",
"println!(\"Shape area: {:.2}\", shape.area());"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Dynamic Polymorphism in Rust"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Shape area: 78.54\n",
"Shape area: 50.00\n",
"Shape area: 49.00\n"
]
}
],
"source": [
"#[derive(Debug)]\n",
"struct Square {\n",
" position: Point,\n",
" side: u32\n",
"} \n",
"\n",
"impl Shape for Square {\n",
"\n",
" fn move_by(&mut self, dx: i32, dy: i32) {\n",
" self.position.x += dx;\n",
" self.position.y += dy;\n",
" }\n",
"\n",
" fn area(&self) -> f64 {\n",
" (self.side * self.side) as f64\n",
" }\n",
"}\n",
"\n",
"impl Drawable for Square {\n",
" fn draw(&self) {\n",
" println!(\"Drawing the square at {:?} with side: {}\", self.position, self.side);\n",
" }\n",
"}\n",
"\n",
"fn main() {\n",
" let mut shapes: Vec<Box<dyn Shape>> = vec![\n",
" Box::new(Circle{position: Point{x: 10, y: 20}, radius: 5}),\n",
" Box::new(Rectangle{position: Point{x: 40, y: 50}, width: 10, height: 5}),\n",
" Box::new(Square{position: Point{x: 70, y: 80}, side: 7})\n",
" ];\n",
"\n",
" for shape in shapes.iter_mut() {\n",
" shape.move_by(5, 5);\n",
" println!(\"Shape area: {:.2}\", shape.area());\n",
" }\n",
"}\n",
"\n",
"main();"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Combining Traits\n",
"\n",
"* Trait objects can only use one trait at a time, but you can work around this by creating a supertrait:"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Drawing the circle at Point { x: 20, y: 35 } with radius: 5\n",
"Drawing the rectangle at Point { x: 50, y: 65 } with width: 10 and height: 5\n",
"Drawing the square at Point { x: 80, y: 95 } with side: 7\n"
]
}
],
"source": [
"trait DrawableShape: Shape + Drawable {} // Trait that combines Shape and Drawable - a supertrait\n",
"\n",
"impl<T> DrawableShape for T \n",
" where T: Shape + Drawable \n",
"{} // Implement DrawableShape for any type that implements Shape and Drawable - blanket implementation\n",
"\n",
"fn main() {\n",
" let mut drawable_shapes: Vec<Box<dyn DrawableShape>> = vec![\n",
" Box::new(Circle{position: Point{x: 10, y: 20}, radius: 5}),\n",
" Box::new(Rectangle{position: Point{x: 40, y: 50}, width: 10, height: 5}),\n",
" Box::new(Square{position: Point{x: 70, y: 80}, side: 7})\n",
" ];\n",
"\n",
" for shape in drawable_shapes.iter_mut() {\n",
" shape.move_by(10, 15);\n",
" shape.draw();\n",
" }\n",
"}\n",
"\n",
"main();"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Orphan Rules\n",
"\n",
"* The **orphan rule** prevent us from implementing a foreign trait on a foreign type\n",
"* **You are allowed to implement any trait on any type, as long as either the trait or the type is local to your crate**"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Extension traits"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [],
"source": [
"// Extension trait for checking if a character is an emoji\n",
"trait IsEmoji {\n",
" fn is_emoji(&self) -> bool;\n",
"}\n",
"\n",
"// Implementation of the IsEmoji trait for the char type\n",
"impl IsEmoji for char {\n",
" fn is_emoji(&self) -> bool {\n",
" let c = *self as u32;\n",
" (c >= 0x1F600 && c <= 0x1F64F) || (c >= 0x1F300 && c <= 0x1F5FF) || (c >= 0x1F680 && c <= 0x1F6FF) || (c >= 0x1F900 && c <= 0x1F9FF)\n",
" }\n",
"}\n",
"\n",
"fn main() {\n",
" let c = '😀';\n",
" println!(\"Is '{}' an emoji? {}\", c, c.is_emoji());\n",
" println!(\"Is '{}' an emoji? {}\", '@', '@'.is_emoji());\n",
"}\n",
"\n",
"main();"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"* `IsEmoji` is an *extension trait* - adds a method to an existing type"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Generic `impl` blocks\n",
"\n",
"* A generic `impl` block for a trait allows you to implement a trait for any type that satisfies certain bounds. This is a powerful way to write reusable, composable behavior across many types."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [],
"source": [
"use std::fmt::Debug;\n",
"\n",
"trait DrawInDebug {\n",
" fn draw_dbg(&self);\n",
"}\n",
"\n",
"impl<T: Debug + Drawable> DrawInDebug for T {\n",
" fn draw_dbg(&self) {\n",
" println!(\"Drawing a {:?}\", self);\n",
" }\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [],
"source": [
"circle.draw_dbg();\n",
"rect.draw_dbg();"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Associated Types\n",
"\n",
"* Associated types are a way to associate a type placeholder with a trait\n",
"* Each type that implements the trait can specify its own concrete type for the associated type"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0\n",
"1\n",
"2\n",
"3\n",
"4\n"
]
}
],
"source": [
"trait Iterator {\n",
" type Item; // associated type\n",
" fn next(&mut self) -> Option<Self::Item>;\n",
"}\n",
"\n",
"struct Counter {\n",
" value: u32,\n",
" sentinel: u32\n",
"}\n",
"\n",
"impl Counter {\n",
" fn new(sentinel: u32) -> Counter {\n",
" Counter{value: 0, sentinel}\n",
" }\n",
"}\n",
"\n",
"impl Iterator for Counter {\n",
" type Item = u32; // definition of associated type for Counter\n",
"\n",
" fn next(&mut self) -> Option<Self::Item> {\n",
" if self.value < self.sentinel {\n",
" let result = self.value;\n",
" self.value += 1;\n",
" Some(result)\n",
" } else {\n",
" None\n",
" }\n",
" }\n",
"}\n",
"\n",
"fn main() {\n",
" let mut counter = Counter::new(5);\n",
"\n",
" while let Some(value) = counter.next() {\n",
" println!(\"{}\", value);\n",
" }\n",
"}\n",
"\n",
"main();"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"* Generic code can use associated types:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [],
"source": [
"fn collect_into_vector<I: Iterator>(mut iter: I) -> Vec<I::Item> // I::Item is the associated type of the Iterator\n",
"{\n",
" let mut results = Vec::new();\n",
"\n",
" while let Some(value) = iter.next() {\n",
" results.push(value);\n",
" }\n",
"\n",
" results\n",
"}\n",
"\n",
"fn main() { \n",
" let numbers = collect_into_vector(Counter::new(5));\n",
" println!(\"Collected numbers: {:?}\", numbers);\n",
"}\n",
"\n",
"main();"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Subtraits\n",
"\n",
"* A trait can be an extension of another trait"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Forward iteration:\n",
"Counter value: 0\n",
"Counter value: 1\n",
"Counter value: 2\n",
"Counter value: 3\n",
"Counter value: 4\n",
"\n",
"Backward iteration:\n",
"Counter value: 4\n",
"Counter value: 3\n",
"Counter value: 2\n",
"Counter value: 1\n",
"Counter value: 0\n"
]
}
],
"source": [
"trait BidirectionalIterator: Iterator {\n",
" fn prev(&mut self) -> Option<Self::Item>;\n",
"}\n",
"\n",
"impl BidirectionalIterator for Counter {\n",
" fn prev(&mut self) -> Option<Self::Item> {\n",
" if self.value > 0 {\n",
" let result = self.value;\n",
" self.value -= 1;\n",
" Some(self.value)\n",
" } else {\n",
" None\n",
" }\n",
" }\n",
"}\n",
"\n",
"fn main() {\n",
" let mut counter = Counter::new(5);\n",
"\n",
" println!(\"Forward iteration:\");\n",
" while let Some(value) = counter.next() {\n",
" println!(\"Counter value: {}\", value);\n",
" }\n",
"\n",
" println!(\"\\nBackward iteration:\");\n",
" while let Some(value) = counter.prev() {\n",
" println!(\"Counter value: {}\", value);\n",
" }\n",
"}\n",
"\n",
"main();"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Static Methods in Traits\n",
"\n",
"* Traits can have static methods"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Creating set:\n",
"Set contains 'apple': true\n",
"Set contains 'grape': false\n",
"\n",
"Creating set from slice:\n",
"Set contains 'apple': true\n",
"Set contains 'grape': false\n"
]
}
],
"source": [
"trait StringSet {\n",
" /// Creates a new, empty set. - static method\n",
" fn new() -> Self;\n",
"\n",
" /// Creates a set from a slice of strings. - static method\n",
" fn from_slice(values: &[&str]) -> Self;\n",
"\n",
" /// Adds a string to the set.\n",
" fn add(&mut self, value: &str);\n",
"\n",
" /// Removes a string from the set.\n",
" fn contains(&self, value: &str) -> bool;\n",
"}\n",
"\n",
"struct HashSet {\n",
" data: std::collections::HashSet<String>\n",
"}\n",
"\n",
"impl StringSet for HashSet {\n",
" fn new() -> Self {\n",
" HashSet{data: std::collections::HashSet::new()}\n",
" }\n",
"\n",
" fn from_slice(values: &[&str]) -> Self {\n",
" let mut set = HashSet::new();\n",
" for value in values {\n",
" set.add(value);\n",
" }\n",
" set\n",
" }\n",
"\n",
" fn add(&mut self, value: &str) {\n",
" self.data.insert(value.to_string());\n",
" }\n",
"\n",
" fn contains(&self, value: &str) -> bool {\n",
" self.data.contains(value)\n",
" }\n",
"}\n",
"\n",
"fn main() {\n",
" println!(\"Creating set:\");\n",
" let mut set = HashSet::new();\n",
" set.add(\"apple\");\n",
" set.add(\"banana\");\n",
" set.add(\"orange\");\n",
"\n",
" println!(\"Set contains 'apple': {}\", set.contains(\"apple\"));\n",
" println!(\"Set contains 'grape': {}\", set.contains(\"grape\"));\n",
"\n",
" println!(\"\\nCreating set from slice:\");\n",
"\n",
" let set = HashSet::from_slice(&[\"apple\", \"banana\", \"orange\"]);\n",
" println!(\"Set contains 'apple': {}\", set.contains(\"apple\"));\n",
" println!(\"Set contains 'grape': {}\", set.contains(\"grape\"));\n",
"}\n",
"\n",
"main();"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Fully qualified methods\n",
"\n",
"* A fully qualified method call is a way to explicitly specify which trait’s method you want to call, especially when:\n",
" - Multiple traits define methods with the same name.\n",
" - You’re calling a trait method that isn’t in scope via automatic dispatch.\n",
" - You want to call a method on a trait for a type that implements it, but the method isn’t available via dot syntax.\n"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Drawing a deck of cards on the screen\n",
"Drawing a card from the deck\n",
"Drawing a card from the deck\n"
]
}
],
"source": [
"mod FullyQualifiedSyntax {\n",
" \n",
" pub trait Drawable {\n",
" fn draw(&self);\n",
" }\n",
"\n",
" pub trait Deck {\n",
" fn draw(&self) -> String;\n",
" }\n",
"\n",
" pub struct DeckOfCards;\n",
"\n",
" impl Drawable for DeckOfCards {\n",
" fn draw(&self) {\n",
" println!(\"Drawing a deck of cards on the screen\");\n",
" }\n",
" }\n",
"\n",
" impl Deck for DeckOfCards {\n",
" fn draw(&self) -> String {\n",
" println!(\"Drawing a card from the deck\");\n",
" \"Ace of Spades\".to_string()\n",
" }\n",
" }\n",
"}\n",
"\n",
"use FullyQualifiedSyntax::*;\n",
"\n",
"let deck_of_cards = DeckOfCards;\n",
"\n",
"// deck_of_cards.draw(); // ambigous call of draw() method\n",
"\n",
"FullyQualifiedSyntax::Drawable::draw(&deck_of_cards);\n",
"Deck::draw(&deck_of_cards);\n",
"<DeckOfCards as Deck>::draw(&deck_of_cards);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Reverse-Engineering Bounds\n",
"\n",
"* When writing generic function or structs with trait bounds, it is often useful to reverse-engineer the bounds from the function body.\n",
"* This approach helps in understanding the minimum requirements for the types used in generics.\n",
"* Compiler errors can guide the addition of necessary trait bounds."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### First attempt"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [],
"source": [
"fn dot<N>(v1: &[N], v2: &[N]) -> N {\n",
" let mut total: N = N::default();\n",
" for i in 0 .. v1.len() {\n",
" total = total + v1[i] * v2[i];\n",
" }\n",
" total\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### After reading compiler errors"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [],
"source": [
"use std::ops::{Add, Mul};\n",
"\n",
"fn dot<N: Default + Mul<Output=N> + Add<Output=N> + Copy>(v1: &[N], v2: &[N]) -> N \n",
"{\n",
" let mut total: N = N::default();\n",
" for i in 0 .. v1.len() {\n",
" total = total + v1[i] * v2[i];\n",
" }\n",
" total\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [],
"source": [
"let v1 = [1, 2, 3];\n",
"let v2 = vec![4, 5, 6];\n",
"\n",
"dot(&v1[..], &v2[..])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Generics"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Generic Function"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [],
"source": [
"fn min<T: Ord>(a: T, b: T) -> T {\n",
" if a < b {\n",
" a\n",
" } else {\n",
" b\n",
" }\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [
{
"data": {
"text/plain": [
"1"
]
},
"execution_count": 25,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"min(1, 2)"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [
{
"data": {
"text/plain": [
"\"hello\""
]
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"min(\"hello\", \"world\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Generic Structure\n",
"\n",
"* Generic structures allow us to define data types that can hold values of any type"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Popped: 3\n",
"Popped: 2\n",
"Popped: 1\n"
]
}
],
"source": [
"struct Stack<T> {\n",
" items: Vec<T>\n",
"}\n",
"\n",
"impl<T> Stack<T> {\n",
" fn push(&mut self, item: T) {\n",
" self.items.push(item);\n",
" }\n",
"\n",
" fn pop(&mut self) -> Option<T> {\n",
" self.items.pop()\n",
" }\n",
"}\n",
"\n",
"fn main() {\n",
" let mut stack = Stack{items: Vec::new()};\n",
" stack.push(1);\n",
" stack.push(2);\n",
" stack.push(3);\n",
"\n",
" while let Some(item) = stack.pop() {\n",
" println!(\"Popped: {}\", item);\n",
" }\n",
"}\n",
"\n",
"main();"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Generic Traits\n",
"\n",
"* Traits can also be generic over types"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [],
"source": [
"mod explain {\n",
" /// std::ops::Mul, the trait for types that support `*`.\n",
" pub trait Mul<RHS=Self> {\n",
" /// The resulting type after applying the `*` operator\n",
" type Output;\n",
"\n",
" /// The method for the `*` operator\n",
" fn mul(self, rhs: RHS) -> Self::Output;\n",
" }\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {
"vscode": {
"languageId": "rust"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Result: -5 + 10i\n"
]
}
],
"source": [
"#[derive(Debug)]\n",
"struct Complex {\n",
" real: f64,\n",
" imag: f64\n",
"}\n",
"\n",
"use std::ops::Mul;\n",
"\n",
"impl Mul for Complex {\n",
" type Output = Complex;\n",
"\n",
" fn mul(self, rhs: Complex) -> Complex {\n",
" Complex {\n",
" real: self.real * rhs.real - self.imag * rhs.imag,\n",
" imag: self.real * rhs.imag + self.imag * rhs.real\n",
" }\n",
" }\n",
"}\n",
"\n",
"fn main() {\n",
" let a = Complex{real: 1.0, imag: 2.0};\n",
" let b = Complex{real: 3.0, imag: 4.0};\n",
"\n",
" let c = a.mul(b);\n",
" println!(\"Result: {} + {}i\", c.real, c.imag);\n",
"}\n",
"\n",
"main();"
]
}
],
"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
}