{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Smart Pointers" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## `Box`\n", "\n", "* A smart pointer that allocates data on the heap.\n", "* Provides ownership for the heap data.\n", "* Dereferences to the data it points to.\n", "* Useful for recursive data types and large data on the heap." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Storing Data on Heap" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "5\n" ] } ], "source": [ "fn main() {\n", " let on_heap = Box::new(5);\n", " println!(\"{}\", on_heap);\n", "}\n", "\n", "main();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Recursive Data Structures" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [], "source": [ "enum List {\n", " Cons(i32, Box), // Box allows for recursive data structures\n", " Nil,\n", "}\n", "\n", "use List::{Cons, Nil};\n", "\n", "fn main() {\n", " let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));\n", "}\n", "\n", "main();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Dereferencing" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [], "source": [ "fn main() {\n", " let x = 5;\n", " let y = &x;\n", " let z = Box::new(x);\n", "\n", " assert!(5 == x);\n", " assert!(5 == *y);\n", " assert!(5 == *z);\n", "}\n", "\n", "main();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### How it works" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Creating MyBox with some data!\n", "Dropping MyBox with some data!\n" ] } ], "source": [ "struct MyBox(T);\n", "\n", "impl MyBox {\n", " fn new(x: T) -> MyBox {\n", " println!(\"Creating MyBox with some data!\");\n", " MyBox(x)\n", " }\n", "}\n", " \n", "use std::ops::Deref;\n", "\n", "impl Deref for MyBox {\n", " type Target = T;\n", "\n", " fn deref(&self) -> &Self::Target {\n", " &self.0\n", " }\n", "}\n", "\n", "impl Drop for MyBox {\n", " fn drop(&mut self) {\n", " println!(\"Dropping MyBox with some data!\");\n", " }\n", "}\n", "\n", "fn main() {\n", " let x = 5;\n", " let y = MyBox::new(x);\n", "\n", " assert!(5 == x);\n", " assert!(5 == *y);\n", "}\n", "\n", "main();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Deref coercion\n", "\n", "* Deref coercion converts a reference to a type that implements the `Deref` trait into a reference to another type. For example, deref coercion can convert `&String` to `&str` because `String` implements the `Deref` trait such that it returns `&str`" ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello, John!\n", "Hello, Jane!\n", "Creating MyBox with some data!\n", "Hello, Rust!\n", "Dropping MyBox with some data!\n" ] } ], "source": [ "fn hello(name: &str) {\n", " println!(\"Hello, {}!\", name);\n", "}\n", "\n", "fn main() {\n", " hello(\"John\");\n", " \n", " let name = String::from(\"Jane\");\n", " hello(&name);\n", "\n", " let m = MyBox::new(String::from(\"Rust\"));\n", " hello(&m);\n", "}\n", "\n", "main();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## `Rc` (Reference Counted Smart Pointer)\n", "\n", "* `Rc` enables multiple ownership of the same data; `Rc` keeps track of the number of references to a value which determines whether or not a value is still in use.\n", "* `Rc` is only for use in single-threaded scenarios. For multi-threaded code, `Arc` should be used.\n", "* `Rc` points to immutable data. If you need mutability, put a `RefCell` inside the `Rc`." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Using Value(\"foo\"). RC: 3\n", "Using Value(\"bar\"). RC: 2\n", "v_inner - Value(\"bar\") - it was only one strong reference\n", "Dropping Value(\"bar\")\n", "Value(\"foo\")\n", "---------------------------------\n", "Dropping Value(\"foo\")\n", "---------------------------------\n" ] } ], "source": [ "use std::collections::HashMap;\n", "use std::rc::Rc;\n", "\n", "#[derive(Debug)]\n", "struct Value(String);\n", "\n", "impl Value {\n", " fn value(&self) -> &String {\n", " &self.0\n", " }\n", "}\n", "\n", "impl Drop for Value {\n", " fn drop(&mut self) {\n", " println!(\"Dropping {:?}\", self);\n", " }\n", "}\n", "\n", "fn use_value(data: Rc) {\n", " println!(\"Using {:?}. RC: {}\", data, Rc::strong_count(&data));\n", "}\n", "\n", "fn main() {\n", " let mut map = HashMap::<&str, Rc>::new();\n", " {\n", " let v_foo = Rc::new(Value(\"foo\".to_string()));\n", " map.insert(\"foo\", v_foo.clone());\n", " use_value(v_foo.clone());\n", " {\n", " let v_bar = Rc::new(Value(\"bar\".to_string()));\n", " use_value(v_bar.clone());\n", " \n", " let v_inner = Rc::into_inner(v_bar);\n", "\n", " match v_inner {\n", " Some(value) => {\n", " println!(\"v_inner - {:?} - it was only one strong reference\", value);\n", " }\n", " None => {\n", " println!(\"v_inner is None - more than one reference to it\");\n", " }\n", " }\n", " } \n", " }\n", "\n", " println!(\"{:?}\", map.get(\"foo\").unwrap());\n", "\n", " println!(\"---------------------------------\");\n", "\n", " map.clear();\n", "\n", " println!(\"---------------------------------\");\n", "}\n", "\n", "main();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## `Weak` - Breaking Reference Cycles\n", "\n", "* `Weak` is a version of `Rc` that holds a non-owning reference. The object that `Weak` points to is only valid if there is at least one `Rc` pointing to it.\n", "* `Weak` is useful in breaking reference cycles. A reference cycle is a situation where two objects reference each other, preventing either from being cleaned up." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Memory Leak - Reference Cycles" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "John: John (30) is not married\n", "Jane: Jane (25) is not married\n", "Dropping Jane (25)\n", "Dropping John (30)\n" ] } ], "source": [ "use std::cell::RefCell;\n", "use std::fmt::{self, Display, Formatter, Debug};\n", "\n", "struct Human {\n", " name: String,\n", " age: u32,\n", "\n", " partner: RefCell>>,\n", "}\n", "\n", "impl Debug for Human {\n", " fn fmt(&self, f: &mut Formatter) -> fmt::Result {\n", " write!(f, \"{} ({})\", self.name, self.age)\n", " }\n", "}\n", "\n", "impl Display for Human {\n", " fn fmt(&self, f: &mut Formatter) -> fmt::Result {\n", " write!(f, \"{} ({})\", self.name, self.age);\n", "\n", " if let Some(partner) = &*self.partner.borrow() {\n", " write!(f, \" is married to {}\", partner.name)\n", " } else {\n", " write!(f, \" is not married\")\n", " }\n", " }\n", "}\n", "\n", "impl Drop for Human {\n", " fn drop(&mut self) {\n", " println!(\"Dropping {:?}\", self);\n", " }\n", "}\n", "\n", "impl Human {\n", " fn new(name: &str, age: u32) -> Rc {\n", " Rc::new(Human {\n", " name: name.to_string(),\n", " age,\n", " partner: RefCell::new(None),\n", " })\n", " }\n", "}\n", "\n", "fn marry(h1: Rc, h2: Rc) {\n", " *h1.partner.borrow_mut() = Some(h2.clone());\n", " *h2.partner.borrow_mut() = Some(h1.clone());\n", "}\n", "\n", "fn main() {\n", " let mut john = Human::new(\"John\", 30);\n", " let mut jane = Human::new(\"Jane\", 25);\n", "\n", " //marry(john.clone(), jane.clone()); // this creates a reference cycle -> memory leak\n", "\n", " println!(\"John: {}\", john);\n", " println!(\"Jane: {}\", jane);\n", "}\n", "\n", "main();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Breaking Reference Cycles with `Weak`" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "vscode": { "languageId": "rust" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "John: John (30) is married to Jane - Rc::strong_count(1)\n", "Jane: Jane (25) is married to John - Rc::strong_count(1)\n", "Dropping Jane (25)\n", "John: John (30) is widow/widower\n", "Dropping John (30)\n" ] } ], "source": [ "use std::rc::{Rc, Weak}\n", "use std::cell::RefCell;\n", "use std::fmt::{self, Display, Formatter};\n", "\n", "struct Human {\n", " name: String,\n", " age: u32,\n", "\n", " partner: RefCell>>,\n", "}\n", "\n", "impl Debug for Human {\n", " fn fmt(&self, f: &mut Formatter) -> fmt::Result {\n", " write!(f, \"{} ({})\", self.name, self.age)\n", " }\n", "}\n", "\n", "impl Display for Human {\n", " fn fmt(&self, f: &mut Formatter) -> fmt::Result {\n", " write!(f, \"{} ({})\", self.name, self.age);\n", "\n", " if let Some(partner) = &*self.partner.borrow() {\n", " if let Some(partner) = partner.upgrade() {\n", " write!(f, \" is married to {}\", partner.name)\n", " } else {\n", " write!(f, \" is widow/widower\")\n", " }\n", " } else {\n", " write!(f, \" is not married\")\n", " }\n", " }\n", "}\n", "\n", "impl Drop for Human {\n", " fn drop(&mut self) {\n", " println!(\"Dropping {:?}\", self);\n", " }\n", "}\n", "\n", "impl Human {\n", " fn new(name: &str, age: u32) -> Rc {\n", " Rc::new(Human {\n", " name: name.to_string(),\n", " age,\n", " partner: RefCell::new(None),\n", " })\n", " }\n", "}\n", "\n", "fn marry(h1: Rc, h2: Rc) {\n", " *h1.partner.borrow_mut() = Some(Rc::downgrade(&h2));\n", " *h2.partner.borrow_mut() = Some(Rc::downgrade(&h1));\n", "}\n", "\n", "fn main() {\n", " let mut john: Rc = Human::new(\"John\", 30);\n", " let mut jane: Rc = Human::new(\"Jane\", 25);\n", "\n", " marry(john.clone(), jane.clone());\n", "\n", " println!(\"John: {} - Rc::strong_count({})\", john, Rc::strong_count(&john));\n", " println!(\"Jane: {} - Rc::strong_count({})\", jane, Rc::strong_count(&jane));\n", "\n", " std::mem::drop(jane);\n", " println!(\"John: {}\", john);\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 }