{ "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(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(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(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 {\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 = 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 {\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` 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 = Box::new(Circle{position: Point{x: 10, y: 20}, radius: 5});\n", "let mut shape: Box = box_circle; // OK - Box 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> = 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 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> = 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 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;\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 {\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(mut iter: I) -> Vec // 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;\n", "}\n", "\n", "impl BidirectionalIterator for Counter {\n", " fn prev(&mut self) -> Option {\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\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", "::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(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 + Add + 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(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 {\n", " items: Vec\n", "}\n", "\n", "impl Stack {\n", " fn push(&mut self, item: T) {\n", " self.items.push(item);\n", " }\n", "\n", " fn pop(&mut self) -> Option {\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 {\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 }