{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Operator overloading\n", "\n", "Rust allows you to overload operators by implementing specific traits from the `std::ops` module." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "| Category | Trait | Operator |\n", "| -------------------- | -------------------------- | --------------------------------- |\n", "| Unary operators | `std::ops::Neg` | `-x ` |\n", "| | `std::ops::Not` | `!x ` |\n", "| Arithmetic operators | `std::ops::Add` | `x + y` |\n", "| | `std::ops::Sub` | `x - y` |\n", "| | `std::ops::Mul` | `x * y` |\n", "| | `std::ops::Div` | `x / y` |\n", "| | `std::ops::Rem` | `x % y` |\n", "| Bitwise operators | `std::ops::BitAnd` | `x & y` |\n", "| | `std::ops::BitOr ` | `x \\| y` |\n", "| | `std::ops::BitXor` | `x ^ y` |\n", "| | `std::ops::Shl ` | `x << y` |\n", "| | `std::ops::Shr ` | `x >> y` |\n", "| Compound assignment arithmetic operators | `std::ops::AddAssign` | `x += y` |\n", "| | `std::ops::MulAssign` | `x *= y` |\n", "| | `std::ops::SubAssign` | `x -= y` |\n", "| | `std::ops::DivAssign` | `x /= y` |\n", "| | `std::ops::RemAssign` | `x %= y` |\n", "| Compound assignment bitwise operators | `std::ops::BitAndAssign` | `x &= y` |\n", "| | `std::ops::BitOrAssign ` | `x \\| = y` |\n", "| | `std::ops::BitXorAssign` | `x ^= y` |\n", "| | `std::ops::ShlAssign ` | `x <<= y` |\n", "| | `std::ops::ShrAssign ` | `x >>= y` |\n", "| Comparison | `std::cmp::PartialEq ` | `x == y`, `x != y` |\n", "| | `std::cmp::PartialOrd` | `x < y,  x <= y,  x > y,  x >= y` |\n", "| Indexing | `std::ops::Index` | `x[y],  &x[y]` |\n", "| | `std::ops::IndexMut` | `x[y] = z,  &mut x[y]` |" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [], "source": [ "#[derive(Debug, Clone, Copy)]\n", "struct Complex {\n", " re: T,\n", " im: T,\n", "}" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [], "source": [ "let z: Complex = Complex { re: 1.0, im: 2.0 };" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [ { "ename": "Error", "evalue": "an implementation of `Add` might be missing for `Complex`", "output_type": "error", "traceback": [ "\u001b[31mError:\u001b[0m an implementation of `Add` might be missing for `Complex`" ] } ], "source": [ "z + z" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Implementing `Add` trait" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [], "source": [ "mod explain {\n", " trait Add {\n", " type Output;\n", " fn add(self, rhs: RHS) -> Self::Output;\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [], "source": [ "use std::ops::Add;\n", "\n", "impl Add for Complex \n", "where T: Add {\n", " type Output = Self; \n", " \n", " fn add(self, rhs: Complex) -> Self::Output {\n", " Complex {\n", " re: self.re + rhs.re,\n", " im: self.im + rhs.im,\n", " }\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [ { "data": { "text/plain": [ "Complex { re: 2.0, im: 4.0 }" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "z + z" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Implementing `Neg` trait" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [], "source": [ "mod explain {\n", " trait Neg {\n", " type Output;\n", " fn neg(self) -> Self::Output;\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [ { "ename": "Error", "evalue": "an implementation of `std::ops::Neg` might be missing for `Complex`", "output_type": "error", "traceback": [ "\u001b[31mError:\u001b[0m an implementation of `std::ops::Neg` might be missing for `Complex`" ] } ], "source": [ "-z" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [], "source": [ "use std::ops::Neg;\n", "\n", "impl Neg for Complex\n", "where T: Neg {\n", " type Output = Self;\n", " \n", " fn neg(self) -> Self::Output {\n", " Complex {\n", " re: -self.re,\n", " im: -self.im,\n", " }\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [ { "data": { "text/plain": [ "Complex { re: -1.0, im: -2.0 }" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "-z" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Compound Assignment Operators: `+=`, `-=`, `*=`, `/=`, `%=`" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [], "source": [ "mod explain {\n", " trait AddAssign {\n", " fn add_assign(&mut self, rhs: RHS);\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [], "source": [ "use std::ops::AddAssign;\n", "\n", "impl AddAssign for Complex\n", "where T: AddAssign {\n", " fn add_assign(&mut self, rhs: Complex) {\n", " self.re += rhs.re;\n", " self.im += rhs.im;\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [ { "data": { "text/plain": [ "Complex { re: 4.0, im: 6.0 }" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "let mut c = Complex { re: 1.0, im: 2.0 };\n", "\n", "c += Complex { re: 3.0, im: 4.0 };\n", "\n", "c" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Equality and Ordering\n", "\n", "### PartialEq\n", "\n", "* in order to use `==` and `!=` operators, implement `PartialEq` trait" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [], "source": [ "mod explain {\n", " trait PartialEq \n", " where RHS: ?Sized\n", " {\n", " fn eq(&self, other: &RHS) -> bool; \n", " fn ne(&self, other: &RHS) -> bool { !self.eq(other) }\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [], "source": [ "use std::cmp::PartialEq;\n", "\n", "impl PartialEq for Complex {\n", " fn eq(&self, other: &Complex) -> bool {\n", " self.re == other.re && self.im == other.im\n", " }\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* now we can check for equality using `==` and `!=` operators" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [ { "data": { "text/plain": [ "true" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "z == z" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [ { "data": { "text/plain": [ "true" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "z != c" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### PartialEq & f64\n", "\n", " * Mathematical definition of an equivalence relation, of which equality is one instance, imposes three requirements. For any values `x` and `y`:\n", " * If `x == y` is true, then `y == x` must be true as well. In other words, swapping the two sides of an equality comparison doesn’t affect the result.\n", " * If `x == y` and `y == z`, then it must be the case that `x == z`. Given any chain of values, each equal to the next, each value in the chain is directly equal to every other. Equality is contagious.\n", " * It must always be true that `x == x`.\n", "\n", " Types `f32` and `f64` are IEEE standard floating-point values. According to that standard, expressions like `0.0/0.0` and others with no appropriate value must produce special not-a-number values, usually referred to as `NaN` values. The standard further requires that a `NaN` value be treated as unequal to every other value—including itself." ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [], "source": [ "assert!(f64::is_nan(0.0 / 0.0));\n", "\n", "assert_eq!(0.0/0.0 == 0.0/0.0, false);\n", "assert_eq!(0.0/0.0 != 0.0/0.0, true);" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "NaN == NaN yields false\n" ] } ], "source": [ "println!(\"{:?} == {:?} yields {:?}\", 0.0/0.0, f64::NAN, 0.0/0.0 == f64::NAN);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "While Rust’s `==` operator meets the first two requirements for equivalence relations, it clearly doesn’t meet the third when used on IEEE floating-point values. This is called a partial equivalence relation, so Rust uses the name `PartialEq` for the `==` operator’s built-in trait." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Eq - Full equivalence relation\n", "\n", "If you’d prefer your generic code to require a full equivalence relation, you can instead use the `std::cmp::Eq` trait as a bound, which represents a full equivalence relation: if a type implements `Eq`, then `x == x` must be `true` for every value `x` of that type. \n", "\n", "In practice, almost every type that implements `PartialEq` should implement `Eq` as well; `f32` and `f64` are the only types in the standard library that are `PartialEq` but not `Eq`.\n", "\n", "The standard library defines `Eq` as an extension of `PartialEq`, adding no new methods:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [], "source": [ "mod explain {\n", " trait Eq : PartialEq {}\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Implementing `Eq` for `Complex` is straightforward:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [], "source": [ "impl Eq for Complex {}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We could also derive `Eq` for generic types that implement `PartialEq`:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [], "source": [ "#[derive(Copy, Clone, Debug, PartialEq, Eq)]\n", "struct Value {\n", " value: T\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Derived implementations on a generic type may depend on the type parameters. With the derive attribute, `Value` would implement `Eq`, because `i32` does, but `Value` would implement only `PartialEq`, since `f32` doesn’t implement `Eq`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### PartialOrd" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Any comparison for `f64` or `f32` involving `NaN` will return `false`:" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [], "source": [ "assert_eq!(0.0/0.0 < 0.0/0.0, false);\n", "assert_eq!(0.0/0.0 > 0.0/0.0, false);\n", "assert_eq!(0.0/0.0 <= 0.0/0.0, false);\n", "assert_eq!(0.0/0.0 >= 0.0/0.0, false);" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [], "source": [ "mod explain {\n", " enum Ordering {\n", " Less,\n", " Equal,\n", " Greater,\n", " }\n", "\n", " trait PartialOrd: PartialEq \n", " where \n", " RHS: ?Sized \n", " {\n", " fn partial_cmp(&self, other: &RHS) -> Option;\n", " \n", " fn lt(&self, other: &RHS) -> bool { \n", " match self.partial_cmp(other) {\n", " Some(Ordering::Less) => true,\n", " _ => false,\n", " }\n", " }\n", " \n", " fn le(&self, other: &RHS) -> bool { \n", " match self.partial_cmp(other) {\n", " Some(Ordering::Less) | Some(Ordering::Equal) => true,\n", " _ => false,\n", " }\n", " }\n", " \n", " fn gt(&self, other: &RHS) -> bool { \n", " match self.partial_cmp(other) {\n", " Some(Ordering::Greater) => true,\n", " _ => false,\n", " }\n", " }\n", " \n", " fn ge(&self, other: &RHS) -> bool { \n", " match self.partial_cmp(other) {\n", " Some(Ordering::Greater) | Some(Ordering::Equal) => true,\n", " _ => false,\n", " }\n", " }\n", " }\n", "\n", " // trait for types that can be totally ordered\n", " trait Ord: Eq + PartialOrd {\n", " fn cmp(&self, other: &Self) -> Ordering;\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [], "source": [ "#[derive(PartialEq, Eq, Debug)]\n", "struct Interval {\n", " lower: T, // inclusive\n", " upper: T // exclusive \n", "}" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [], "source": [ "use std::cmp::{Ordering, PartialOrd};\n", "\n", "impl PartialOrd for Interval {\n", " fn partial_cmp(&self, other: &Interval) -> Option {\n", " if self == other {\n", " Some(Ordering::Equal)\n", " } else if self.lower >= other.upper {\n", " Some(Ordering::Greater)\n", " } else if self.upper <= other.lower {\n", " Some(Ordering::Less)\n", " } else {\n", " None\n", " } \n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [], "source": [ "assert!(Interval { lower: 10, upper: 20 } < Interval { lower: 20, upper: 40 });\n", "assert!(Interval { lower: 7, upper: 8 } >= Interval { lower: 0, upper: 1 });\n", "assert!(Interval { lower: 7, upper: 8 } <= Interval { lower: 7, upper: 8 });\n", "\n", "// Overlapping intervals aren't ordered with respect to each other.\n", "let left = Interval { lower: 10, upper: 30 };\n", "let right = Interval { lower: 20, upper: 40 };\n", "assert!(!(left < right));\n", "assert!(!(left >= right));" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### PartialOrd & f64\n", "\n", "Floating-point numbers have a `PartialOrd` implementation, because `NaN` is neither greater than, less than, nor equal to any other value." ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [ { "data": { "text/plain": [ "None" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "3.14.partial_cmp(&f64::NAN)" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [ { "data": { "text/plain": [ "None" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "f64::NAN.partial_cmp(&f64::NAN)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Indexing\n", "\n", "* In order to use indexing expression `obj[i]` for a given type we have to implement `Index` (and optionally `IndexMut`) trait:" ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [], "source": [ "mod explain {\n", " \n", " trait Index {\n", " type Output: ?Sized;\n", " fn index(&self, index: Idx) -> &Self::Output;\n", " }\n", "\n", " trait IndexMut: Index {\n", " fn index_mut(&mut self, index: Idx) -> &mut Self::Output;\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [], "source": [ "struct Image

{\n", " width: usize,\n", " pixels: Vec

,\n", "}\n", "\n", "impl Image

{\n", "\n", " /// Create a new image with the given dimensions.\n", " fn new(width: usize, height: usize) -> Self {\n", " Image {\n", " width: width,\n", " pixels: vec![P::default(); width * height],\n", " }\n", " }\n", "}\n", "\n", "impl

std::ops::Index for Image

{\n", " type Output = [P];\n", " \n", " fn index(&self, row: usize) -> &[P] {\n", " let start = row * self.width;\n", " &self.pixels[start..start + self.width]\n", " }\n", "}\n", "\n", "impl

std::ops::IndexMut for Image

{\n", " fn index_mut(&mut self, row: usize) -> &mut [P] {\n", " let start = row * self.width;\n", " &mut self.pixels[start..start + self.width]\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [], "source": [ "let mut bitmap = Image::new(2, 2);\n", "\n", "bitmap[0][0] = 1;\n", "bitmap[0][1] = 2;\n", "\n", "assert!(bitmap[0][0] == 1);\n", "assert!(bitmap[0][1] == 2);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Sorting floats" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* Common `sort()` method from `std::slice::SliceExt` trait doesn't work for floats because of the `NaN` values:" ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [ { "ename": "Error", "evalue": "the trait bound `{float}: Ord` is not satisfied", "output_type": "error", "traceback": [ "\u001b[31m[E0277] Error:\u001b[0m the trait bound `{float}: Ord` is not satisfied", " \u001b[38;5;246m╭\u001b[0m\u001b[38;5;246m─\u001b[0m\u001b[38;5;246m[\u001b[0m command_30:1:1 \u001b[38;5;246m]\u001b[0m", " \u001b[38;5;246m│\u001b[0m", " \u001b[38;5;246m3 │\u001b[0m \u001b[38;5;249md\u001b[0m\u001b[38;5;249ma\u001b[0m\u001b[38;5;249mt\u001b[0m\u001b[38;5;249ma\u001b[0m\u001b[38;5;249m.\u001b[0m\u001b[38;5;54ms\u001b[0m\u001b[38;5;54mo\u001b[0m\u001b[38;5;54mr\u001b[0m\u001b[38;5;54mt\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;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 the trait `Ord` is not implemented for `{float}`", "\u001b[38;5;246m───╯\u001b[0m" ] } ], "source": [ "let mut data = vec![2.1, 3.14, 0.3, 0.271, 0.0, 5.34, 1.0, 0.0];\n", "\n", "data.sort();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* When we assume that `NaN` is not present in the slice, we can use `sort_by()` method from `std::slice::SliceExt` trait combined with `partial_cmp()` method from `std::cmp::PartialOrd` trait:" ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[-inf, NaN, NaN, 0.0, 0.271, 0.3, 1.0, 2.1, 3.14, 3.141592653589793, 5.34, inf]\n" ] } ], "source": [ "let mut data = vec![2.1, 3.14, 0.3, 0.271, 0.0, std::f64::NAN, 5.34, 1.0, 0.0/0.0, std::f64::consts::PI, std::f64::INFINITY, std::f64::NEG_INFINITY];\n", "\n", "data.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Less));\n", "\n", "println!(\"{:?}\", data);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Total ordering for floats\n", "\n", "* Since Rust 1.64 we can use `f64::total_cmp()` method. It returns `Ordering` enum which can be used to sort floats.\n", "\n", "* The floating point values are totally ordered in the following sequence:\n", "\n", " * negative quiet NaN\n", " * negative signaling NaN\n", " * negative infinity\n", " * negative numbers\n", " * negative subnormal numbers\n", " * negative zero\n", " * positive zero\n", " * positive subnormal numbers\n", " * positive numbers\n", " * positive infinity\n", " * positive signaling NaN\n", " * positive quiet NaN." ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [ { "data": { "text/plain": [ "Greater" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "f64::NAN.total_cmp(&std::f64::consts::PI)" ] }, { "cell_type": "code", "execution_count": 33, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[-inf, 0.0, 0.271, 0.3, 1.0, 2.1, 3.14, 3.141592653589793, 5.34, inf, NaN, NaN]\n" ] } ], "source": [ "let mut data = vec![2.1, 3.14, 0.3, 0.271, 0.0, std::f64::NAN, 5.34, 1.0, 0.0/0.0, std::f64::consts::PI, std::f64::INFINITY, std::f64::NEG_INFINITY];\n", "\n", "// data.sort_by(|a, b| f64::total_cmp(a, b));\n", "data.sort_by(f64::total_cmp);\n", "\n", "println!(\"{:?}\", data);" ] } ], "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 }