From 224f1ecc5f702189d0dca714fff2752e24807364 Mon Sep 17 00:00:00 2001 From: jwalton Date: Wed, 31 May 2023 21:07:13 +0000 Subject: [PATCH] deploy: ff0e7cbf34ed75e2e37641e5077974c3ee72827d --- 404.html | 4 ++-- assets/js/{0837bd7b.86a1ce03.js => 0837bd7b.37ea0968.js} | 2 +- assets/js/{25ec9f74.a3b9db90.js => 25ec9f74.2326306c.js} | 2 +- assets/js/829cc90e.75d46d74.js | 1 + assets/js/829cc90e.95720db2.js | 1 - assets/js/{935f2afb.ff231fb8.js => 935f2afb.e94cfbb0.js} | 2 +- assets/js/96675f22.a990bd42.js | 1 + assets/js/96675f22.e0297e82.js | 1 - assets/js/{c670d531.78c57de5.js => c670d531.4757bcce.js} | 2 +- assets/js/f538945b.1f93a5a9.js | 1 + assets/js/f538945b.6f7f54ac.js | 1 - ...{runtime~main.ce1f3172.js => runtime~main.801a8238.js} | 2 +- .../10---generic-types-traits-and-lifetimes/index.html | 6 +++--- category/19---advanced-features/index.html | 4 ++-- category/20---multithreaded-web-server/index.html | 4 ++-- category/appendix/index.html | 4 ++-- ch01-getting-started/index.html | 4 ++-- ch02-guessing-game/index.html | 4 ++-- ch03-common-programming-concepts/index.html | 6 +++--- ch04-ownership/index.html | 6 +++--- ch05-structs/index.html | 4 ++-- ch06-enums-and-pattern-matching/index.html | 4 ++-- ch07-packages-crates-modules/index.html | 4 ++-- ch08-common-collections/index.html | 4 ++-- ch09-error-handling/index.html | 4 ++-- ch10/ch10-01-generic-data-types/index.html | 4 ++-- ch10/ch10-02-traits/index.html | 6 +++--- ch10/ch10-03-lifetimes/index.html | 8 ++++---- ch11-automated-tests/index.html | 4 ++-- ch12-io-project-cli/index.html | 4 ++-- ch13-functional-language-features/index.html | 4 ++-- ch14-more-about-cargo/index.html | 6 +++--- ch15-smart-pointers/index.html | 4 ++-- ch16-fearless-concurrency/index.html | 4 ++-- ch17-object-oriented-features/index.html | 4 ++-- ch18-patterns-and-matching/index.html | 4 ++-- ch19/ch19-01-unsafe/index.html | 4 ++-- ch19/ch19-02-advanced-traits/index.html | 4 ++-- ch19/ch19-03-advanced-types/index.html | 4 ++-- ch19/ch19-04-advanced-functions-and-closures/index.html | 4 ++-- ch19/ch19-05-macros/index.html | 4 ++-- ch20/ch20-01-single-threaded-web-server/index.html | 4 ++-- ch20/ch20-02-multi-threaded-web-server/index.html | 4 ++-- ch20/ch20-03-graceful-shutdown/index.html | 4 ++-- ch21-async/index.html | 4 ++-- index.html | 6 +++--- zz-appendix/appendix-01-keywords/index.html | 4 ++-- zz-appendix/appendix-02-operators/index.html | 4 ++-- zz-appendix/appendix-03-derivable-traits/index.html | 4 ++-- .../appendix-04-useful-development-tools/index.html | 4 ++-- zz-appendix/appendix-05-editions/index.html | 4 ++-- zz-appendix/appendix-06-licenses/index.html | 4 ++-- 52 files changed, 98 insertions(+), 98 deletions(-) rename assets/js/{0837bd7b.86a1ce03.js => 0837bd7b.37ea0968.js} (99%) rename assets/js/{25ec9f74.a3b9db90.js => 25ec9f74.2326306c.js} (98%) create mode 100644 assets/js/829cc90e.75d46d74.js delete mode 100644 assets/js/829cc90e.95720db2.js rename assets/js/{935f2afb.ff231fb8.js => 935f2afb.e94cfbb0.js} (56%) create mode 100644 assets/js/96675f22.a990bd42.js delete mode 100644 assets/js/96675f22.e0297e82.js rename assets/js/{c670d531.78c57de5.js => c670d531.4757bcce.js} (71%) create mode 100644 assets/js/f538945b.1f93a5a9.js delete mode 100644 assets/js/f538945b.6f7f54ac.js rename assets/js/{runtime~main.ce1f3172.js => runtime~main.801a8238.js} (79%) diff --git a/404.html b/404.html index 7903efa..a160ec8 100644 --- a/404.html +++ b/404.html @@ -4,13 +4,13 @@ Page Not Found | The rs Book - +
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

- + \ No newline at end of file diff --git a/assets/js/0837bd7b.86a1ce03.js b/assets/js/0837bd7b.37ea0968.js similarity index 99% rename from assets/js/0837bd7b.86a1ce03.js rename to assets/js/0837bd7b.37ea0968.js index 21f18dd..f67ba78 100644 --- a/assets/js/0837bd7b.86a1ce03.js +++ b/assets/js/0837bd7b.37ea0968.js @@ -1 +1 @@ -"use strict";(self.webpackChunkrust_book_abridged=self.webpackChunkrust_book_abridged||[]).push([[380],{3905:(e,t,a)=>{a.d(t,{Zo:()=>u,kt:()=>m});var r=a(7294);function n(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function i(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,r)}return a}function o(e){for(var t=1;t=0||(n[a]=e[a]);return n}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(n[a]=e[a])}return n}var l=r.createContext({}),h=function(e){var t=r.useContext(l),a=t;return e&&(a="function"==typeof e?e(t):o(o({},t),e)),a},u=function(e){var t=h(e.components);return r.createElement(l.Provider,{value:t},e.children)},d="mdxType",p={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},c=r.forwardRef((function(e,t){var a=e.components,n=e.mdxType,i=e.originalType,l=e.parentName,u=s(e,["components","mdxType","originalType","parentName"]),d=h(a),c=n,m=d["".concat(l,".").concat(c)]||d[c]||p[c]||i;return a?r.createElement(m,o(o({ref:t},u),{},{components:a})):r.createElement(m,o({ref:t},u))}));function m(e,t){var a=arguments,n=t&&t.mdxType;if("string"==typeof e||n){var i=a.length,o=new Array(i);o[0]=c;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[d]="string"==typeof e?e:n,o[1]=s;for(var h=2;h{a.r(t),a.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>p,frontMatter:()=>i,metadata:()=>s,toc:()=>h});var r=a(7462),n=(a(7294),a(3905));const i={sidebar_position:1,slug:"/",title:"The Rust Book (Abridged)",hide_title:!0},o=void 0,s={unversionedId:"ch00-intro",id:"ch00-intro",title:"The Rust Book (Abridged)",description:"The Rust Book (Abridged)",source:"@site/docs/ch00-intro.md",sourceDirName:".",slug:"/",permalink:"/rust-book-abridged/",draft:!1,editUrl:"https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/docs/ch00-intro.md",tags:[],version:"current",sidebarPosition:1,frontMatter:{sidebar_position:1,slug:"/",title:"The Rust Book (Abridged)",hide_title:!0},sidebar:"tutorialSidebar",next:{title:"1 - Getting Started",permalink:"/rust-book-abridged/ch01-getting-started"}},l={},h=[{value:"What is this?",id:"what-is-this",level:2},{value:"What's different about this book?",id:"whats-different-about-this-book",level:2},{value:"Table of Contents",id:"table-of-contents",level:2}],u={toc:h},d="wrapper";function p(e){let{components:t,...a}=e;return(0,n.kt)(d,(0,r.Z)({},u,a,{components:t,mdxType:"MDXLayout"}),(0,n.kt)("div",{align:"center"},(0,n.kt)("h1",null,"The Rust Book (Abridged)"),(0,n.kt)("p",null,"v0.2.1 - Draft"),(0,n.kt)("p",null,"By Jason Walton"),(0,n.kt)("p",null,"Based on ",(0,n.kt)("a",{href:"https://doc.rust-lang.org/stable/book/"},'"The Rust Programming Language"')," by Steve Klabnik and Carol Nichols.")),(0,n.kt)("p",null,"PDF version of this book is available ",(0,n.kt)("a",{parentName:"p",href:"https://github.com/jwalton/rust-book-abridged/releases/latest/download/rust-book-abridged.pdf"},"here"),"."),(0,n.kt)("h2",{id:"what-is-this"},"What is this?"),(0,n.kt)("p",null,"This is an abridged - or perhaps a better word would be condensed - version of ",(0,n.kt)("a",{parentName:"p",href:"https://doc.rust-lang.org/stable/book/title-page.html"},'"The Rust Programming Language"')," (AKA \"the Rust Book\"). This is not an original work - all the chapter names and examples in this book have been copied verbatim from the original, but all of the prose has been rewritten from scratch, leaving out anything that's not about learning Rust. This book is about 1/2 the length of the original, but I don't think it is missing anything that an experienced software developer wouldn't already know."),(0,n.kt)("p",null,"The Rust Book is a great resource for learning Rust, especially if you're new to programming. If you fall into this category, then I strongly suggest you put this book down and go read it instead. But... the Rust Book is quite wordy. If you're already familiar with one or more other programming languages, then you are likely already familiar with a lot of the concepts the book covers, and you might benefit from this shorter version. If you are already familiar with ideas like the stack and the heap, with test driven development, with the ",(0,n.kt)("a",{parentName:"p",href:"https://en.wikipedia.org/wiki/Don%27t_repeat_yourself"},"DRY principle"),", then this might be a better read."),(0,n.kt)("p",null,"This isn't meant to be a criticism of the Rust Book. It's excellent and well written, and there's a reason why it's highly recommended. The problem here is not with the original book, but more a mismatch when it comes to intended audience."),(0,n.kt)("h2",{id:"whats-different-about-this-book"},"What's different about this book?"),(0,n.kt)("p",null,"As mentioned above, the chapter names in this book are all the same as in the original, and in many cases the subsections in each chapter are the same. In most cases examples have been copied directly from the original. Keeping the original structure and examples hopefully makes it easy to jump back and forth between this book and the original, in case there are places where this book is unclear or covers concepts you are not familiar with."),(0,n.kt)("p",null,"Where the original would build up a code example piece by piece, in most cases this version presents the finished code so you can read through it, and then points out some interesting parts. Where possible I've tried to add in material I think an advanced reader would find interesting. In some places this explains things in a different way than the original. This also adds an extra bonus chapter about async programming!"),(0,n.kt)("p",null,"I have a great deal of experience in TypeScript, Java, C/C++, Go, and a few other languages. I spent about two weeks putting this book together, reading the original, condensing it, and researching parts that weren't clear. Hopefully someone finds this useful! But I am new to Rust so if you find something that doesn't make sense, please feel free to ",(0,n.kt)("a",{parentName:"p",href:"https://github.com/jwalton/rust-book-abridged"},"raise an issue"),"."),(0,n.kt)("p",null,"This book was written entirely by a human - none of this is generated by ChatGPT."),(0,n.kt)("p",null,"If you enjoy this book, please ",(0,n.kt)("a",{parentName:"p",href:"https://github.com/jwalton/rust-book-abridged"},"give it a star on GitHub"),"."),(0,n.kt)("h2",{id:"table-of-contents"},"Table of Contents"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch01-getting-started",title:"Chapter 1: Getting Started"},"Chapter 1: Getting Started")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch02-guessing-game",title:"Chapter 2: Guessing Game"},"Chapter 2: Guessing Game")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch03-common-programming-concepts",title:"Chapter 3: Common Programming Concepts"},"Chapter 3: Common Programming Concepts")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch04-ownership",title:"Chapter 4: Ownership, References, and Slices"},"Chapter 4: Ownership, References, and Slices")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch05-structs",title:"Chapter 5: Using Structs to Structure Related Data"},"Chapter 5: Using Structs to Structure Related Data")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch06-enums-and-pattern-matching",title:"Chapter 6: Enums and Pattern Matching"},"Chapter 6: Enums and Pattern Matching")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch07-packages-crates-modules",title:"Chapter 7: Managing Growing Projects with Packages, Crates, and Modules"},"Chapter 7: Managing Growing Projects with Packages, Crates, and Modules")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch08-common-collections",title:"Chapter 8: Common Collections"},"Chapter 8: Common Collections")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch09-error-handling",title:"Chapter 9: Error Handling"},"Chapter 9: Error Handling")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch10/ch10-01-generic-data-types",title:"Chapter 10: Generic Types, Traits, and Lifetimes"},"Chapter 10: Generic Types, Traits, and Lifetimes")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch11-automated-tests",title:"Chapter 11: Writing Automated Tests"},"Chapter 11: Writing Automated Tests")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch12-io-project-cli",title:"Chapter 12: An I/O Project: Building a Command Line Program"},"Chapter 12: An I/O Project: Building a Command Line Program")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch13-functional-language-features",title:"Chapter 13: Functional Language Features: Iterators and Closures"},"Chapter 13: Functional Language Features: Iterators and Closures")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch14-more-about-cargo",title:"Chapter 14: More About Cargo and Crates.io"},"Chapter 14: More About Cargo and Crates.io")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch15-smart-pointers",title:"Chapter 15: Smart Pointers"},"Chapter 15: Smart Pointers")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch16-fearless-concurrency",title:"Chapter 16: Fearless Concurrency"},"Chapter 16: Fearless Concurrency")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch17-object-oriented-features",title:"Chapter 17: Object Oriented Features of Rust"},"Chapter 17: Object Oriented Features of Rust")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch18-patterns-and-matching",title:"Chapter 18: Patterns and Matching"},"Chapter 18: Patterns and Matching")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch19/ch19-01-unsafe",title:"Chapter 19: Advanced Features"},"Chapter 19: Advanced Features")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch20/ch20-01-single-threaded-web-server",title:"Chapter 20: Multithreaded Web Server"},"Chapter 20: Multithreaded Web Server")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch21-async",title:"Chapter 21: Bonus Chapter: Async Programming"},"Chapter 21: Bonus Chapter: Async Programming"))),(0,n.kt)("p",null,"(This version of this book is based on ",(0,n.kt)("a",{parentName:"p",href:"https://github.com/rust-lang/book/commit/c06006157b14b3d47b5c716fc392b77f3b2e21ce"},"commit c06006"),")."))}p.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkrust_book_abridged=self.webpackChunkrust_book_abridged||[]).push([[380],{3905:(e,t,a)=>{a.d(t,{Zo:()=>u,kt:()=>m});var r=a(7294);function n(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function i(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,r)}return a}function o(e){for(var t=1;t=0||(n[a]=e[a]);return n}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(n[a]=e[a])}return n}var l=r.createContext({}),h=function(e){var t=r.useContext(l),a=t;return e&&(a="function"==typeof e?e(t):o(o({},t),e)),a},u=function(e){var t=h(e.components);return r.createElement(l.Provider,{value:t},e.children)},d="mdxType",p={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},c=r.forwardRef((function(e,t){var a=e.components,n=e.mdxType,i=e.originalType,l=e.parentName,u=s(e,["components","mdxType","originalType","parentName"]),d=h(a),c=n,m=d["".concat(l,".").concat(c)]||d[c]||p[c]||i;return a?r.createElement(m,o(o({ref:t},u),{},{components:a})):r.createElement(m,o({ref:t},u))}));function m(e,t){var a=arguments,n=t&&t.mdxType;if("string"==typeof e||n){var i=a.length,o=new Array(i);o[0]=c;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[d]="string"==typeof e?e:n,o[1]=s;for(var h=2;h{a.r(t),a.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>p,frontMatter:()=>i,metadata:()=>s,toc:()=>h});var r=a(7462),n=(a(7294),a(3905));const i={sidebar_position:1,slug:"/",title:"The Rust Book (Abridged)",hide_title:!0},o=void 0,s={unversionedId:"ch00-intro",id:"ch00-intro",title:"The Rust Book (Abridged)",description:"The Rust Book (Abridged)",source:"@site/docs/ch00-intro.md",sourceDirName:".",slug:"/",permalink:"/rust-book-abridged/",draft:!1,editUrl:"https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/docs/ch00-intro.md",tags:[],version:"current",sidebarPosition:1,frontMatter:{sidebar_position:1,slug:"/",title:"The Rust Book (Abridged)",hide_title:!0},sidebar:"tutorialSidebar",next:{title:"1 - Getting Started",permalink:"/rust-book-abridged/ch01-getting-started"}},l={},h=[{value:"What is this?",id:"what-is-this",level:2},{value:"What's different about this book?",id:"whats-different-about-this-book",level:2},{value:"Table of Contents",id:"table-of-contents",level:2}],u={toc:h},d="wrapper";function p(e){let{components:t,...a}=e;return(0,n.kt)(d,(0,r.Z)({},u,a,{components:t,mdxType:"MDXLayout"}),(0,n.kt)("div",{align:"center"},(0,n.kt)("h1",null,"The Rust Book (Abridged)"),(0,n.kt)("p",null,"v0.3.0 - Draft"),(0,n.kt)("p",null,"By Jason Walton"),(0,n.kt)("p",null,"Based on ",(0,n.kt)("a",{href:"https://doc.rust-lang.org/stable/book/"},'"The Rust Programming Language"')," by Steve Klabnik and Carol Nichols.")),(0,n.kt)("p",null,"PDF version of this book is available ",(0,n.kt)("a",{parentName:"p",href:"https://github.com/jwalton/rust-book-abridged/releases/latest/download/rust-book-abridged.pdf"},"here"),"."),(0,n.kt)("h2",{id:"what-is-this"},"What is this?"),(0,n.kt)("p",null,"This is an abridged - or perhaps a better word would be condensed - version of ",(0,n.kt)("a",{parentName:"p",href:"https://doc.rust-lang.org/stable/book/title-page.html"},'"The Rust Programming Language"')," (AKA \"the Rust Book\"). This is not an original work - all the chapter names and examples in this book have been copied verbatim from the original, but all of the prose has been rewritten from scratch, leaving out anything that's not about learning Rust. This book is about 1/2 the length of the original, but I don't think it is missing anything that an experienced software developer wouldn't already know."),(0,n.kt)("p",null,"The Rust Book is a great resource for learning Rust, especially if you're new to programming. If you fall into this category, then I strongly suggest you put this book down and go read it instead. But... the Rust Book is quite wordy. If you're already familiar with one or more other programming languages, then you are likely already familiar with a lot of the concepts the book covers, and you might benefit from this shorter version. If you are already familiar with ideas like the stack and the heap, with test driven development, with the ",(0,n.kt)("a",{parentName:"p",href:"https://en.wikipedia.org/wiki/Don%27t_repeat_yourself"},"DRY principle"),", then this might be a better read."),(0,n.kt)("p",null,"This isn't meant to be a criticism of the Rust Book. It's excellent and well written, and there's a reason why it's highly recommended. The problem here is not with the original book, but more a mismatch when it comes to intended audience."),(0,n.kt)("h2",{id:"whats-different-about-this-book"},"What's different about this book?"),(0,n.kt)("p",null,"As mentioned above, the chapter names in this book are all the same as in the original, and in many cases the subsections in each chapter are the same. In most cases examples have been copied directly from the original. Keeping the original structure and examples hopefully makes it easy to jump back and forth between this book and the original, in case there are places where this book is unclear or covers concepts you are not familiar with."),(0,n.kt)("p",null,"Where the original would build up a code example piece by piece, in most cases this version presents the finished code so you can read through it, and then points out some interesting parts. Where possible I've tried to add in material I think an advanced reader would find interesting. In some places this explains things in a different way than the original. This also adds an extra bonus chapter about async programming!"),(0,n.kt)("p",null,"I have a great deal of experience in TypeScript, Java, C/C++, Go, and a few other languages. I spent about two weeks putting this book together, reading the original, condensing it, and researching parts that weren't clear. Hopefully someone finds this useful! But I am new to Rust so if you find something that doesn't make sense, please feel free to ",(0,n.kt)("a",{parentName:"p",href:"https://github.com/jwalton/rust-book-abridged"},"raise an issue"),"."),(0,n.kt)("p",null,"This book was written entirely by a human - none of this is generated by ChatGPT."),(0,n.kt)("p",null,"If you enjoy this book, please ",(0,n.kt)("a",{parentName:"p",href:"https://github.com/jwalton/rust-book-abridged"},"give it a star on GitHub"),"."),(0,n.kt)("h2",{id:"table-of-contents"},"Table of Contents"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch01-getting-started",title:"Chapter 1: Getting Started"},"Chapter 1: Getting Started")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch02-guessing-game",title:"Chapter 2: Guessing Game"},"Chapter 2: Guessing Game")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch03-common-programming-concepts",title:"Chapter 3: Common Programming Concepts"},"Chapter 3: Common Programming Concepts")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch04-ownership",title:"Chapter 4: Ownership, References, and Slices"},"Chapter 4: Ownership, References, and Slices")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch05-structs",title:"Chapter 5: Using Structs to Structure Related Data"},"Chapter 5: Using Structs to Structure Related Data")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch06-enums-and-pattern-matching",title:"Chapter 6: Enums and Pattern Matching"},"Chapter 6: Enums and Pattern Matching")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch07-packages-crates-modules",title:"Chapter 7: Managing Growing Projects with Packages, Crates, and Modules"},"Chapter 7: Managing Growing Projects with Packages, Crates, and Modules")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch08-common-collections",title:"Chapter 8: Common Collections"},"Chapter 8: Common Collections")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch09-error-handling",title:"Chapter 9: Error Handling"},"Chapter 9: Error Handling")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch10/ch10-01-generic-data-types",title:"Chapter 10: Generic Types, Traits, and Lifetimes"},"Chapter 10: Generic Types, Traits, and Lifetimes")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch11-automated-tests",title:"Chapter 11: Writing Automated Tests"},"Chapter 11: Writing Automated Tests")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch12-io-project-cli",title:"Chapter 12: An I/O Project: Building a Command Line Program"},"Chapter 12: An I/O Project: Building a Command Line Program")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch13-functional-language-features",title:"Chapter 13: Functional Language Features: Iterators and Closures"},"Chapter 13: Functional Language Features: Iterators and Closures")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch14-more-about-cargo",title:"Chapter 14: More About Cargo and Crates.io"},"Chapter 14: More About Cargo and Crates.io")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch15-smart-pointers",title:"Chapter 15: Smart Pointers"},"Chapter 15: Smart Pointers")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch16-fearless-concurrency",title:"Chapter 16: Fearless Concurrency"},"Chapter 16: Fearless Concurrency")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch17-object-oriented-features",title:"Chapter 17: Object Oriented Features of Rust"},"Chapter 17: Object Oriented Features of Rust")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch18-patterns-and-matching",title:"Chapter 18: Patterns and Matching"},"Chapter 18: Patterns and Matching")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch19/ch19-01-unsafe",title:"Chapter 19: Advanced Features"},"Chapter 19: Advanced Features")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch20/ch20-01-single-threaded-web-server",title:"Chapter 20: Multithreaded Web Server"},"Chapter 20: Multithreaded Web Server")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch21-async",title:"Chapter 21: Bonus Chapter: Async Programming"},"Chapter 21: Bonus Chapter: Async Programming"))),(0,n.kt)("p",null,"(This version of this book is based on ",(0,n.kt)("a",{parentName:"p",href:"https://github.com/rust-lang/book/commit/c06006157b14b3d47b5c716fc392b77f3b2e21ce"},"commit c06006"),")."))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/25ec9f74.a3b9db90.js b/assets/js/25ec9f74.2326306c.js similarity index 98% rename from assets/js/25ec9f74.a3b9db90.js rename to assets/js/25ec9f74.2326306c.js index 8232615..439bdf5 100644 --- a/assets/js/25ec9f74.a3b9db90.js +++ b/assets/js/25ec9f74.2326306c.js @@ -1 +1 @@ -"use strict";(self.webpackChunkrust_book_abridged=self.webpackChunkrust_book_abridged||[]).push([[462],{3905:(e,t,a)=>{a.d(t,{Zo:()=>d,kt:()=>h});var n=a(7294);function r(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function o(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}function i(e){for(var t=1;t=0||(r[a]=e[a]);return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(r[a]=e[a])}return r}var s=n.createContext({}),p=function(e){var t=n.useContext(s),a=t;return e&&(a="function"==typeof e?e(t):i(i({},t),e)),a},d=function(e){var t=p(e.components);return n.createElement(s.Provider,{value:t},e.children)},c="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},m=n.forwardRef((function(e,t){var a=e.components,r=e.mdxType,o=e.originalType,s=e.parentName,d=l(e,["components","mdxType","originalType","parentName"]),c=p(a),m=r,h=c["".concat(s,".").concat(m)]||c[m]||u[m]||o;return a?n.createElement(h,i(i({ref:t},d),{},{components:a})):n.createElement(h,i({ref:t},d))}));function h(e,t){var a=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var o=a.length,i=new Array(o);i[0]=m;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[c]="string"==typeof e?e:r,i[1]=l;for(var p=2;p{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>i,default:()=>u,frontMatter:()=>o,metadata:()=>l,toc:()=>p});var n=a(7462),r=(a(7294),a(3905));const o={},i="14 - More about Cargo and Crates",l={unversionedId:"ch14-more-about-cargo",id:"ch14-more-about-cargo",title:"14 - More about Cargo and Crates",description:"14.1 - Customizing Builds with Release Profiles",source:"@site/docs/ch14-more-about-cargo.md",sourceDirName:".",slug:"/ch14-more-about-cargo",permalink:"/rust-book-abridged/ch14-more-about-cargo",draft:!1,editUrl:"https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/docs/ch14-more-about-cargo.md",tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"13 - Functional Language Features: Iterators and Closures",permalink:"/rust-book-abridged/ch13-functional-language-features"},next:{title:"15 - Smart Pointers",permalink:"/rust-book-abridged/ch15-smart-pointers"}},s={},p=[{value:"14.1 - Customizing Builds with Release Profiles",id:"141---customizing-builds-with-release-profiles",level:2},{value:"14.2 - Publishing a Crate to Crates.io",id:"142---publishing-a-crate-to-cratesio",level:2},{value:"Setting Up a Crates.io Account",id:"setting-up-a-cratesio-account",level:3},{value:"Making Useful Documentation Comments",id:"making-useful-documentation-comments",level:3},{value:"Commonly Used Sections",id:"commonly-used-sections",level:3},{value:"Commenting Contained Items",id:"commenting-contained-items",level:3},{value:"Exporting a Convenient Public API with pub use",id:"exporting-a-convenient-public-api-with-pub-use",level:3},{value:"Adding Metadata to a New Crate",id:"adding-metadata-to-a-new-crate",level:3},{value:"Publishing Your Crate",id:"publishing-your-crate",level:3},{value:"Deprecating Versions from Crates.io with cargo yank",id:"deprecating-versions-from-cratesio-with-cargo-yank",level:3},{value:"14.3 - Cargo Workspaces",id:"143---cargo-workspaces",level:2},{value:"Creating a Workspace",id:"creating-a-workspace",level:3},{value:"Referencing Other Packages in the Workspace",id:"referencing-other-packages-in-the-workspace",level:3},{value:"Depending on an External Package in a Workspace",id:"depending-on-an-external-package-in-a-workspace",level:3},{value:"14.4 - Installing Binaries with cargo install",id:"144---installing-binaries-with-cargo-install",level:2},{value:"14.5 - Extending Cargo with Custom Commands",id:"145---extending-cargo-with-custom-commands",level:2}],d={toc:p},c="wrapper";function u(e){let{components:t,...a}=e;return(0,r.kt)(c,(0,n.Z)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"14---more-about-cargo-and-crates"},"14 - More about Cargo and Crates"),(0,r.kt)("h2",{id:"141---customizing-builds-with-release-profiles"},"14.1 - Customizing Builds with Release Profiles"),(0,r.kt)("p",null,"Cargo has four built-in release profiles called ",(0,r.kt)("inlineCode",{parentName:"p"},"dev"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"release"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"test"),", and ",(0,r.kt)("inlineCode",{parentName:"p"},"bench"),". ",(0,r.kt)("inlineCode",{parentName:"p"},"cargo build")," will build in the ",(0,r.kt)("inlineCode",{parentName:"p"},"dev")," profile, and ",(0,r.kt)("inlineCode",{parentName:"p"},"cargo build --release")," in the ",(0,r.kt)("inlineCode",{parentName:"p"},"release")," profile, and the other two are used when running tests. Cargo has various settings for for these profiles, which you can override in ",(0,r.kt)("em",{parentName:"p"},"Cargo.toml"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-toml"},"[profile.dev]\nopt-level = 0\noverflow-checks = true\n\n[profile.release]\nopt-level = 3\noverflow-checks = false\n")),(0,r.kt)("p",null,"In this example ",(0,r.kt)("inlineCode",{parentName:"p"},"opt-level")," controls how much Rust tries to optimize your code and can be set from 0 to 3 (these are also the defaults - 0 for dev because you want the build to be fast, 3 for release because you want your program to be fast). ",(0,r.kt)("inlineCode",{parentName:"p"},"overflow-checks")," is used to determine whether or not Rust will add in runtime checks to see if integer arithmetic overflows any values."),(0,r.kt)("p",null,"For a full list of options see ",(0,r.kt)("a",{parentName:"p",href:"https://doc.rust-lang.org/cargo/reference/profiles.html"},"the cargo documentation"),"."),(0,r.kt)("h2",{id:"142---publishing-a-crate-to-cratesio"},"14.2 - Publishing a Crate to Crates.io"),(0,r.kt)("p",null,"If you write a library crate, you can publish it to ",(0,r.kt)("a",{parentName:"p",href:"https://crates.io"},"crates.io")," so other people can use it."),(0,r.kt)("h3",{id:"setting-up-a-cratesio-account"},"Setting Up a Crates.io Account"),(0,r.kt)("p",null,"To publish something on ",(0,r.kt)("a",{parentName:"p",href:"https://crates.io"},"crates.io"),", first you'll need to create an account. Then visit ",(0,r.kt)("a",{parentName:"p",href:"https://crates.io/me"},"https://crates.io/me"),", grab your API key, and run:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-sh"},"$ cargo login your-key-here\n")),(0,r.kt)("p",null,"Your API key will be stored in ",(0,r.kt)("em",{parentName:"p"},"~/.cargo/credentials"),". This is secret so if anyone gets ahold of your API key, be sure to revoke it and generate a new one, otherwise people can publish crates in your name, and before you know it all your crates will be full of crypto miners or worse."),(0,r.kt)("h3",{id:"making-useful-documentation-comments"},"Making Useful Documentation Comments"),(0,r.kt)("p",null,"One thing we haven't done much of in our examples so far is to document our public structs and functions, but if you look at any package on ",(0,r.kt)("a",{parentName:"p",href:"https://crates.io"},"crates.io")," you'll see everything has automatically generated helpful documentation. This documentation comes from ",(0,r.kt)("em",{parentName:"p"},"documentation comments")," which start with three slashes instead of two, and support markdown. Documentation comments should be placed immediately before the thing they are documenting:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"/// Adds one to the number given.\n///\n/// # Examples\n///\n/// ```\n/// let arg = 5;\n/// let answer = my_crate::add_one(arg);\n///\n/// assert_eq!(6, answer);\n/// ```\npub fn add_one(x: i32) -> i32 {\n x + 1\n}\n")),(0,r.kt)("p",null,"If you run ",(0,r.kt)("inlineCode",{parentName:"p"},"cargo doc --open")," in your project, you can see what the generated documentation for your project will look like. This will also include documentation for any crates that you depend on."),(0,r.kt)("h3",{id:"commonly-used-sections"},"Commonly Used Sections"),(0,r.kt)("p",null,"We used the ",(0,r.kt)("inlineCode",{parentName:"p"},"# Examples")," markdown heading above to make a section for our examples. Some other commonly used headings:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"# Panics")," describes the scenarios in which the given function might panic."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"# Errors")," describes the kinds of conditions under which this function might return an error, and what kinds of errors are returned."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"# Safety")," should be present for any function that is ",(0,r.kt)("inlineCode",{parentName:"li"},"unsafe")," (see ",(0,r.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch19/ch19-01-unsafe",title:"Chapter 19: Advanced Features"},"chapter 19"),").")),(0,r.kt)("p",null,"You don't need all of these on every function. Any examples you provide will automatically be run as tests when you ",(0,r.kt)("inlineCode",{parentName:"p"},"cargo test"),", so you'll know your examples actually work."),(0,r.kt)("h3",{id:"commenting-contained-items"},"Commenting Contained Items"),(0,r.kt)("p",null,"There's a second kind of documentation comment ",(0,r.kt)("inlineCode",{parentName:"p"},"//!"),' that, instead of documenting what comes right after it, documents the "parent" of the comment. You can use these at the root of ',(0,r.kt)("em",{parentName:"p"},"src/lib.rs")," to document the crate as a whole, or inside a module to document the module:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"//! # My Crate\n//!\n//! `my_crate` is a collection of utilities to make performing certain\n//! calculations more convenient.\n\n/// Adds one to the number given.\n// --snip--\n")),(0,r.kt)("h3",{id:"exporting-a-convenient-public-api-with-pub-use"},"Exporting a Convenient Public API with ",(0,r.kt)("inlineCode",{parentName:"h3"},"pub use")),(0,r.kt)("p",null,"We talked about this back in ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch07-packages-crates-modules#re-exporting-names-with-pub-use"},"chapter 7"),", but sometimes we might organize the internals of our library in such a way that makes sense to use when we're developing it, but is at odds with how someone would actually want to use our crate. If you have some struct or module that is frequently used by users of your crate, but is buried deep in the module hierarchy, this is going to be a pain point for your users."),(0,r.kt)("p",null,"Here's an example:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"//! # Art\n//!\n//! A library for modeling artistic concepts.\n\npub mod kinds {\n /// The primary colors according to the RYB color model.\n pub enum PrimaryColor {\n Red,\n Yellow,\n Blue,\n }\n\n /// The secondary colors according to the RYB color model.\n pub enum SecondaryColor {\n Orange,\n Green,\n Purple,\n }\n}\n\npub mod utils {\n use crate::kinds::*;\n\n /// Combines two primary colors in equal amounts to create\n /// a secondary color.\n pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {\n // --snip--\n }\n}\n")),(0,r.kt)("p",null,"Users of this crate would have to write code like:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"use art::kinds::PrimaryColor;\nuse art::utils::mix;\n\nfn main() {\n let red = PrimaryColor::Red;\n let yellow = PrimaryColor::Yellow;\n mix(red, yellow);\n}\n")),(0,r.kt)("p",null,"but users of this crate probably don't care about ",(0,r.kt)("inlineCode",{parentName:"p"},"kinds")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"utils"),' - to them this should be top level functionality for this crate. We can "re-export" these at the top level with ',(0,r.kt)("inlineCode",{parentName:"p"},"pub use"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"//! # Art\n//!\n//! A library for modeling artistic concepts.\n\npub use self::kinds::PrimaryColor;\npub use self::kinds::SecondaryColor;\npub use self::utils::mix;\n\npub mod kinds {\n // --snip--\n}\n\npub mod utils {\n // --snip--\n}\n")),(0,r.kt)("h3",{id:"adding-metadata-to-a-new-crate"},"Adding Metadata to a New Crate"),(0,r.kt)("p",null,"In order to publish on ",(0,r.kt)("a",{parentName:"p",href:"https://crates.io"},"crates.io"),", our crate needs a name, a description, and a valid ",(0,r.kt)("a",{parentName:"p",href:"https://spdx.org/licenses/"},"license identifier"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-toml",metastring:'title="Cargo.toml"',title:'"Cargo.toml"'},'[package]\nname = "my_awesome_colors"\nversion = "0.1.0"\nedition = "2021"\ndescription = "A fancy awesome crate for generating colored text in the terminal."\nlicense = "MIT"\n')),(0,r.kt)("h3",{id:"publishing-your-crate"},"Publishing Your Crate"),(0,r.kt)("p",null,"To publish your package run:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-sh"},"$ cargo publish\n")),(0,r.kt)("p",null,"If someone has already used your name on ",(0,r.kt)("a",{parentName:"p",href:"https://crates.io"},"crates.io")," then this will fail - names are handed out first-come-first-served. If you make some changes to your crate, bump the version number (using ",(0,r.kt)("a",{parentName:"p",href:"https://semver.org/"},"semantic versioning rules"),") and then run ",(0,r.kt)("inlineCode",{parentName:"p"},"cargo publish")," again."),(0,r.kt)("h3",{id:"deprecating-versions-from-cratesio-with-cargo-yank"},"Deprecating Versions from Crates.io with ",(0,r.kt)("inlineCode",{parentName:"h3"},"cargo yank")),(0,r.kt)("p",null,"Sometimes an older version of your crate will have some terrible security bug, or has a fatal bug that completely breaks it. For this or other reasons, you'll want to stop people from installing this version and warn existing users they need to upgrade. You can't remove an old version, but you can mark it as deprecated:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-sh"},"$ cargo yank --vers 1.0.1\n")),(0,r.kt)("p",null,"If you accidentally yank the wrong version, you can undo this:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-sh"},"$ cargo yank --vers 1.0.1 --undo\n")),(0,r.kt)("h2",{id:"143---cargo-workspaces"},"14.3 - Cargo Workspaces"),(0,r.kt)("p",null,"Sometimes a library crate gets so large that you want to split it up into multiple smaller crates. Cargo workspaces lets you do this while keeping all the crates together in the same git repo. It's a bit like the Rust version of a monorepo. You can build all packages in a workspace by running ",(0,r.kt)("inlineCode",{parentName:"p"},"cargo build")," from the root folder of the workspace, and run tests in all workspaces with ",(0,r.kt)("inlineCode",{parentName:"p"},"cargo test"),"."),(0,r.kt)("h3",{id:"creating-a-workspace"},"Creating a Workspace"),(0,r.kt)("p",null,"A workspace consists of multiple packages with their own individual ",(0,r.kt)("em",{parentName:"p"},"Cargo.yaml")," files, with a single ",(0,r.kt)("em",{parentName:"p"},"Cargo.lock")," file at the root of the workspace. We'll create a simple example here with a single binary crate and two library crates. If you want to see the code for this, check ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/jwalton/rust-book-abridged/blob/master/examples/ch14-workspace"},"this book's GitHub repo"),". First we'll create a new directory for our workspace and initialize it as a git repo:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-sh"},'$ mkdir add\n$ cd add\n$ git init .\n$ echo "/target" > .gitignore\n')),(0,r.kt)("p",null,"The ",(0,r.kt)("em",{parentName:"p"},"add"),' directory is the root of our workspace, so all other files we create will be relative to this folder. We\'re going to add three packages to this workspace: "adder", our binary package, and "add_one" and "add_two", our libraries. Let\'s start by creating these packages as subdirectories:'),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-sh"},"$ cargo new adder\n$ cargo new add_one --lib\n$ cargo new add_two --lib\n")),(0,r.kt)("p",null,"You may have noticed that we ran ",(0,r.kt)("inlineCode",{parentName:"p"},"git init")," in the add directory - we did this because generally we want to commit the entire workspace as a single repo, and if we hadn't run ",(0,r.kt)("inlineCode",{parentName:"p"},"git init"),", then ",(0,r.kt)("inlineCode",{parentName:"p"},"cargo new ..."),' would have "helpfully" initialized all three new packages as git repos for us.'),(0,r.kt)("p",null,"Now in the ",(0,r.kt)("em",{parentName:"p"},"add")," folder - the root folder of our workspace - we are going to create a ",(0,r.kt)("em",{parentName:"p"},"Cargo.toml")," for the entire workspace. This ",(0,r.kt)("em",{parentName:"p"},"Cargo.toml")," won't have any metadata or dependencies, it will simply list all the packages that make up the workspace:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-toml"},'[workspace]\n\nmembers = [\n "adder",\n "add_one",\n "add_two",\n]\n')),(0,r.kt)("admonition",{type:"tip"},(0,r.kt)("p",{parentName:"admonition"},"If you do these in the opposite order - create the top-level Cargo.toml first and then create the child packages - it will still work, but as you create each package you'll get warnings from cargo about not being able to find the packages you haven't created yet.")),(0,r.kt)("p",null,"We can build this workspace, to make sure we did everything right:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-sh"},"$ cargo build\n Compiling add_two v0.1.0 (add/add_two)\n Compiling adder v0.1.0 (add/adder)\n Compiling add_one v0.1.0 (add/add_one)\n Finished dev [unoptimized + debuginfo] target(s) in 0.11s\n")),(0,r.kt)("p",null,"At this point you should have a directory structure inside ",(0,r.kt)("em",{parentName:"p"},"add")," that looks like:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-txt"},"\u251c\u2500\u2500 .git\n\u251c\u2500\u2500 .gitignore\n\u251c\u2500\u2500 Cargo.lock\n\u251c\u2500\u2500 Cargo.toml\n\u251c\u2500\u2500 add_one\n\u2502 \u251c\u2500\u2500 Cargo.toml\n\u2502 \u2514\u2500\u2500 src\n\u2502 \u2514\u2500\u2500 lib.rs\n\u251c\u2500\u2500 add_two\n\u2502 \u251c\u2500\u2500 Cargo.toml\n\u2502 \u2514\u2500\u2500 src\n\u2502 \u2514\u2500\u2500 lib.rs\n\u251c\u2500\u2500 adder\n\u2502 \u251c\u2500\u2500 Cargo.toml\n\u2502 \u2514\u2500\u2500 src\n\u2502 \u2514\u2500\u2500 main.rs\n\u2514\u2500\u2500 target\n")),(0,r.kt)("p",null,"Note that the top level folder has a ",(0,r.kt)("em",{parentName:"p"},"Cargo.lock")," file, but none of the child projects do."),(0,r.kt)("h3",{id:"referencing-other-packages-in-the-workspace"},"Referencing Other Packages in the Workspace"),(0,r.kt)("p",null,"We'll put this in ",(0,r.kt)("em",{parentName:"p"},"add_one/src/lib.rs"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"pub fn add_one(x: i32) -> i32 {\n x + 1\n}\n")),(0,r.kt)("p",null,"We want to use this library in our binary crate in the ",(0,r.kt)("em",{parentName:"p"},"adder")," folder. To do this, first we have to add the ",(0,r.kt)("inlineCode",{parentName:"p"},"add_one")," package as a dependency of ",(0,r.kt)("inlineCode",{parentName:"p"},"adder"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-toml",metastring:'title="adder/Cargo.toml"',title:'"adder/Cargo.toml"'},'[dependencies]\nadd_one = { path = "../add_one" }\n')),(0,r.kt)("p",null,"And then we can ",(0,r.kt)("inlineCode",{parentName:"p"},"use add_one")," in the adder package, just as we would any other dependency:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust",metastring:'title="adder/src/main.rs"',title:'"adder/src/main.rs"'},'use add_one;\n\nfn main() {\n let num = 10;\n println!("Hello, world! {num} plus one is {}!", add_one::add_one(num));\n}\n')),(0,r.kt)("p",null,"From the ",(0,r.kt)("em",{parentName:"p"},"add")," directory we can now run:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-sh"},"$ cargo run\n Finished dev [unoptimized + debuginfo] target(s) in 0.00s\n Running `target/debug/adder`\nHello, world! 10 plus one is 11!\n")),(0,r.kt)("p",null,"If we had multiple packages with binary crates in the workspace, we'd have to specify which package to run with ",(0,r.kt)("inlineCode",{parentName:"p"},"cargo run -p adder"),", or we could ",(0,r.kt)("inlineCode",{parentName:"p"},"cd adder")," and then ",(0,r.kt)("inlineCode",{parentName:"p"},"cargo run")," from the adder folder."),(0,r.kt)("h3",{id:"depending-on-an-external-package-in-a-workspace"},"Depending on an External Package in a Workspace"),(0,r.kt)("p",null,"We can depend on an external create in a workspace by adding it to the ",(0,r.kt)("inlineCode",{parentName:"p"},"[dependencies]")," section of the appropriate package's ",(0,r.kt)("em",{parentName:"p"},"Cargo.toml"),". For example, we can add the ",(0,r.kt)("inlineCode",{parentName:"p"},"rand")," crate to ",(0,r.kt)("inlineCode",{parentName:"p"},"add_one")," in ",(0,r.kt)("em",{parentName:"p"},"add_one/Cargo.toml"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-toml",metastring:'title="add_one/Cargo.toml"',title:'"add_one/Cargo.toml"'},'[dependencies]\nrand = "0.8.5"\n')),(0,r.kt)("p",null,"If we add ",(0,r.kt)("inlineCode",{parentName:"p"},"use rand;")," inside ",(0,r.kt)("em",{parentName:"p"},"add_one/src/lib.rs"),", then ",(0,r.kt)("inlineCode",{parentName:"p"},"cargo build"),", we'll see the ",(0,r.kt)("inlineCode",{parentName:"p"},"rand")," package being downloaded. We'll also get a warning because we're ",(0,r.kt)("inlineCode",{parentName:"p"},"use"),"ing rand, but we never reference it in the library. Oops!"),(0,r.kt)("p",null,"If we want to use ",(0,r.kt)("inlineCode",{parentName:"p"},"rand")," in other packages in our workspace, we have to add it again to the appropriate ",(0,r.kt)("em",{parentName:"p"},"Cargo.yaml"),". Since there's only one ",(0,r.kt)("em",{parentName:"p"},"Cargo.lock")," file for the whole workspace, if ",(0,r.kt)("inlineCode",{parentName:"p"},"adder")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"add_one")," both depend on ",(0,r.kt)("inlineCode",{parentName:"p"},"rand"),", we know that they will both depend on the same version of ",(0,r.kt)("inlineCode",{parentName:"p"},"rand")," thanks to the common lockfile (at least, assuming the have compatible semver versions in the different ",(0,r.kt)("em",{parentName:"p"},"Cargo.toml")," files)."),(0,r.kt)("h2",{id:"144---installing-binaries-with-cargo-install"},"14.4 - Installing Binaries with cargo install"),(0,r.kt)("p",null,"You can publish more than just library crates to crates.io - you can also publish binary crates! Users can install your crates with ",(0,r.kt)("inlineCode",{parentName:"p"},"cargo install"),". (This is very similar to ",(0,r.kt)("inlineCode",{parentName:"p"},"npm install -g")," if you're a node.js developer.) For example, ",(0,r.kt)("inlineCode",{parentName:"p"},"ripgrep")," is a very fast alternative to the ",(0,r.kt)("inlineCode",{parentName:"p"},"grep")," command:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-sh"},"$ cargo install ripgrep\n")),(0,r.kt)("p",null,"Binaries you install this way get put in ",(0,r.kt)("inlineCode",{parentName:"p"},"~/.cargo/bin")," (assuming you're running a default installation of cargo from rustup). You'll probably want to put this folder in your shell's ",(0,r.kt)("inlineCode",{parentName:"p"},"$PATH"),". The name of the installed binary is not necessarily the same as the name of the crate. If you try installing ",(0,r.kt)("inlineCode",{parentName:"p"},"ripgrep")," above, for example, it will install ",(0,r.kt)("inlineCode",{parentName:"p"},"~/.cargo/rg"),"."),(0,r.kt)("h2",{id:"145---extending-cargo-with-custom-commands"},"14.5 - Extending Cargo with Custom Commands"),(0,r.kt)("p",null,"Much like git, you can create your own custom cargo commands. If there's an executable on your path called ",(0,r.kt)("inlineCode",{parentName:"p"},"cargo-something"),", then you can run ",(0,r.kt)("inlineCode",{parentName:"p"},"cargo something")," to run that executable. These custom commands will also show up in ",(0,r.kt)("inlineCode",{parentName:"p"},"cargo --list"),"."),(0,r.kt)("p",null,"Continue to ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch15-smart-pointers",title:"Chapter 15: Smart Pointers"},"chapter 15"),"."))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkrust_book_abridged=self.webpackChunkrust_book_abridged||[]).push([[462],{3905:(e,t,a)=>{a.d(t,{Zo:()=>d,kt:()=>h});var n=a(7294);function r(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function o(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}function i(e){for(var t=1;t=0||(r[a]=e[a]);return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(r[a]=e[a])}return r}var s=n.createContext({}),p=function(e){var t=n.useContext(s),a=t;return e&&(a="function"==typeof e?e(t):i(i({},t),e)),a},d=function(e){var t=p(e.components);return n.createElement(s.Provider,{value:t},e.children)},c="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},m=n.forwardRef((function(e,t){var a=e.components,r=e.mdxType,o=e.originalType,s=e.parentName,d=l(e,["components","mdxType","originalType","parentName"]),c=p(a),m=r,h=c["".concat(s,".").concat(m)]||c[m]||u[m]||o;return a?n.createElement(h,i(i({ref:t},d),{},{components:a})):n.createElement(h,i({ref:t},d))}));function h(e,t){var a=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var o=a.length,i=new Array(o);i[0]=m;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[c]="string"==typeof e?e:r,i[1]=l;for(var p=2;p{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>i,default:()=>u,frontMatter:()=>o,metadata:()=>l,toc:()=>p});var n=a(7462),r=(a(7294),a(3905));const o={},i="14 - More about Cargo and Crates",l={unversionedId:"ch14-more-about-cargo",id:"ch14-more-about-cargo",title:"14 - More about Cargo and Crates",description:"14.1 - Customizing Builds with Release Profiles",source:"@site/docs/ch14-more-about-cargo.md",sourceDirName:".",slug:"/ch14-more-about-cargo",permalink:"/rust-book-abridged/ch14-more-about-cargo",draft:!1,editUrl:"https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/docs/ch14-more-about-cargo.md",tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"13 - Functional Language Features: Iterators and Closures",permalink:"/rust-book-abridged/ch13-functional-language-features"},next:{title:"15 - Smart Pointers",permalink:"/rust-book-abridged/ch15-smart-pointers"}},s={},p=[{value:"14.1 - Customizing Builds with Release Profiles",id:"141---customizing-builds-with-release-profiles",level:2},{value:"14.2 - Publishing a Crate to Crates.io",id:"142---publishing-a-crate-to-cratesio",level:2},{value:"Setting Up a Crates.io Account",id:"setting-up-a-cratesio-account",level:3},{value:"Making Useful Documentation Comments",id:"making-useful-documentation-comments",level:3},{value:"Commonly Used Sections",id:"commonly-used-sections",level:3},{value:"Commenting Contained Items",id:"commenting-contained-items",level:3},{value:"Exporting a Convenient Public API with pub use",id:"exporting-a-convenient-public-api-with-pub-use",level:3},{value:"Adding Metadata to a New Crate",id:"adding-metadata-to-a-new-crate",level:3},{value:"Publishing Your Crate",id:"publishing-your-crate",level:3},{value:"Deprecating Versions from Crates.io with cargo yank",id:"deprecating-versions-from-cratesio-with-cargo-yank",level:3},{value:"14.3 - Cargo Workspaces",id:"143---cargo-workspaces",level:2},{value:"Creating a Workspace",id:"creating-a-workspace",level:3},{value:"Referencing Other Packages in the Workspace",id:"referencing-other-packages-in-the-workspace",level:3},{value:"Depending on an External Package in a Workspace",id:"depending-on-an-external-package-in-a-workspace",level:3},{value:"14.4 - Installing Binaries with cargo install",id:"144---installing-binaries-with-cargo-install",level:2},{value:"14.5 - Extending Cargo with Custom Commands",id:"145---extending-cargo-with-custom-commands",level:2}],d={toc:p},c="wrapper";function u(e){let{components:t,...a}=e;return(0,r.kt)(c,(0,n.Z)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"14---more-about-cargo-and-crates"},"14 - More about Cargo and Crates"),(0,r.kt)("h2",{id:"141---customizing-builds-with-release-profiles"},"14.1 - Customizing Builds with Release Profiles"),(0,r.kt)("p",null,"Cargo has four built-in release profiles called ",(0,r.kt)("inlineCode",{parentName:"p"},"dev"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"release"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"test"),", and ",(0,r.kt)("inlineCode",{parentName:"p"},"bench"),". ",(0,r.kt)("inlineCode",{parentName:"p"},"cargo build")," will build in the ",(0,r.kt)("inlineCode",{parentName:"p"},"dev")," profile, and ",(0,r.kt)("inlineCode",{parentName:"p"},"cargo build --release")," in the ",(0,r.kt)("inlineCode",{parentName:"p"},"release")," profile, and the other two are used when running tests. Cargo has various settings for for these profiles, which you can override in ",(0,r.kt)("em",{parentName:"p"},"Cargo.toml"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-toml"},"[profile.dev]\nopt-level = 0\noverflow-checks = true\n\n[profile.release]\nopt-level = 3\noverflow-checks = false\n")),(0,r.kt)("p",null,"In this example ",(0,r.kt)("inlineCode",{parentName:"p"},"opt-level")," controls how much Rust tries to optimize your code and can be set from 0 to 3 (these are also the defaults - 0 for dev because you want the build to be fast, 3 for release because you want your program to be fast). ",(0,r.kt)("inlineCode",{parentName:"p"},"overflow-checks")," is used to determine whether or not Rust will add in runtime checks to see if integer arithmetic overflows any values."),(0,r.kt)("p",null,"For a full list of options see ",(0,r.kt)("a",{parentName:"p",href:"https://doc.rust-lang.org/cargo/reference/profiles.html"},"the cargo documentation"),"."),(0,r.kt)("h2",{id:"142---publishing-a-crate-to-cratesio"},"14.2 - Publishing a Crate to Crates.io"),(0,r.kt)("p",null,"If you write a library crate, you can publish it to ",(0,r.kt)("a",{parentName:"p",href:"https://crates.io"},"crates.io")," so other people can use it."),(0,r.kt)("h3",{id:"setting-up-a-cratesio-account"},"Setting Up a Crates.io Account"),(0,r.kt)("p",null,"To publish something on ",(0,r.kt)("a",{parentName:"p",href:"https://crates.io"},"crates.io"),", first you'll need to create an account. Then visit ",(0,r.kt)("a",{parentName:"p",href:"https://crates.io/me"},"https://crates.io/me"),", grab your API key, and run:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-sh"},"$ cargo login your-key-here\n")),(0,r.kt)("p",null,"Your API key will be stored in ",(0,r.kt)("em",{parentName:"p"},"~/.cargo/credentials"),". This is secret so if anyone gets ahold of your API key, be sure to revoke it and generate a new one, otherwise people can publish crates in your name, and before you know it all your crates will be full of crypto miners or worse."),(0,r.kt)("h3",{id:"making-useful-documentation-comments"},"Making Useful Documentation Comments"),(0,r.kt)("p",null,"One thing we haven't done much of in our examples so far is to document our public structs and functions, but if you look at any package on ",(0,r.kt)("a",{parentName:"p",href:"https://crates.io"},"crates.io")," you'll see everything has automatically generated helpful documentation. This documentation comes from ",(0,r.kt)("em",{parentName:"p"},"documentation comments")," which start with three slashes instead of two, and support markdown. Documentation comments should be placed immediately before the thing they are documenting:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"/// Adds one to the number given.\n///\n/// # Examples\n///\n/// ```\n/// let arg = 5;\n/// let answer = my_crate::add_one(arg);\n///\n/// assert_eq!(6, answer);\n/// ```\npub fn add_one(x: i32) -> i32 {\n x + 1\n}\n")),(0,r.kt)("p",null,"If you run ",(0,r.kt)("inlineCode",{parentName:"p"},"cargo doc --open")," in your project, you can see what the generated documentation for your project will look like. This will also include documentation for any crates that you depend on."),(0,r.kt)("h3",{id:"commonly-used-sections"},"Commonly Used Sections"),(0,r.kt)("p",null,"We used the ",(0,r.kt)("inlineCode",{parentName:"p"},"# Examples")," markdown heading above to make a section for our examples. Some other commonly used headings:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"# Panics")," describes the scenarios in which the given function might panic."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"# Errors")," describes the kinds of conditions under which this function might return an error, and what kinds of errors are returned."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("inlineCode",{parentName:"li"},"# Safety")," should be present for any function that is ",(0,r.kt)("inlineCode",{parentName:"li"},"unsafe")," (see ",(0,r.kt)("a",{parentName:"li",href:"/rust-book-abridged/ch19/ch19-01-unsafe",title:"Chapter 19: Advanced Features"},"chapter 19"),").")),(0,r.kt)("p",null,"You don't need all of these on every function. Any examples you provide will automatically be run as tests when you ",(0,r.kt)("inlineCode",{parentName:"p"},"cargo test"),", so you'll know your examples actually work."),(0,r.kt)("h3",{id:"commenting-contained-items"},"Commenting Contained Items"),(0,r.kt)("p",null,"There's a second kind of documentation comment ",(0,r.kt)("inlineCode",{parentName:"p"},"//!"),' that, instead of documenting what comes right after it, documents the "parent" of the comment. You can use these at the root of ',(0,r.kt)("em",{parentName:"p"},"src/lib.rs")," to document the crate as a whole, or inside a module to document the module:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"//! # My Crate\n//!\n//! `my_crate` is a collection of utilities to make performing certain\n//! calculations more convenient.\n\n/// Adds one to the number given.\n// --snip--\n")),(0,r.kt)("h3",{id:"exporting-a-convenient-public-api-with-pub-use"},"Exporting a Convenient Public API with ",(0,r.kt)("inlineCode",{parentName:"h3"},"pub use")),(0,r.kt)("p",null,"We talked about this back in ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch07-packages-crates-modules#re-exporting-names-with-pub-use"},"chapter 7"),", but sometimes we might organize the internals of our library in such a way that makes sense to use when we're developing it, but is at odds with how someone would actually want to use our crate. If you have some struct or module that is frequently used by users of your crate, but is buried deep in the module hierarchy, this is going to be a pain point for your users."),(0,r.kt)("p",null,"Here's an example:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"//! # Art\n//!\n//! A library for modeling artistic concepts.\n\npub mod kinds {\n /// The primary colors according to the RYB color model.\n pub enum PrimaryColor {\n Red,\n Yellow,\n Blue,\n }\n\n /// The secondary colors according to the RYB color model.\n pub enum SecondaryColor {\n Orange,\n Green,\n Purple,\n }\n}\n\npub mod utils {\n use crate::kinds::*;\n\n /// Combines two primary colors in equal amounts to create\n /// a secondary color.\n pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {\n // --snip--\n }\n}\n")),(0,r.kt)("p",null,"Users of this crate would have to write code like:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"use art::kinds::PrimaryColor;\nuse art::utils::mix;\n\nfn main() {\n let red = PrimaryColor::Red;\n let yellow = PrimaryColor::Yellow;\n mix(red, yellow);\n}\n")),(0,r.kt)("p",null,"but users of this crate probably don't care about ",(0,r.kt)("inlineCode",{parentName:"p"},"kinds")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"utils"),' - to them this should be top level functionality for this crate. We can "re-export" these at the top level with ',(0,r.kt)("inlineCode",{parentName:"p"},"pub use"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"//! # Art\n//!\n//! A library for modeling artistic concepts.\n\npub use self::kinds::PrimaryColor;\npub use self::kinds::SecondaryColor;\npub use self::utils::mix;\n\npub mod kinds {\n // --snip--\n}\n\npub mod utils {\n // --snip--\n}\n")),(0,r.kt)("h3",{id:"adding-metadata-to-a-new-crate"},"Adding Metadata to a New Crate"),(0,r.kt)("p",null,"In order to publish on ",(0,r.kt)("a",{parentName:"p",href:"https://crates.io"},"crates.io"),", our crate needs a name, a description, and a valid ",(0,r.kt)("a",{parentName:"p",href:"https://spdx.org/licenses/"},"license identifier"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-toml",metastring:'title="Cargo.toml"',title:'"Cargo.toml"'},'[package]\nname = "my_awesome_colors"\nversion = "0.1.0"\nedition = "2021"\ndescription = "A fancy awesome crate for generating colored text in the terminal."\nlicense = "MIT"\n')),(0,r.kt)("h3",{id:"publishing-your-crate"},"Publishing Your Crate"),(0,r.kt)("p",null,"To publish your package run:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-sh"},"$ cargo publish\n")),(0,r.kt)("p",null,"If someone has already used your name on ",(0,r.kt)("a",{parentName:"p",href:"https://crates.io"},"crates.io")," then this will fail - names are handed out first-come-first-served. If you make some changes to your crate, bump the version number (using ",(0,r.kt)("a",{parentName:"p",href:"https://semver.org/"},"semantic versioning rules"),") and then run ",(0,r.kt)("inlineCode",{parentName:"p"},"cargo publish")," again."),(0,r.kt)("h3",{id:"deprecating-versions-from-cratesio-with-cargo-yank"},"Deprecating Versions from Crates.io with ",(0,r.kt)("inlineCode",{parentName:"h3"},"cargo yank")),(0,r.kt)("p",null,"Sometimes an older version of your crate will have some terrible security bug, or has a fatal bug that completely breaks it. For this or other reasons, you'll want to stop people from installing this version and warn existing users they need to upgrade. You can't remove an old version, but you can mark it as deprecated:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-sh"},"$ cargo yank --vers 1.0.1\n")),(0,r.kt)("p",null,"If you accidentally yank the wrong version, you can undo this:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-sh"},"$ cargo yank --vers 1.0.1 --undo\n")),(0,r.kt)("h2",{id:"143---cargo-workspaces"},"14.3 - Cargo Workspaces"),(0,r.kt)("p",null,"Sometimes a library crate gets so large that you want to split it up into multiple smaller crates. Cargo workspaces lets you do this while keeping all the crates together in the same git repo. It's a bit like the Rust version of a monorepo. You can build all packages in a workspace by running ",(0,r.kt)("inlineCode",{parentName:"p"},"cargo build")," from the root folder of the workspace, and run tests in all workspaces with ",(0,r.kt)("inlineCode",{parentName:"p"},"cargo test"),"."),(0,r.kt)("h3",{id:"creating-a-workspace"},"Creating a Workspace"),(0,r.kt)("p",null,"A workspace consists of multiple packages with their own individual ",(0,r.kt)("em",{parentName:"p"},"Cargo.yaml")," files, with a single ",(0,r.kt)("em",{parentName:"p"},"Cargo.lock")," file at the root of the workspace. We'll create a simple example here with a single binary crate and two library crates. If you want to see the code for this, check ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/jwalton/rust-book-abridged/blob/master/examples/ch14-workspace"},"this book's GitHub repo"),". First we'll create a new directory for our workspace and initialize it as a git repo:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-sh"},'$ mkdir add\n$ cd add\n$ git init .\n$ echo "/target" > .gitignore\n')),(0,r.kt)("p",null,"The ",(0,r.kt)("em",{parentName:"p"},"add"),' directory is the root of our workspace, so all other files we create will be relative to this folder. We\'re going to add three packages to this workspace: "adder", our binary package, and "add_one" and "add_two", our libraries. Let\'s start by creating these packages as subdirectories:'),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-sh"},"$ cargo new adder\n$ cargo new add_one --lib\n$ cargo new add_two --lib\n")),(0,r.kt)("p",null,"You may have noticed that we ran ",(0,r.kt)("inlineCode",{parentName:"p"},"git init")," in the add directory - we did this because generally we want to commit the entire workspace as a single repo, and if we hadn't run ",(0,r.kt)("inlineCode",{parentName:"p"},"git init"),", then ",(0,r.kt)("inlineCode",{parentName:"p"},"cargo new ..."),' would have "helpfully" initialized all three new packages as git repos for us.'),(0,r.kt)("p",null,"Now in the ",(0,r.kt)("em",{parentName:"p"},"add")," folder - the root folder of our workspace - we are going to create a ",(0,r.kt)("em",{parentName:"p"},"Cargo.toml")," for the entire workspace. This ",(0,r.kt)("em",{parentName:"p"},"Cargo.toml")," won't have any metadata or dependencies, it will simply list all the packages that make up the workspace:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-toml"},'[workspace]\n\nmembers = [\n "adder",\n "add_one",\n "add_two",\n]\n')),(0,r.kt)("admonition",{type:"tip"},(0,r.kt)("p",{parentName:"admonition"},"If you do these in the opposite order - create the top-level Cargo.toml first and then create the child packages - it will still work, but as you create each package you'll get warnings from cargo about not being able to find the packages you haven't created yet.")),(0,r.kt)("p",null,"We can build this workspace, to make sure we did everything right:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-sh"},"$ cargo build\n Compiling add_two v0.1.0 (add/add_two)\n Compiling adder v0.1.0 (add/adder)\n Compiling add_one v0.1.0 (add/add_one)\n Finished dev [unoptimized + debuginfo] target(s) in 0.11s\n")),(0,r.kt)("p",null,"At this point you should have a directory structure inside ",(0,r.kt)("em",{parentName:"p"},"add")," that looks like:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-txt"},"\u251c\u2500\u2500 .git\n\u251c\u2500\u2500 .gitignore\n\u251c\u2500\u2500 Cargo.lock\n\u251c\u2500\u2500 Cargo.toml\n\u251c\u2500\u2500 add_one\n\u2502 \u251c\u2500\u2500 Cargo.toml\n\u2502 \u2514\u2500\u2500 src\n\u2502 \u2514\u2500\u2500 lib.rs\n\u251c\u2500\u2500 add_two\n\u2502 \u251c\u2500\u2500 Cargo.toml\n\u2502 \u2514\u2500\u2500 src\n\u2502 \u2514\u2500\u2500 lib.rs\n\u251c\u2500\u2500 adder\n\u2502 \u251c\u2500\u2500 Cargo.toml\n\u2502 \u2514\u2500\u2500 src\n\u2502 \u2514\u2500\u2500 main.rs\n\u2514\u2500\u2500 target\n")),(0,r.kt)("p",null,"Note that the top level folder has a ",(0,r.kt)("em",{parentName:"p"},"Cargo.lock")," file, but none of the child projects do."),(0,r.kt)("h3",{id:"referencing-other-packages-in-the-workspace"},"Referencing Other Packages in the Workspace"),(0,r.kt)("p",null,"We'll put this in ",(0,r.kt)("em",{parentName:"p"},"add_one/src/lib.rs"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"pub fn add_one(x: i32) -> i32 {\n x + 1\n}\n")),(0,r.kt)("p",null,"We want to use this library in our binary crate in the ",(0,r.kt)("em",{parentName:"p"},"adder")," folder. To do this, first we have to add the ",(0,r.kt)("inlineCode",{parentName:"p"},"add_one")," package as a dependency of ",(0,r.kt)("inlineCode",{parentName:"p"},"adder"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-toml",metastring:'title="adder/Cargo.toml"',title:'"adder/Cargo.toml"'},'[dependencies]\nadd_one = { path = "../add_one" }\n')),(0,r.kt)("p",null,"And then we can ",(0,r.kt)("inlineCode",{parentName:"p"},"use add_one")," in the adder package, just as we would any other dependency:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust",metastring:'title="adder/src/main.rs"',title:'"adder/src/main.rs"'},'use add_one;\n\nfn main() {\n let num = 10;\n println!("Hello, world! {num} plus one is {}!", add_one::add_one(num));\n}\n')),(0,r.kt)("p",null,"From the ",(0,r.kt)("em",{parentName:"p"},"add")," directory we can now run:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-sh"},"$ cargo run\n Finished dev [unoptimized + debuginfo] target(s) in 0.00s\n Running `target/debug/adder`\nHello, world! 10 plus one is 11!\n")),(0,r.kt)("p",null,"If we had multiple packages with binary crates in the workspace, we'd have to specify which package to run with ",(0,r.kt)("inlineCode",{parentName:"p"},"cargo run -p adder"),", or we could ",(0,r.kt)("inlineCode",{parentName:"p"},"cd adder")," and then ",(0,r.kt)("inlineCode",{parentName:"p"},"cargo run")," from the adder folder."),(0,r.kt)("h3",{id:"depending-on-an-external-package-in-a-workspace"},"Depending on an External Package in a Workspace"),(0,r.kt)("p",null,"We can depend on an external create in a workspace by adding it to the ",(0,r.kt)("inlineCode",{parentName:"p"},"[dependencies]")," section of the appropriate package's ",(0,r.kt)("em",{parentName:"p"},"Cargo.toml"),". For example, we can add the ",(0,r.kt)("inlineCode",{parentName:"p"},"rand")," crate to ",(0,r.kt)("inlineCode",{parentName:"p"},"add_one")," in ",(0,r.kt)("em",{parentName:"p"},"add_one/Cargo.toml"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-toml",metastring:'title="add_one/Cargo.toml"',title:'"add_one/Cargo.toml"'},'[dependencies]\nrand = "0.8.5"\n')),(0,r.kt)("p",null,"If we add ",(0,r.kt)("inlineCode",{parentName:"p"},"use rand;")," inside ",(0,r.kt)("em",{parentName:"p"},"add_one/src/lib.rs"),", then ",(0,r.kt)("inlineCode",{parentName:"p"},"cargo build"),", we'll see the ",(0,r.kt)("inlineCode",{parentName:"p"},"rand")," package being downloaded. We'll also get a warning because we're ",(0,r.kt)("inlineCode",{parentName:"p"},"use"),"ing rand, but we never reference it in the library. Oops!"),(0,r.kt)("p",null,"If we want to use ",(0,r.kt)("inlineCode",{parentName:"p"},"rand")," in other packages in our workspace, we have to add it again to the appropriate ",(0,r.kt)("em",{parentName:"p"},"Cargo.yaml"),". Since there's only one ",(0,r.kt)("em",{parentName:"p"},"Cargo.lock")," file for the whole workspace, if ",(0,r.kt)("inlineCode",{parentName:"p"},"adder")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"add_one")," both depend on ",(0,r.kt)("inlineCode",{parentName:"p"},"rand"),", we know that they will both depend on the same version of ",(0,r.kt)("inlineCode",{parentName:"p"},"rand")," thanks to the common lockfile (at least, assuming the have compatible semver versions in the different ",(0,r.kt)("em",{parentName:"p"},"Cargo.toml")," files)."),(0,r.kt)("h2",{id:"144---installing-binaries-with-cargo-install"},"14.4 - Installing Binaries with cargo install"),(0,r.kt)("p",null,"You can publish more than just library crates to crates.io - you can also publish binary crates! Users can install your crates with ",(0,r.kt)("inlineCode",{parentName:"p"},"cargo install"),". (This is very similar to ",(0,r.kt)("inlineCode",{parentName:"p"},"npm install -g")," if you're a node.js developer.) For example, ",(0,r.kt)("inlineCode",{parentName:"p"},"ripgrep")," is a very fast alternative to the ",(0,r.kt)("inlineCode",{parentName:"p"},"grep")," command:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-sh"},"$ cargo install ripgrep\n")),(0,r.kt)("p",null,"Binaries you install this way get put in ",(0,r.kt)("inlineCode",{parentName:"p"},"~/.cargo/bin")," (assuming you're running a default installation of cargo from rustup). You'll probably want to put this folder in your shell's ",(0,r.kt)("inlineCode",{parentName:"p"},"$PATH"),". The name of the installed binary is not necessarily the same as the name of the crate. If you try installing ",(0,r.kt)("inlineCode",{parentName:"p"},"ripgrep")," above, for example, it will install ",(0,r.kt)("inlineCode",{parentName:"p"},"~/.cargo/rg"),"."),(0,r.kt)("h2",{id:"145---extending-cargo-with-custom-commands"},"14.5 - Extending Cargo with Custom Commands"),(0,r.kt)("p",null,"Much like git, you can create your own custom cargo commands. If there's an executable on your path called ",(0,r.kt)("inlineCode",{parentName:"p"},"cargo-something"),", then you can run ",(0,r.kt)("inlineCode",{parentName:"p"},"cargo something")," to run that executable. These custom commands will also show up in ",(0,r.kt)("inlineCode",{parentName:"p"},"cargo --list"),"."),(0,r.kt)("p",null,"One handy command you can install this way is ",(0,r.kt)("a",{parentName:"p",href:"https://docs.rs/cargo-audit/latest/cargo_audit/"},"cargo-audit"),", which will check your dependencies against the ",(0,r.kt)("a",{parentName:"p",href:"https://rustsec.org/"},"rustsec security advisory database"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-sh"},"$ cargo install cargo-audit\n$ cargo audit\n")),(0,r.kt)("p",null,"Continue to ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch15-smart-pointers",title:"Chapter 15: Smart Pointers"},"chapter 15"),"."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/829cc90e.75d46d74.js b/assets/js/829cc90e.75d46d74.js new file mode 100644 index 0000000..9a983ac --- /dev/null +++ b/assets/js/829cc90e.75d46d74.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkrust_book_abridged=self.webpackChunkrust_book_abridged||[]).push([[809],{3905:(e,t,n)=>{n.d(t,{Zo:()=>u,kt:()=>h});var a=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var s=a.createContext({}),p=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},u=function(e){var t=p(e.components);return a.createElement(s.Provider,{value:t},e.children)},m="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},c=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,i=e.originalType,s=e.parentName,u=l(e,["components","mdxType","originalType","parentName"]),m=p(n),c=r,h=m["".concat(s,".").concat(c)]||m[c]||d[c]||i;return n?a.createElement(h,o(o({ref:t},u),{},{components:n})):a.createElement(h,o({ref:t},u))}));function h(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var i=n.length,o=new Array(i);o[0]=c;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[m]="string"==typeof e?e:r,o[1]=l;for(var p=2;p{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>d,frontMatter:()=>i,metadata:()=>l,toc:()=>p});var a=n(7462),r=(n(7294),n(3905));const i={},o="3 - Common Programming Concepts",l={unversionedId:"ch03-common-programming-concepts",id:"ch03-common-programming-concepts",title:"3 - Common Programming Concepts",description:"In which we learn about variables, basic types, functions, comments, and control flow.",source:"@site/docs/ch03-common-programming-concepts.md",sourceDirName:".",slug:"/ch03-common-programming-concepts",permalink:"/rust-book-abridged/ch03-common-programming-concepts",draft:!1,editUrl:"https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/docs/ch03-common-programming-concepts.md",tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"2 - Programming a Guessing Game",permalink:"/rust-book-abridged/ch02-guessing-game"},next:{title:"4 - Ownership, References, and Slices",permalink:"/rust-book-abridged/ch04-ownership"}},s={},p=[{value:"3.1 - Variables and Mutability",id:"31---variables-and-mutability",level:2},{value:"Constants",id:"constants",level:3},{value:"Static Variables",id:"static-variables",level:3},{value:"Shadowing",id:"shadowing",level:3},{value:"3.2 - Data Types",id:"32---data-types",level:2},{value:"Integer Types",id:"integer-types",level:3},{value:"Floating-Point Types",id:"floating-point-types",level:3},{value:"Number Operators",id:"number-operators",level:3},{value:"Boolean type",id:"boolean-type",level:3},{value:"Character Type",id:"character-type",level:3},{value:"&str and String",id:"str-and-string",level:3},{value:"Compound Types",id:"compound-types",level:2},{value:"Tuple Type",id:"tuple-type",level:3},{value:"Array Type",id:"array-type",level:3},{value:"struct type",id:"struct-type",level:3},{value:"3.3 - Functions",id:"33---functions",level:2},{value:"3.4 - Comments",id:"34---comments",level:2},{value:"3.5 - Control Flow",id:"35---control-flow",level:2},{value:"if Expression",id:"if-expression",level:3},{value:"Repetition with Loops",id:"repetition-with-loops",level:3}],u={toc:p},m="wrapper";function d(e){let{components:t,...n}=e;return(0,r.kt)(m,(0,a.Z)({},u,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"3---common-programming-concepts"},"3 - Common Programming Concepts"),(0,r.kt)("p",null,"In which we learn about variables, basic types, functions, comments, and control flow."),(0,r.kt)("h2",{id:"31---variables-and-mutability"},"3.1 - Variables and Mutability"),(0,r.kt)("p",null,"Variables are declared with the ",(0,r.kt)("inlineCode",{parentName:"p"},"let")," keyword. By default, variables are immutable unless they are declared ",(0,r.kt)("inlineCode",{parentName:"p"},"mut"),". This program will fail to compile with the error ",(0,r.kt)("inlineCode",{parentName:"p"},"cannot assign twice to immutable variable `x` "),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"fn main() {\n let x = 5;\n x = 6; // This will error!\n\n let mut y = 5;\n y = 6; // This is okay.\n}\n")),(0,r.kt)("p",null,"Immutability in Rust is similar to ",(0,r.kt)("inlineCode",{parentName:"p"},"const")," in JavaScript, or ",(0,r.kt)("inlineCode",{parentName:"p"},"final")," in Java. The value the reference points to can't be modified (mostly - see the info box below):"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'fn main() {\n let foo = String::from("foo");\n foo.clear(); // This will error!\n}\n')),(0,r.kt)("p",null,"Here ",(0,r.kt)("inlineCode",{parentName:"p"},"clear")," will try to empty the string, but will fail with the error ",(0,r.kt)("inlineCode",{parentName:"p"},"cannot borrow `foo` as mutable, as it is not declared as mutable"),". If you go look at the source code for the ",(0,r.kt)("inlineCode",{parentName:"p"},"clear")," method it is defined as requiring ",(0,r.kt)("inlineCode",{parentName:"p"},"self")," to be a mutable reference (",(0,r.kt)("inlineCode",{parentName:"p"},"self")," is a bit like ",(0,r.kt)("inlineCode",{parentName:"p"},"this")," in other languages)."),(0,r.kt)("p",null,"Variables cannot be declared at the global scope ",(0,r.kt)("a",{parentName:"p",href:"#static-variables"},"unless they are ",(0,r.kt)("inlineCode",{parentName:"a"},"static")),"."),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},'You may have noticed that that "mostly" above when we were talking about immutable variables. Immutability prevents us from directly modifying members of a struct, however in ',(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch15-smart-pointers",title:"Chapter 15: Smart Pointers"},"chapter 15")," we're going to find out that sometimes you can modify individual parts of an immutable struct through a concept call ",(0,r.kt)("em",{parentName:"p"},"interior mutability"),". A mutex is an example of an object that is immutable, but you're allowed to change the value in it if you own the lock.")),(0,r.kt)("h3",{id:"constants"},"Constants"),(0,r.kt)("p",null,"Rust also has the concept of a ",(0,r.kt)("em",{parentName:"p"},"constant")," which at first sounds a lot like an immutable variable:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;\n")),(0,r.kt)("p",null,"Constants are subtly different from immutable variables. They are stored directly in the program binary, so they cannot be ",(0,r.kt)("inlineCode",{parentName:"p"},"mut")," and the value of the constant has to be something that can be determined at compile time. The Rust Reference has a ",(0,r.kt)("a",{parentName:"p",href:"https://doc.rust-lang.org/stable/reference/const_eval.html"},"section on constant evaluation")," that lays out all the rules for what operators you're allowed to use and what you're not, but here the compiler can convert ",(0,r.kt)("inlineCode",{parentName:"p"},"60 * 60 * 3")," into ",(0,r.kt)("inlineCode",{parentName:"p"},"10800")," for us and store that in the executable."),(0,r.kt)("p",null,"Constants must always be annotated, and can be declared in the global scope."),(0,r.kt)("h3",{id:"static-variables"},"Static Variables"),(0,r.kt)("p",null,(0,r.kt)("em",{parentName:"p"},"Static variables")," or global variables are declared with the static keyword and are named in ",(0,r.kt)("inlineCode",{parentName:"p"},"SCREAMING_SNAKE_CASE"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'static HELLO_WORLD: &str = "Hello, world!";\n\nfn main() {\n println!("name is: {}", HELLO_WORLD);\n}\n')),(0,r.kt)("p",null,"Static variables can be mutable, but to access or modify them we need to talk about ",(0,r.kt)("inlineCode",{parentName:"p"},"unsafe")," code, ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch19/ch19-01-unsafe#accessing-or-modifying-a-mutable-static-variable"},"which we'll do later"),"."),(0,r.kt)("h3",{id:"shadowing"},"Shadowing"),(0,r.kt)("p",null,"As we saw in ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch02-guessing-game",title:"Chapter 2: Guessing Game"},"chapter 2"),", a variable declaration can shadow another:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'fn main() {\n let x = 5;\n\n let x = x + 1;\n\n {\n let x = x * 2;\n println!("The value of x in the inner scope is: {x}");\n }\n\n println!("The value of x is: {x}");\n}\n')),(0,r.kt)("p",null,'There are a total of three variables in this function, all named "x". Variables last until the end of the block they were declared in, so this program prints out:'),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-txt"},"The value of x in the inner scope is: 12\nThe value of x is: 6\n")),(0,r.kt)("p",null,"When shadowing a variable, the new variable does not have to have the same type as the one it is shadowing."),(0,r.kt)("h2",{id:"32---data-types"},"3.2 - Data Types"),(0,r.kt)("p",null,"Keep in mind that Rust is a statically typed language, so the type of every variable (and how much space it will occupy in memory, if it is stored on the stack) must be known at compile time. Rust's type inference is amazing, so frequently we don't have to tell Rust what type a variable is, but sometimes a variable's type is ambiguous in which case we need to ",(0,r.kt)("em",{parentName:"p"},"annotate")," it (e.g. ",(0,r.kt)("inlineCode",{parentName:"p"},"let guess: 32 = ..."),")."),(0,r.kt)("p",null,'A "scalar type" represents a single value. There are four kinds of scalar types in Rust: integers, floating-point numbers, Booleans, and characters.'),(0,r.kt)("h3",{id:"integer-types"},"Integer Types"),(0,r.kt)("p",null,"Integer types:"),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"Length (bits)"),(0,r.kt)("th",{parentName:"tr",align:null},"Signed"),(0,r.kt)("th",{parentName:"tr",align:null},"Unsigned"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"8"),(0,r.kt)("td",{parentName:"tr",align:null},"i8"),(0,r.kt)("td",{parentName:"tr",align:null},"u8")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"16"),(0,r.kt)("td",{parentName:"tr",align:null},"i16"),(0,r.kt)("td",{parentName:"tr",align:null},"u16")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"32"),(0,r.kt)("td",{parentName:"tr",align:null},"i32"),(0,r.kt)("td",{parentName:"tr",align:null},"u32")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"64"),(0,r.kt)("td",{parentName:"tr",align:null},"i64"),(0,r.kt)("td",{parentName:"tr",align:null},"u64")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"128"),(0,r.kt)("td",{parentName:"tr",align:null},"i128"),(0,r.kt)("td",{parentName:"tr",align:null},"u128")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"arch"),(0,r.kt)("td",{parentName:"tr",align:null},"isize"),(0,r.kt)("td",{parentName:"tr",align:null},"usize")))),(0,r.kt)("p",null,"Signed integers are stored using ",(0,r.kt)("a",{parentName:"p",href:"https://en.wikipedia.org/wiki/Two%27s_complement"},"two's complement"),". ",(0,r.kt)("inlineCode",{parentName:"p"},"isize")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"usize")," depend on your architecture, so they'll be 32 bit numbers on a 32 bit architecture, or 64 bit on a 64 bit architecture."),(0,r.kt)("p",null,"Integer literals can be written using any of the methods below. Integer literals in Rust can use an ",(0,r.kt)("inlineCode",{parentName:"p"},"_"),' as a visual separator (similar to how we might write "1,000" in English, we can write "1_000" in Rust).'),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"Number literals"),(0,r.kt)("th",{parentName:"tr",align:null},"Example"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"Decimal"),(0,r.kt)("td",{parentName:"tr",align:null},"98_222")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"Hex"),(0,r.kt)("td",{parentName:"tr",align:null},"0xff")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"Octal"),(0,r.kt)("td",{parentName:"tr",align:null},"Oo77")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"Binary"),(0,r.kt)("td",{parentName:"tr",align:null},"0b1111_0000")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"Byte (u8)"),(0,r.kt)("td",{parentName:"tr",align:null},"b'A'")))),(0,r.kt)("p",null,"If you try to overflow an integer (e.g. you try to store 256 in a u8), what happens (by default) depends on whether you compiled your program with ",(0,r.kt)("inlineCode",{parentName:"p"},"--release")," or not. In debug mode Rust adds runtime checks to ensure you don't overflow a value, so your program will panic and crash (see ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch09-error-handling",title:"Chapter 9: Error Handling"},"chapter 9")," for more about panics). With the --release flag, the integer will overflow as you would expect it to in another language like C or Java (the largest value a u8 can hold is 255, so 256 wraps to 0)."),(0,r.kt)("p",null,"The standard library has functions that let you explicitly define how you want to handle overflows if you don't want to panic. For example ",(0,r.kt)("a",{parentName:"p",href:"https://doc.rust-lang.org/std/intrinsics/fn.wrapping_add.html"},(0,r.kt)("inlineCode",{parentName:"a"},"wrapping_add"))," will add two numbers and let them wrap around. There are ",(0,r.kt)("inlineCode",{parentName:"p"},"wrapping_*"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"checked_*"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"overflowing_*"),", and ",(0,r.kt)("inlineCode",{parentName:"p"},"saturating_*")," functions for integer arithmetic."),(0,r.kt)("admonition",{type:"tip"},(0,r.kt)("p",{parentName:"admonition"},"We can change how overflows are handled at runtime for development and release through ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch14-more-about-cargo#141---customizing-builds-with-release-profiles"},"release profiles"),".")),(0,r.kt)("h3",{id:"floating-point-types"},"Floating-Point Types"),(0,r.kt)("p",null,"There are two floating point types, ",(0,r.kt)("inlineCode",{parentName:"p"},"f64")," (the default) and ",(0,r.kt)("inlineCode",{parentName:"p"},"f32"),". Floating-point numbers are stored using the IEEE-754 standard."),(0,r.kt)("h3",{id:"number-operators"},"Number Operators"),(0,r.kt)("p",null,"Rust has the operators you'd expect: ",(0,r.kt)("inlineCode",{parentName:"p"},"+"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"-"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"*"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"/"),", and ",(0,r.kt)("inlineCode",{parentName:"p"},"%")," for modulus. See ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/zz-appendix/appendix-02-operators"},"the Rust Book Appendix B")," for a complete list of all the operators in Rust."),(0,r.kt)("h3",{id:"boolean-type"},"Boolean type"),(0,r.kt)("p",null,"Booleans are of type ",(0,r.kt)("inlineCode",{parentName:"p"},"bool")," and can be ",(0,r.kt)("inlineCode",{parentName:"p"},"true")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"false"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"let t = true;\nlet f: bool = false;\n")),(0,r.kt)("h3",{id:"character-type"},"Character Type"),(0,r.kt)("p",null,"A ",(0,r.kt)("inlineCode",{parentName:"p"},"char")," in Rust is a four-byte unicode scalar value."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"let c = 'z';\nlet z: char = '\u2124';\nlet heart_eyed_cat = '\ud83d\ude3b';\nlet space_woman_zwj = '\ud83d\udc69\ud83c\udffb\u200d\ud83d\ude80'; // <== This doesn't work!\n")),(0,r.kt)("p",null,"That last example doesn't work. Our female astronaut friend might look like a single character, but she's actually two emoji joined together with a zero-width-joiner (ZWJ). We'll talk a lot more about UTF-8 and Unicode in ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch08-common-collections",title:"Chapter 8: Common Collections"},"chapter 8"),"."),(0,r.kt)("h3",{id:"str-and-string"},(0,r.kt)("inlineCode",{parentName:"h3"},"&str")," and ",(0,r.kt)("inlineCode",{parentName:"h3"},"String")),(0,r.kt)("p",null,"You'll see two different string types in Rust: ",(0,r.kt)("inlineCode",{parentName:"p"},"str")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"String"),". ",(0,r.kt)("inlineCode",{parentName:"p"},"String")," is similar to a ",(0,r.kt)("inlineCode",{parentName:"p"},"Vector")," - it's a data type that stores a list of characters in a variable-length chunk of memory on the heap. Any time you accept input from the user or read a string from a file, it's going to end up in a ",(0,r.kt)("inlineCode",{parentName:"p"},"String"),"."),(0,r.kt)("p",null,"The type ",(0,r.kt)("inlineCode",{parentName:"p"},"&str")," (almost always seen in it's borrowed form) is also known as a ",(0,r.kt)("em",{parentName:"p"},"string slice")," (which we'll learn more about in ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch04-ownership",title:"Chapter 4: Ownership, References, and Slices"},"the next chapter"),"), and is both a pointer to the string's data and a length for the string. Any string literal in Rust is a ",(0,r.kt)("inlineCode",{parentName:"p"},"&str"),", since the actual string is stored somewhere in the executable and we just have an immutable reference to it. A string slice can be used as a view into a ",(0,r.kt)("inlineCode",{parentName:"p"},"String"),"."),(0,r.kt)("h2",{id:"compound-types"},"Compound Types"),(0,r.kt)("p",null,"Compound types group multiple values into one type. Rust has two primitive compound types, the ",(0,r.kt)("em",{parentName:"p"},"tuple")," and the ",(0,r.kt)("em",{parentName:"p"},"array"),"."),(0,r.kt)("h3",{id:"tuple-type"},"Tuple Type"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"let tup: (i32, f64, u8) = (800, 6.4, 1);\n\n// Destructuring assignment\nlet (x, y, z) = tup;\n\n// Access individual elements\nlet a = tup.0;\nlet b = tup.1;\nlet c = tup.2;\n")),(0,r.kt)("p",null,"An empty tuple is written ",(0,r.kt)("inlineCode",{parentName:"p"},"()"),' and is called a "unit". This represents an empty value or an empty return type. Functions without a return type implicitly return this.'),(0,r.kt)("h3",{id:"array-type"},"Array Type"),(0,r.kt)("p",null,'Every element in an array must be the same type, and arrays must be fixed length. If you\'re looking for a "variable length" array, you want a vector from the standard library (see ',(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch08-common-collections",title:"Chapter 8: Common Collections"},"Chapter 8"),"). If you declare a variable as an array in a function, then the contents of that variable will end up on the stack, while for a vector contents will end up on the heap. (Although you can put the contents of an array on the heap by using a smart pointer like a ",(0,r.kt)("inlineCode",{parentName:"p"},"Box")," - see ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch15-smart-pointers",title:"Chapter 15: Smart Pointers"},"chapter 15"),")."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"let a = [1, 2, 3, 4, 5];\n\n// Destructuring assignment. Must use all elements!\nlet [x, y, z, _, _] = a;\n\n// Access individual elements\nlet first = a[0];\nlet second = a[1];\n\n// Create array of type i32, length 5.\nlet b: [i32; 5] = [1, 2, 3, 4, 5];\n\n// Create array of five zeros.\nlet c = [0; 5]\n")),(0,r.kt)("p",null,"Array accesses are checked at runtime. Trying to access an index which is out-of-bounds will cause a panic."),(0,r.kt)("p",null,"If you're coming to Rust from JavaScript, it's worth pointing out that JavaScript \"arrays\" are not quite like arrays in any other programming language. The Rust ",(0,r.kt)("inlineCode",{parentName:"p"},"Vec")," type, or ",(0,r.kt)("em",{parentName:"p"},"vector"),", is much closer to a JavaScript array than a Rust array is. We'll talk about vectors in ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch08-common-collections",title:"Chapter 8: Common Collections"},"chapter 8"),"."),(0,r.kt)("h3",{id:"struct-type"},(0,r.kt)("inlineCode",{parentName:"h3"},"struct")," type"),(0,r.kt)("p",null,"We can define our own compound types using the ",(0,r.kt)("inlineCode",{parentName:"p"},"struct")," keyword:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"struct User {\n name: String,\n age: u32,\n}\n")),(0,r.kt)("h2",{id:"33---functions"},"3.3 - Functions"),(0,r.kt)("p",null,"Functions are defined by ",(0,r.kt)("inlineCode",{parentName:"p"},"fn")," keyword. Parameters are required to have a type annotation, and are annotated with ",(0,r.kt)("inlineCode",{parentName:"p"},": type")," just like variables (and just like in Typescript)."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'fn another_function(x: i32, y: i32) {\n println!("The point is at: {x}, {y}");\n}\n')),(0,r.kt)("p",null,"If you end a function with an expression instead of a statement, then the function will return the value of that expression. Return types must be explicitly declared with an arrow (",(0,r.kt)("inlineCode",{parentName:"p"},"->"),")."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"// Returns 1\nfn implicit_return() -> i32 {\n 1\n}\n\n// Also returns 1, but using `return` is not\n// idiomatic in Rust unless you want to return\n// from the middle of a function.\nfn explicit_return() -> i32 {\n return 1;\n}\n\n// The semicolon makes this a statement instead\n// of an expression, so this returns `()`.\nfn no_return() {\n 1;\n}\n")),(0,r.kt)("p",null,"Assignments are always statements (i.e. ",(0,r.kt)("inlineCode",{parentName:"p"},"let x = 6")," does not evaluate to 6), as are function definitions (i.e. you can't do ",(0,r.kt)("inlineCode",{parentName:"p"},"let x = fn foo() {}"),"). Functions can be called before they are defined. In ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch10/ch10-01-generic-data-types",title:"Chapter 10: Generic Types, Traits, and Lifetimes"},"chapter 10")," we'll learn about using generics with functions."),(0,r.kt)("p",null,"Rust also has closures, which are inline functions that can be assigned to variables or passed as parameters. We'll learn about them in detail in ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch13-functional-language-features",title:"Chapter 13: Functional Language Features: Iterators and Closures"},"chapter 13"),", but the syntax is:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"let my_closure = |param1, param2| { /* function body goes here */ };\n")),(0,r.kt)("h2",{id:"34---comments"},"3.4 - Comments"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'// This is a comment. Multi-line comments\n// generally are written this way.\n\n/* You can use this style of comment too. */\n\n/// This is a doc comment for the "next thing", in\n/// this case for the `foo` function. Markdown is\n/// allowed here. See chapter 14 for more details.\nfn foo() {}\n\nmod bar {\n //! This is a doc comment for the "parent thing",\n //! in this case the "bar" module.\n}\n')),(0,r.kt)("h2",{id:"35---control-flow"},"3.5 - Control Flow"),(0,r.kt)("h3",{id:"if-expression"},"if Expression"),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"if")," statements don't have braces around the condition, (much like Go, and much unlike Java, JavaScript, or C):"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'if number < 5 {\n println!("less than 5");\n} else if number > 10 {\n println!("greater than 10");\n} else {\n println!("greater than 4, less than 11");\n}\n')),(0,r.kt)("p",null,"Note that ",(0,r.kt)("inlineCode",{parentName:"p"},"if"),' can be used as an expression. In this case each "arm" of the if must be the same type:'),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'// This is OK\nlet number = if condition { 5 } else { 6 };\n\n// This breaks! `if` and `else` have\n// incompatible types\nlet wat = if condition { 5 } else { "six" };\n\n// But this is OK.\nloop {\n let wat = if condition { 5 } else { break };\n}\n')),(0,r.kt)("h3",{id:"repetition-with-loops"},"Repetition with Loops"),(0,r.kt)("p",null,"Rust has three kinds of loops: ",(0,r.kt)("inlineCode",{parentName:"p"},"loop"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"while"),", and ",(0,r.kt)("inlineCode",{parentName:"p"},"for"),". The ",(0,r.kt)("inlineCode",{parentName:"p"},"break")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"continue")," statements work exactly as they do in other languages: ",(0,r.kt)("inlineCode",{parentName:"p"},"break")," will stop the loop, and ",(0,r.kt)("inlineCode",{parentName:"p"},"continue")," will stop execution of the current iteration and start the next one. Note that loops can be used as expressions."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"loop {\n println!(\"Infinite loop!\")\n}\n\n// Loops can be used as expressions, with `break`\n// returning the value from the block.\nlet mut counter = 0;\nlet x = loop {\n counter += 1;\n if counter == 10 {\n break counter * 2;\n }\n};\n\n// Loops can be labeled with a single quote\n// followed by the label and the a colon.\n'outer: loop {\n 'inner: loop {\n break 'outer;\n }\n}\n\n// A while loop looks a lot like a\n// while loop in every other language.\nlet mut number = 0;\nwhile number < 10 {\n number++;\n}\n")),(0,r.kt)("p",null,"For loops in Rust are always of the format ",(0,r.kt)("inlineCode",{parentName:"p"},"for [var] in [iterator] {}"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'// Iterate over an array\nlet a = [1, 2, 3, 4, 5];\nfor element in a {\n println!("value is {element}");\n}\n\n// Count from 1 to 5\nfor element in (1..6) {\n println!("value is {element}");\n}\n')),(0,r.kt)("p",null,"We'll see more about iterators in ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch13-functional-language-features",title:"Chapter 13: Functional Language Features: Iterators and Closures"},"chapter 13"),"."),(0,r.kt)("p",null,"Now that we know some basics, it's time to learn about ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch04-ownership",title:"Chapter 4: Ownership, References, and Slices"},"ownership"),"."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/829cc90e.95720db2.js b/assets/js/829cc90e.95720db2.js deleted file mode 100644 index 0a4ce8d..0000000 --- a/assets/js/829cc90e.95720db2.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkrust_book_abridged=self.webpackChunkrust_book_abridged||[]).push([[809],{3905:(e,t,n)=>{n.d(t,{Zo:()=>u,kt:()=>h});var a=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var s=a.createContext({}),p=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},u=function(e){var t=p(e.components);return a.createElement(s.Provider,{value:t},e.children)},m="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},d=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,i=e.originalType,s=e.parentName,u=l(e,["components","mdxType","originalType","parentName"]),m=p(n),d=r,h=m["".concat(s,".").concat(d)]||m[d]||c[d]||i;return n?a.createElement(h,o(o({ref:t},u),{},{components:n})):a.createElement(h,o({ref:t},u))}));function h(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var i=n.length,o=new Array(i);o[0]=d;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[m]="string"==typeof e?e:r,o[1]=l;for(var p=2;p{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>c,frontMatter:()=>i,metadata:()=>l,toc:()=>p});var a=n(7462),r=(n(7294),n(3905));const i={},o="3 - Common Programming Concepts",l={unversionedId:"ch03-common-programming-concepts",id:"ch03-common-programming-concepts",title:"3 - Common Programming Concepts",description:"In which we learn about variables, basic types, functions, comments, and control flow.",source:"@site/docs/ch03-common-programming-concepts.md",sourceDirName:".",slug:"/ch03-common-programming-concepts",permalink:"/rust-book-abridged/ch03-common-programming-concepts",draft:!1,editUrl:"https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/docs/ch03-common-programming-concepts.md",tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"2 - Programming a Guessing Game",permalink:"/rust-book-abridged/ch02-guessing-game"},next:{title:"4 - Ownership, References, and Slices",permalink:"/rust-book-abridged/ch04-ownership"}},s={},p=[{value:"3.1 - Variables and Mutability",id:"31---variables-and-mutability",level:2},{value:"Constants",id:"constants",level:3},{value:"Static Variables",id:"static-variables",level:3},{value:"Shadowing",id:"shadowing",level:3},{value:"3.2 - Data Types",id:"32---data-types",level:2},{value:"Integer Types",id:"integer-types",level:3},{value:"Floating-Point Types",id:"floating-point-types",level:3},{value:"Number Operators",id:"number-operators",level:3},{value:"Boolean type",id:"boolean-type",level:3},{value:"Character Type",id:"character-type",level:3},{value:"&str and String",id:"str-and-string",level:3},{value:"Compound Types",id:"compound-types",level:2},{value:"Tuple Type",id:"tuple-type",level:3},{value:"Array Type",id:"array-type",level:3},{value:"struct type",id:"struct-type",level:3},{value:"3.3 - Functions",id:"33---functions",level:2},{value:"3.4 - Comments",id:"34---comments",level:2},{value:"3.5 - Control Flow",id:"35---control-flow",level:2},{value:"if Expression",id:"if-expression",level:3},{value:"Repetition with Loops",id:"repetition-with-loops",level:3}],u={toc:p},m="wrapper";function c(e){let{components:t,...n}=e;return(0,r.kt)(m,(0,a.Z)({},u,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"3---common-programming-concepts"},"3 - Common Programming Concepts"),(0,r.kt)("p",null,"In which we learn about variables, basic types, functions, comments, and control flow."),(0,r.kt)("h2",{id:"31---variables-and-mutability"},"3.1 - Variables and Mutability"),(0,r.kt)("p",null,"Variables are declared with the ",(0,r.kt)("inlineCode",{parentName:"p"},"let")," keyword. By default, variables are immutable unless they are declared ",(0,r.kt)("inlineCode",{parentName:"p"},"mut"),". This program will fail to compile with the error ",(0,r.kt)("inlineCode",{parentName:"p"},"cannot assign twice to immutable variable `x` "),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"fn main() {\n let x = 5;\n x = 6; // This will error!\n\n let mut y = 5;\n y = 6; // This is okay.\n}\n")),(0,r.kt)("p",null,"Immutability in Rust is similar to ",(0,r.kt)("inlineCode",{parentName:"p"},"const")," in JavaScript, or ",(0,r.kt)("inlineCode",{parentName:"p"},"final")," in Java. The value the reference points to can't be modified (mostly):"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'fn main() {\n let foo = String::from("foo");\n foo.clear(); // This will error!\n}\n')),(0,r.kt)("p",null,"Here ",(0,r.kt)("inlineCode",{parentName:"p"},"clear")," will try to empty the string, but will fail with the error ",(0,r.kt)("inlineCode",{parentName:"p"},"cannot borrow `foo` as mutable, as it is not declared as mutable"),". If you go look at the source code for the ",(0,r.kt)("inlineCode",{parentName:"p"},"clear")," method it is defined as requiring ",(0,r.kt)("inlineCode",{parentName:"p"},"self")," to be a mutable reference (",(0,r.kt)("inlineCode",{parentName:"p"},"self")," is a bit like ",(0,r.kt)("inlineCode",{parentName:"p"},"this")," in other languages)."),(0,r.kt)("p",null,"Variables cannot be declared at the global scope ",(0,r.kt)("a",{parentName:"p",href:"#static-variables"},"unless they are ",(0,r.kt)("inlineCode",{parentName:"a"},"static")),"."),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},'You may have noticed that that "mostly" above when we were talking about immutable variables. Immutability prevents us from directly modifying members of a struct, however in ',(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch15-smart-pointers",title:"Chapter 15: Smart Pointers"},"chapter 15")," we're going to find out how you can modify parts of immutable objects through a concept call ",(0,r.kt)("em",{parentName:"p"},"interior mutability"),", and that we can share mutable objects across multiple places in the code with smart pointers like ",(0,r.kt)("inlineCode",{parentName:"p"},"Rc")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"Arc"),". A Rust mutex is an example of an object that is immutable, but you're allowed to change the value in it if you own the lock.")),(0,r.kt)("h3",{id:"constants"},"Constants"),(0,r.kt)("p",null,"Rust also has the concept of a ",(0,r.kt)("em",{parentName:"p"},"constant")," which at first sounds a lot like an immutable variable:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;\n")),(0,r.kt)("p",null,"Constants are subtly different from immutable variables. They are stored directly in the program binary, so they cannot be ",(0,r.kt)("inlineCode",{parentName:"p"},"mut")," and the value of the constant has to be something that can be determined at compile time. The Rust Reference has a ",(0,r.kt)("a",{parentName:"p",href:"https://doc.rust-lang.org/stable/reference/const_eval.html"},"section on constant evaluation")," that lays out all the rules for what operators you're allowed to use and what you're not, but here the compiler can convert ",(0,r.kt)("inlineCode",{parentName:"p"},"60 * 60 * 3")," into ",(0,r.kt)("inlineCode",{parentName:"p"},"10800")," for us and store that in the executable."),(0,r.kt)("p",null,"Constants must always be annotated, and can be declared in the global scope."),(0,r.kt)("h3",{id:"static-variables"},"Static Variables"),(0,r.kt)("p",null,(0,r.kt)("em",{parentName:"p"},"Static variables")," or global variables are declared with the static keyword and are named in ",(0,r.kt)("inlineCode",{parentName:"p"},"SCREAMING_SNAKE_CASE"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'static HELLO_WORLD: &str = "Hello, world!";\n\nfn main() {\n println!("name is: {}", HELLO_WORLD);\n}\n')),(0,r.kt)("p",null,"Static variables can be mutable, but to access or modify them we need to talk about ",(0,r.kt)("inlineCode",{parentName:"p"},"unsafe")," code, ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch19/ch19-01-unsafe#accessing-or-modifying-a-mutable-static-variable"},"which we'll do later"),"."),(0,r.kt)("h3",{id:"shadowing"},"Shadowing"),(0,r.kt)("p",null,"As we saw in ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch02-guessing-game",title:"Chapter 2: Guessing Game"},"chapter 2"),", a variable declaration can shadow another:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'fn main() {\n let x = 5;\n\n let x = x + 1;\n\n {\n let x = x * 2;\n println!("The value of x in the inner scope is: {x}");\n }\n\n println!("The value of x is: {x}");\n}\n')),(0,r.kt)("p",null,'There are a total of three variables in this function, all named "x". Variables last until the end of the block they were declared in, so this program prints out:'),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-txt"},"The value of x in the inner scope is: 12\nThe value of x is: 6\n")),(0,r.kt)("p",null,"When shadowing a variable, the new variable does not have to have the same type as the one it is shadowing."),(0,r.kt)("h2",{id:"32---data-types"},"3.2 - Data Types"),(0,r.kt)("p",null,"Keep in mind that Rust is a statically typed language, so the type of every variable (and how much space it will occupy in memory, if it is stored on the stack) must be known at compile time. Rust's type inference is amazing, so frequently we don't have to tell Rust what type a variable is, but sometimes a variable's type is ambiguous in which case we need to ",(0,r.kt)("em",{parentName:"p"},"annotate")," it (e.g. ",(0,r.kt)("inlineCode",{parentName:"p"},"let guess: 32 = ..."),")."),(0,r.kt)("p",null,'A "scalar type" represents a single value. There are four kinds of scalar types in Rust: integers, floating-point numbers, Booleans, and characters.'),(0,r.kt)("h3",{id:"integer-types"},"Integer Types"),(0,r.kt)("p",null,"Integer types:"),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"Length (bits)"),(0,r.kt)("th",{parentName:"tr",align:null},"Signed"),(0,r.kt)("th",{parentName:"tr",align:null},"Unsigned"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"8"),(0,r.kt)("td",{parentName:"tr",align:null},"i8"),(0,r.kt)("td",{parentName:"tr",align:null},"u8")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"16"),(0,r.kt)("td",{parentName:"tr",align:null},"i16"),(0,r.kt)("td",{parentName:"tr",align:null},"u16")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"32"),(0,r.kt)("td",{parentName:"tr",align:null},"i32"),(0,r.kt)("td",{parentName:"tr",align:null},"u32")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"64"),(0,r.kt)("td",{parentName:"tr",align:null},"i64"),(0,r.kt)("td",{parentName:"tr",align:null},"u64")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"128"),(0,r.kt)("td",{parentName:"tr",align:null},"i128"),(0,r.kt)("td",{parentName:"tr",align:null},"u128")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"arch"),(0,r.kt)("td",{parentName:"tr",align:null},"isize"),(0,r.kt)("td",{parentName:"tr",align:null},"usize")))),(0,r.kt)("p",null,"Signed integers are stored using ",(0,r.kt)("a",{parentName:"p",href:"https://en.wikipedia.org/wiki/Two%27s_complement"},"two's complement"),". ",(0,r.kt)("inlineCode",{parentName:"p"},"isize")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"usize")," depend on your architecture, so they'll be 32 bit numbers on a 32 bit architecture, or 64 bit on a 64 bit architecture."),(0,r.kt)("p",null,"Integer literals can be written using any of the methods below. Integer literals in Rust can use an ",(0,r.kt)("inlineCode",{parentName:"p"},"_"),' as a visual separator (similar to how we might write "1,000" in English, we can write "1_000" in Rust).'),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"Number literals"),(0,r.kt)("th",{parentName:"tr",align:null},"Example"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"Decimal"),(0,r.kt)("td",{parentName:"tr",align:null},"98_222")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"Hex"),(0,r.kt)("td",{parentName:"tr",align:null},"0xff")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"Octal"),(0,r.kt)("td",{parentName:"tr",align:null},"Oo77")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"Binary"),(0,r.kt)("td",{parentName:"tr",align:null},"0b1111_0000")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"Byte (u8)"),(0,r.kt)("td",{parentName:"tr",align:null},"b'A'")))),(0,r.kt)("p",null,"If you try to overflow an integer (e.g. you try to store 256 in a u8), what happens (by default) depends on whether you compiled your program with ",(0,r.kt)("inlineCode",{parentName:"p"},"--release")," or not. In debug mode Rust adds runtime checks to ensure you don't overflow a value, so your program will panic and crash (see ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch09-error-handling",title:"Chapter 9: Error Handling"},"chapter 9")," for more about panics). With the --release flag, the integer will overflow as you would expect it to in another language like C or Java (the largest value a u8 can hold is 255, so 256 wraps to 0)."),(0,r.kt)("p",null,"The standard library has functions that let you explicitly define how you want to handle overflows if you don't want to panic. For example ",(0,r.kt)("a",{parentName:"p",href:"https://doc.rust-lang.org/std/intrinsics/fn.wrapping_add.html"},(0,r.kt)("inlineCode",{parentName:"a"},"wrapping_add"))," will add two numbers and let them wrap around. There are ",(0,r.kt)("inlineCode",{parentName:"p"},"wrapping_*"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"checked_*"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"overflowing_*"),", and ",(0,r.kt)("inlineCode",{parentName:"p"},"saturating_*")," functions for integer arithmetic."),(0,r.kt)("admonition",{type:"tip"},(0,r.kt)("p",{parentName:"admonition"},"We can change how overflows are handled at runtime for development and release through ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch14-more-about-cargo#141---customizing-builds-with-release-profiles"},"release profiles"),".")),(0,r.kt)("h3",{id:"floating-point-types"},"Floating-Point Types"),(0,r.kt)("p",null,"There are two floating point types, ",(0,r.kt)("inlineCode",{parentName:"p"},"f64")," (the default) and ",(0,r.kt)("inlineCode",{parentName:"p"},"f32"),". Floating-point numbers are stored using the IEEE-754 standard."),(0,r.kt)("h3",{id:"number-operators"},"Number Operators"),(0,r.kt)("p",null,"Rust has the operators you'd expect: ",(0,r.kt)("inlineCode",{parentName:"p"},"+"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"-"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"*"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"/"),", and ",(0,r.kt)("inlineCode",{parentName:"p"},"%")," for modulus. See ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/zz-appendix/appendix-02-operators"},"the Rust Book Appendix B")," for a complete list of all the operators in Rust."),(0,r.kt)("h3",{id:"boolean-type"},"Boolean type"),(0,r.kt)("p",null,"Booleans are of type ",(0,r.kt)("inlineCode",{parentName:"p"},"bool")," and can be ",(0,r.kt)("inlineCode",{parentName:"p"},"true")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"false"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"let t = true;\nlet f: bool = false;\n")),(0,r.kt)("h3",{id:"character-type"},"Character Type"),(0,r.kt)("p",null,"A ",(0,r.kt)("inlineCode",{parentName:"p"},"char")," in Rust is a four-byte unicode scalar value."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"let c = 'z';\nlet z: char = '\u2124';\nlet heart_eyed_cat = '\ud83d\ude3b';\nlet space_woman_zwj = '\ud83d\udc69\ud83c\udffb\u200d\ud83d\ude80'; // <== This doesn't work!\n")),(0,r.kt)("p",null,"That last example doesn't work. Our female astronaut friend might look like a single character, but she's actually two emoji joined together with a zero-width-joiner (ZWJ). We'll talk a lot more about UTF-8 and Unicode in ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch08-common-collections",title:"Chapter 8: Common Collections"},"chapter 8"),"."),(0,r.kt)("h3",{id:"str-and-string"},(0,r.kt)("inlineCode",{parentName:"h3"},"&str")," and ",(0,r.kt)("inlineCode",{parentName:"h3"},"String")),(0,r.kt)("p",null,"You'll see two different string types in Rust: ",(0,r.kt)("inlineCode",{parentName:"p"},"str")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"String"),". ",(0,r.kt)("inlineCode",{parentName:"p"},"String")," is similar to a ",(0,r.kt)("inlineCode",{parentName:"p"},"Vector")," - it's a data type that stores a list of characters in a variable-length chunk of memory on the heap. Any time you accept input from the user or read a string from a file, it's going to end up in a ",(0,r.kt)("inlineCode",{parentName:"p"},"String"),"."),(0,r.kt)("p",null,"The type ",(0,r.kt)("inlineCode",{parentName:"p"},"&str")," (almost always seen in it's borrowed form) is also known as a ",(0,r.kt)("em",{parentName:"p"},"string slice")," (which we'll learn more about in ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch04-ownership",title:"Chapter 4: Ownership, References, and Slices"},"the next chapter"),"), and is both a pointer to the string's data and a length for the string. Any string literal in Rust is a ",(0,r.kt)("inlineCode",{parentName:"p"},"&str"),", since the actual string is stored somewhere in the executable and we just have an immutable reference to it. A string slice can be used as a view into a ",(0,r.kt)("inlineCode",{parentName:"p"},"String"),"."),(0,r.kt)("h2",{id:"compound-types"},"Compound Types"),(0,r.kt)("p",null,"Compound types group multiple values into one type. Rust has two primitive compound types, the ",(0,r.kt)("em",{parentName:"p"},"tuple")," and the ",(0,r.kt)("em",{parentName:"p"},"array"),"."),(0,r.kt)("h3",{id:"tuple-type"},"Tuple Type"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"let tup: (i32, f64, u8) = (800, 6.4, 1);\n\n// Destructuring assignment\nlet (x, y, z) = tup;\n\n// Access individual elements\nlet a = tup.0;\nlet b = tup.1;\nlet c = tup.2;\n")),(0,r.kt)("p",null,"An empty tuple is written ",(0,r.kt)("inlineCode",{parentName:"p"},"()"),' and is called a "unit". This represents an empty value or an empty return type. Functions without a return type implicitly return this.'),(0,r.kt)("h3",{id:"array-type"},"Array Type"),(0,r.kt)("p",null,'Every element in an array must be the same type, and arrays must be fixed length. If you\'re looking for a "variable length" array, you want a vector from the standard library (see ',(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch08-common-collections",title:"Chapter 8: Common Collections"},"Chapter 8"),"). If you declare a variable as an array in a function, then the contents of that variable will end up on the stack, while for a vector contents will end up on the heap. (Although you can put the contents of an array on the heap by using a smart pointer like a ",(0,r.kt)("inlineCode",{parentName:"p"},"Box")," - see ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch15-smart-pointers",title:"Chapter 15: Smart Pointers"},"chapter 15"),")."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"let a = [1, 2, 3, 4, 5];\n\n// Destructuring assignment. Must use all elements!\nlet [x, y, z, _, _] = a;\n\n// Access individual elements\nlet first = a[0];\nlet second = a[1];\n\n// Create array of type i32, length 5.\nlet b: [i32; 5] = [1, 2, 3, 4, 5];\n\n// Create array of five zeros.\nlet c = [0; 5]\n")),(0,r.kt)("p",null,"Array accesses are checked at runtime. Trying to access an index which is out-of-bounds will cause a panic."),(0,r.kt)("p",null,"If you're coming to Rust from JavaScript, it's worth pointing out that JavaScript \"arrays\" are not quite like arrays in any other programming language. The Rust ",(0,r.kt)("inlineCode",{parentName:"p"},"Vec")," type, or ",(0,r.kt)("em",{parentName:"p"},"vector"),", is much closer to a JavaScript array than a Rust array is. We'll talk about vectors in ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch08-common-collections",title:"Chapter 8: Common Collections"},"chapter 8"),"."),(0,r.kt)("h3",{id:"struct-type"},(0,r.kt)("inlineCode",{parentName:"h3"},"struct")," type"),(0,r.kt)("p",null,"We can define our own compound types using the ",(0,r.kt)("inlineCode",{parentName:"p"},"struct")," keyword:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"struct User {\n name: String,\n age: u32,\n}\n")),(0,r.kt)("h2",{id:"33---functions"},"3.3 - Functions"),(0,r.kt)("p",null,"Functions are defined by ",(0,r.kt)("inlineCode",{parentName:"p"},"fn")," keyword. Parameters are required to have a type annotation, and are annotated with ",(0,r.kt)("inlineCode",{parentName:"p"},": type")," just like variables (and just like in Typescript)."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'fn another_function(x: i32, y: i32) {\n println!("The point is at: {x}, {y}");\n}\n')),(0,r.kt)("p",null,"If you end a function with an expression instead of a statement, then the function will return the value of that expression. Return types must be explicitly declared with an arrow (",(0,r.kt)("inlineCode",{parentName:"p"},"->"),")."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"// Returns 1\nfn implicit_return() -> i32 {\n 1\n}\n\n// Also returns 1, but using `return` is not\n// idiomatic in Rust unless you want to return\n// from the middle of a function.\nfn explicit_return() -> i32 {\n return 1;\n}\n\n// The semicolon makes this a statement instead\n// of an expression, so this returns `()`.\nfn no_return() {\n 1;\n}\n")),(0,r.kt)("p",null,"Assignments are always statements (i.e. ",(0,r.kt)("inlineCode",{parentName:"p"},"let x = 6")," does not evaluate to 6), as are function definitions (i.e. you can't do ",(0,r.kt)("inlineCode",{parentName:"p"},"let x = fn foo() {}"),"). Functions can be called before they are defined. In ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch10/ch10-01-generic-data-types",title:"Chapter 10: Generic Types, Traits, and Lifetimes"},"chapter 10")," we'll learn about using generics with functions."),(0,r.kt)("p",null,"Rust also has closures, which are inline functions that can be assigned to variables or passed as parameters. We'll learn about them in detail in ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch13-functional-language-features",title:"Chapter 13: Functional Language Features: Iterators and Closures"},"chapter 13"),", but the syntax is:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"let my_closure = |param1, param2| { /* function body goes here */ };\n")),(0,r.kt)("h2",{id:"34---comments"},"3.4 - Comments"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"// This is a comment. Multi-line comments\n// generally are written this way.\n\n/* You can use this style of comment too. */\n\n/// This is a doc comment - see chapter 14.\n")),(0,r.kt)("h2",{id:"35---control-flow"},"3.5 - Control Flow"),(0,r.kt)("h3",{id:"if-expression"},"if Expression"),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"if")," statements don't have braces around the condition, (much like Go, and much unlike Java, JavaScript, or C):"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'if number < 5 {\n println!("less than 5");\n} else if number > 10 {\n println!("greater than 10");\n} else {\n println!("greater than 4, less than 11");\n}\n')),(0,r.kt)("p",null,"Note that ",(0,r.kt)("inlineCode",{parentName:"p"},"if"),' can be used as an expression. In this case each "arm" of the if must be the same type:'),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'// This is OK\nlet number = if condition { 5 } else { 6 };\n\n// This breaks! `if` and `else` have\n// incompatible types\nlet wat = if condition { 5 } else { "six" };\n\n// But this is OK.\nloop {\n let wat = if condition { 5 } else { break };\n}\n')),(0,r.kt)("h3",{id:"repetition-with-loops"},"Repetition with Loops"),(0,r.kt)("p",null,"Rust has three kinds of loops: ",(0,r.kt)("inlineCode",{parentName:"p"},"loop"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"while"),", and ",(0,r.kt)("inlineCode",{parentName:"p"},"for"),". The ",(0,r.kt)("inlineCode",{parentName:"p"},"break")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"continue")," statements work exactly as they do in other languages: ",(0,r.kt)("inlineCode",{parentName:"p"},"break")," will stop the loop, and ",(0,r.kt)("inlineCode",{parentName:"p"},"continue")," will stop execution of the current iteration and start the next one. Note that loops can be used as expressions."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"loop {\n println!(\"Infinite loop!\")\n}\n\n// Loops can be used as expressions, with `break`\n// returning the value from the block.\nlet mut counter = 0;\nlet x = loop {\n counter += 1;\n if counter == 10 {\n break counter * 2;\n }\n};\n\n// Loops can be labeled with a single quote\n// followed by the label and the a colon.\n'outer: loop {\n 'inner: loop {\n break 'outer;\n }\n}\n\n// A while loop looks a lot like a\n// while loop in every other language.\nlet mut number = 0;\nwhile number < 10 {\n number++;\n}\n")),(0,r.kt)("p",null,"For loops in Rust are always of the format ",(0,r.kt)("inlineCode",{parentName:"p"},"for [var] in [iterator] {}"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'// Iterate over an array\nlet a = [1, 2, 3, 4, 5];\nfor element in a {\n println!("value is {element}");\n}\n\n// Count from 1 to 5\nfor element in (1..6) {\n println!("value is {element}");\n}\n')),(0,r.kt)("p",null,"We'll see more about iterators in ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch13-functional-language-features",title:"Chapter 13: Functional Language Features: Iterators and Closures"},"chapter 13"),"."),(0,r.kt)("p",null,"Now that we know some basics, it's time to learn about ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch04-ownership",title:"Chapter 4: Ownership, References, and Slices"},"ownership"),"."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/935f2afb.ff231fb8.js b/assets/js/935f2afb.e94cfbb0.js similarity index 56% rename from assets/js/935f2afb.ff231fb8.js rename to assets/js/935f2afb.e94cfbb0.js index 1b7b30b..83c2f93 100644 --- a/assets/js/935f2afb.ff231fb8.js +++ b/assets/js/935f2afb.e94cfbb0.js @@ -1 +1 @@ -"use strict";(self.webpackChunkrust_book_abridged=self.webpackChunkrust_book_abridged||[]).push([[53],{1109:e=>{e.exports=JSON.parse('{"pluginId":"default","version":"current","label":"Next","banner":null,"badge":false,"noIndex":false,"className":"docs-version-current","isLast":true,"docsSidebars":{"tutorialSidebar":[{"type":"link","label":"The Rust Book (Abridged)","href":"/rust-book-abridged/","docId":"ch00-intro"},{"type":"link","label":"1 - Getting Started","href":"/rust-book-abridged/ch01-getting-started","docId":"ch01-getting-started"},{"type":"link","label":"2 - Programming a Guessing Game","href":"/rust-book-abridged/ch02-guessing-game","docId":"ch02-guessing-game"},{"type":"link","label":"3 - Common Programming Concepts","href":"/rust-book-abridged/ch03-common-programming-concepts","docId":"ch03-common-programming-concepts"},{"type":"link","label":"4 - Ownership, References, and Slices","href":"/rust-book-abridged/ch04-ownership","docId":"ch04-ownership"},{"type":"link","label":"5 - Using Structs to Structure Related Data","href":"/rust-book-abridged/ch05-structs","docId":"ch05-structs"},{"type":"link","label":"6 - Enums and Pattern Matching","href":"/rust-book-abridged/ch06-enums-and-pattern-matching","docId":"ch06-enums-and-pattern-matching"},{"type":"link","label":"7 - Managing Growing Projects with Packages, Crates, and Modules","href":"/rust-book-abridged/ch07-packages-crates-modules","docId":"ch07-packages-crates-modules"},{"type":"link","label":"8 - Common Collections","href":"/rust-book-abridged/ch08-common-collections","docId":"ch08-common-collections"},{"type":"link","label":"9 - Error Handling","href":"/rust-book-abridged/ch09-error-handling","docId":"ch09-error-handling"},{"type":"category","label":"10 - Generic Types, Traits, and Lifetimes","collapsible":true,"collapsed":false,"items":[{"type":"link","label":"10.1 - Generic Data Types","href":"/rust-book-abridged/ch10/ch10-01-generic-data-types","docId":"ch10/ch10-01-generic-data-types"},{"type":"link","label":"10.2 - Traits: Defining Shared Behavior","href":"/rust-book-abridged/ch10/ch10-02-traits","docId":"ch10/ch10-02-traits"},{"type":"link","label":"10.3 - Validating References with Lifetimes","href":"/rust-book-abridged/ch10/ch10-03-lifetimes","docId":"ch10/ch10-03-lifetimes"}],"href":"/rust-book-abridged/category/10---generic-types-traits-and-lifetimes"},{"type":"link","label":"11 - Writing Automated Tests","href":"/rust-book-abridged/ch11-automated-tests","docId":"ch11-automated-tests"},{"type":"link","label":"12 - An I/O Project: Building a Command Line Program","href":"/rust-book-abridged/ch12-io-project-cli","docId":"ch12-io-project-cli"},{"type":"link","label":"13 - Functional Language Features: Iterators and Closures","href":"/rust-book-abridged/ch13-functional-language-features","docId":"ch13-functional-language-features"},{"type":"link","label":"14 - More about Cargo and Crates","href":"/rust-book-abridged/ch14-more-about-cargo","docId":"ch14-more-about-cargo"},{"type":"link","label":"15 - Smart Pointers","href":"/rust-book-abridged/ch15-smart-pointers","docId":"ch15-smart-pointers"},{"type":"link","label":"16 - Fearless Concurrency","href":"/rust-book-abridged/ch16-fearless-concurrency","docId":"ch16-fearless-concurrency"},{"type":"link","label":"17 - Object Oriented Features of Rust","href":"/rust-book-abridged/ch17-object-oriented-features","docId":"ch17-object-oriented-features"},{"type":"link","label":"18 - Patterns and Matching","href":"/rust-book-abridged/ch18-patterns-and-matching","docId":"ch18-patterns-and-matching"},{"type":"category","label":"19 - Advanced Features","collapsible":true,"collapsed":false,"items":[{"type":"link","label":"19.1 - Unsafe Rust","href":"/rust-book-abridged/ch19/ch19-01-unsafe","docId":"ch19/ch19-01-unsafe"},{"type":"link","label":"19.2 - Advanced Traits","href":"/rust-book-abridged/ch19/ch19-02-advanced-traits","docId":"ch19/ch19-02-advanced-traits"},{"type":"link","label":"19.3 - Advanced Types","href":"/rust-book-abridged/ch19/ch19-03-advanced-types","docId":"ch19/ch19-03-advanced-types"},{"type":"link","label":"19.4 - Advanced Functions and Closures","href":"/rust-book-abridged/ch19/ch19-04-advanced-functions-and-closures","docId":"ch19/ch19-04-advanced-functions-and-closures"},{"type":"link","label":"19.5 - Macros","href":"/rust-book-abridged/ch19/ch19-05-macros","docId":"ch19/ch19-05-macros"}],"href":"/rust-book-abridged/category/19---advanced-features"},{"type":"category","label":"20 - Multithreaded Web Server","collapsible":true,"collapsed":false,"items":[{"type":"link","label":"20.1 - Building a Single-Threaded Web Server","href":"/rust-book-abridged/ch20/ch20-01-single-threaded-web-server","docId":"ch20/ch20-01-single-threaded-web-server"},{"type":"link","label":"20.2 - Turning Our Single-Threaded Server into a Multithreaded Server","href":"/rust-book-abridged/ch20/ch20-02-multi-threaded-web-server","docId":"ch20/ch20-02-multi-threaded-web-server"},{"type":"link","label":"20.3 - Graceful Shutdown and Cleanup","href":"/rust-book-abridged/ch20/ch20-03-graceful-shutdown","docId":"ch20/ch20-03-graceful-shutdown"}],"href":"/rust-book-abridged/category/20---multithreaded-web-server"},{"type":"link","label":"21 - Async Programming","href":"/rust-book-abridged/ch21-async","docId":"ch21-async"},{"type":"category","label":"Appendix","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Appendix A: Keywords","href":"/rust-book-abridged/zz-appendix/appendix-01-keywords","docId":"zz-appendix/appendix-01-keywords"},{"type":"link","label":"Appendix B: Operators and Symbols","href":"/rust-book-abridged/zz-appendix/appendix-02-operators","docId":"zz-appendix/appendix-02-operators"},{"type":"link","label":"Appendix C: Derivable Traits","href":"/rust-book-abridged/zz-appendix/appendix-03-derivable-traits","docId":"zz-appendix/appendix-03-derivable-traits"},{"type":"link","label":"Appendix D - Useful Development Tools","href":"/rust-book-abridged/zz-appendix/appendix-04-useful-development-tools","docId":"zz-appendix/appendix-04-useful-development-tools"},{"type":"link","label":"Appendix E - Editions","href":"/rust-book-abridged/zz-appendix/appendix-05-editions","docId":"zz-appendix/appendix-05-editions"},{"type":"link","label":"Licenses","href":"/rust-book-abridged/zz-appendix/appendix-06-licenses","docId":"zz-appendix/appendix-06-licenses"}],"href":"/rust-book-abridged/category/appendix"}]},"docs":{"ch00-intro":{"id":"ch00-intro","title":"The Rust Book (Abridged)","description":"The Rust Book (Abridged)","sidebar":"tutorialSidebar"},"ch01-getting-started":{"id":"ch01-getting-started","title":"1 - Getting Started","description":"This chapter is going to get Rust installed, and explain how to use cargo to create and build a new project.","sidebar":"tutorialSidebar"},"ch02-guessing-game":{"id":"ch02-guessing-game","title":"2 - Programming a Guessing Game","description":"This chapter creates a little \\"guessing game\\" program. The program picks a random number, you try to guess the secret number, and the program will tell you if you\'re too high or too low. Hours of fun! We\'re going to introduce a bunch of concepts but not go into anything in too much detail in this chapter.","sidebar":"tutorialSidebar"},"ch03-common-programming-concepts":{"id":"ch03-common-programming-concepts","title":"3 - Common Programming Concepts","description":"In which we learn about variables, basic types, functions, comments, and control flow.","sidebar":"tutorialSidebar"},"ch04-ownership":{"id":"ch04-ownership","title":"4 - Ownership, References, and Slices","description":"Ownership is Rust\'s most unique feature and has deep implications for the rest of the language. It enables Rust to make memory safety guarantees without needing a garbage collector, so it\'s important to understand how ownership works.","sidebar":"tutorialSidebar"},"ch05-structs":{"id":"ch05-structs","title":"5 - Using Structs to Structure Related Data","description":"5.1 - Defining and Instantiating Structs","sidebar":"tutorialSidebar"},"ch06-enums-and-pattern-matching":{"id":"ch06-enums-and-pattern-matching","title":"6 - Enums and Pattern Matching","description":"6.1 - Defining an Enum","sidebar":"tutorialSidebar"},"ch07-packages-crates-modules":{"id":"ch07-packages-crates-modules","title":"7 - Managing Growing Projects with Packages, Crates, and Modules","description":"So far all of our examples have lived in a single file, but almost any non-trivial program would be too large to fit in a single file. Rust provides a number of tools to help us organize a project:","sidebar":"tutorialSidebar"},"ch08-common-collections":{"id":"ch08-common-collections","title":"8 - Common Collections","description":"Rust\'s standard library includes a number of collections which store data on the heap:","sidebar":"tutorialSidebar"},"ch09-error-handling":{"id":"ch09-error-handling","title":"9 - Error Handling","description":"Rust has a tiered error-handling scheme:","sidebar":"tutorialSidebar"},"ch10/ch10-01-generic-data-types":{"id":"ch10/ch10-01-generic-data-types","title":"10.1 - Generic Data Types","description":"In Function Definitions","sidebar":"tutorialSidebar"},"ch10/ch10-02-traits":{"id":"ch10/ch10-02-traits","title":"10.2 - Traits: Defining Shared Behavior","description":"A trait in Rust is very similar to what most other languages call an interface. A trait defines some set of behavior, and every struct that implements the trait needs to implement that behavior.","sidebar":"tutorialSidebar"},"ch10/ch10-03-lifetimes":{"id":"ch10/ch10-03-lifetimes","title":"10.3 - Validating References with Lifetimes","description":"TODO: This section needs some rework. If you want to get deep into how lifetimes work from the compiler\'s perspective, this is a good read.","sidebar":"tutorialSidebar"},"ch11-automated-tests":{"id":"ch11-automated-tests","title":"11 - Writing Automated Tests","description":"11.1 - How to Write Tests","sidebar":"tutorialSidebar"},"ch12-io-project-cli":{"id":"ch12-io-project-cli","title":"12 - An I/O Project: Building a Command Line Program","description":"We know enough Rust now that we can actually write a useful program. We\'re going to make a copy of the Linux grep command. If you\'re a Windows user, or you\'re not much of a command-line person, the grep command basically works like this:","sidebar":"tutorialSidebar"},"ch13-functional-language-features":{"id":"ch13-functional-language-features","title":"13 - Functional Language Features: Iterators and Closures","description":"In this chapter we will cover closures, which are a like functions you can assign to variables or pass around as parameters. We\'ll also learn about iterators which are used for iterating over a collection of items.","sidebar":"tutorialSidebar"},"ch14-more-about-cargo":{"id":"ch14-more-about-cargo","title":"14 - More about Cargo and Crates","description":"14.1 - Customizing Builds with Release Profiles","sidebar":"tutorialSidebar"},"ch15-smart-pointers":{"id":"ch15-smart-pointers","title":"15 - Smart Pointers","description":"In C++, whenever we want to store an object on the heap, we new that object to allocate some memory. At some later point in time, we have to delete that memory. This is much like malloc and free in standard C.","sidebar":"tutorialSidebar"},"ch16-fearless-concurrency":{"id":"ch16-fearless-concurrency","title":"16 - Fearless Concurrency","description":"Concurrent programming has a lot of potential pit falls - race conditions, thread safe access to variables - in other languages these problems show up in production as tricky to reproduce problems. Access to memory is handled through Rust\'s type system and ownership rules, and it turns out these rules can do an excellent job of catching many concurrency problems at compile time too.","sidebar":"tutorialSidebar"},"ch17-object-oriented-features":{"id":"ch17-object-oriented-features","title":"17 - Object Oriented Features of Rust","description":"17.1 - Characteristics of Object Oriented Languages","sidebar":"tutorialSidebar"},"ch18-patterns-and-matching":{"id":"ch18-patterns-and-matching","title":"18 - Patterns and Matching","description":"18.1 - All the Places Patterns Can Be Used","sidebar":"tutorialSidebar"},"ch19/ch19-01-unsafe":{"id":"ch19/ch19-01-unsafe","title":"19.1 - Unsafe Rust","description":"This chapter is meant as an introduction to unsafe code, but if you find yourself actually writing unsafe code, it would be a good idea to read through the Rustonomicon. There are many things you can do in unsafe code that will result in undefined behavior, some of which you might surprise you if you\'re coming from a language like C/C++.","sidebar":"tutorialSidebar"},"ch19/ch19-02-advanced-traits":{"id":"ch19/ch19-02-advanced-traits","title":"19.2 - Advanced Traits","description":"For an introduction to traits, see chapter 10.","sidebar":"tutorialSidebar"},"ch19/ch19-03-advanced-types":{"id":"ch19/ch19-03-advanced-types","title":"19.3 - Advanced Types","description":"Using the Newtype Pattern for Type Safety and Abstraction","sidebar":"tutorialSidebar"},"ch19/ch19-04-advanced-functions-and-closures":{"id":"ch19/ch19-04-advanced-functions-and-closures","title":"19.4 - Advanced Functions and Closures","description":"Function Pointers","sidebar":"tutorialSidebar"},"ch19/ch19-05-macros":{"id":"ch19/ch19-05-macros","title":"19.5 - Macros","description":"If you\'re coming to Rust from C or C++, then you\'re no doubt already familiar with macros. We\'re going to give a quick introduction to macros here, but if you want to read more you should check out The Little Book of Rust Macros.","sidebar":"tutorialSidebar"},"ch20/ch20-01-single-threaded-web-server":{"id":"ch20/ch20-01-single-threaded-web-server","title":"20.1 - Building a Single-Threaded Web Server","description":"In this chapter we\'re going to build a simple HTTP server to put together a number of things we\'ve learned so far. As usual, the code for this project is available in the GitHub repo.","sidebar":"tutorialSidebar"},"ch20/ch20-02-multi-threaded-web-server":{"id":"ch20/ch20-02-multi-threaded-web-server","title":"20.2 - Turning Our Single-Threaded Server into a Multithreaded Server","description":"Simulating a Slow Request in the Current Server Implementation","sidebar":"tutorialSidebar"},"ch20/ch20-03-graceful-shutdown":{"id":"ch20/ch20-03-graceful-shutdown","title":"20.3 - Graceful Shutdown and Cleanup","description":"Right now when we hit CTRL-C to send an interrupt signal to our web server, it stops running, but it also stops any in-flight requests. Let\'s see if we can get our server to shut down gracefully.","sidebar":"tutorialSidebar"},"ch21-async":{"id":"ch21-async","title":"21 - Async Programming","description":"In this section we\'re going to re-implement our web server from chapter 20 using async functions. We\'re just going to give you enough here to get your feet wet. For further reading, check out Asynchronous Programming in Rust, and the Tokio Tutorial. As usual, if you\'re looking for the full source for this project, it\'s in the GitHub repo.","sidebar":"tutorialSidebar"},"zz-appendix/appendix-01-keywords":{"id":"zz-appendix/appendix-01-keywords","title":"Appendix A: Keywords","description":"This appendix was copied directly from \\"The Rust Programming Language\\".","sidebar":"tutorialSidebar"},"zz-appendix/appendix-02-operators":{"id":"zz-appendix/appendix-02-operators","title":"Appendix B: Operators and Symbols","description":"This appendix was copied directly from \\"The Rust Programming Language\\".","sidebar":"tutorialSidebar"},"zz-appendix/appendix-03-derivable-traits":{"id":"zz-appendix/appendix-03-derivable-traits","title":"Appendix C: Derivable Traits","description":"This appendix was copied directly from \\"The Rust Programming Language\\".","sidebar":"tutorialSidebar"},"zz-appendix/appendix-04-useful-development-tools":{"id":"zz-appendix/appendix-04-useful-development-tools","title":"Appendix D - Useful Development Tools","description":"This appendix was copied directly from \\"The Rust Programming Language\\".","sidebar":"tutorialSidebar"},"zz-appendix/appendix-05-editions":{"id":"zz-appendix/appendix-05-editions","title":"Appendix E - Editions","description":"This appendix was copied directly from \\"The Rust Programming Language\\".","sidebar":"tutorialSidebar"},"zz-appendix/appendix-06-licenses":{"id":"zz-appendix/appendix-06-licenses","title":"Licenses","description":"Ferris the Crab was created by Karen Rustad T\xf6lva, and is used here under public domain.","sidebar":"tutorialSidebar"}}}')}}]); \ No newline at end of file +"use strict";(self.webpackChunkrust_book_abridged=self.webpackChunkrust_book_abridged||[]).push([[53],{1109:e=>{e.exports=JSON.parse('{"pluginId":"default","version":"current","label":"Next","banner":null,"badge":false,"noIndex":false,"className":"docs-version-current","isLast":true,"docsSidebars":{"tutorialSidebar":[{"type":"link","label":"The Rust Book (Abridged)","href":"/rust-book-abridged/","docId":"ch00-intro"},{"type":"link","label":"1 - Getting Started","href":"/rust-book-abridged/ch01-getting-started","docId":"ch01-getting-started"},{"type":"link","label":"2 - Programming a Guessing Game","href":"/rust-book-abridged/ch02-guessing-game","docId":"ch02-guessing-game"},{"type":"link","label":"3 - Common Programming Concepts","href":"/rust-book-abridged/ch03-common-programming-concepts","docId":"ch03-common-programming-concepts"},{"type":"link","label":"4 - Ownership, References, and Slices","href":"/rust-book-abridged/ch04-ownership","docId":"ch04-ownership"},{"type":"link","label":"5 - Using Structs to Structure Related Data","href":"/rust-book-abridged/ch05-structs","docId":"ch05-structs"},{"type":"link","label":"6 - Enums and Pattern Matching","href":"/rust-book-abridged/ch06-enums-and-pattern-matching","docId":"ch06-enums-and-pattern-matching"},{"type":"link","label":"7 - Managing Growing Projects with Packages, Crates, and Modules","href":"/rust-book-abridged/ch07-packages-crates-modules","docId":"ch07-packages-crates-modules"},{"type":"link","label":"8 - Common Collections","href":"/rust-book-abridged/ch08-common-collections","docId":"ch08-common-collections"},{"type":"link","label":"9 - Error Handling","href":"/rust-book-abridged/ch09-error-handling","docId":"ch09-error-handling"},{"type":"category","label":"10 - Generic Types, Traits, and Lifetimes","collapsible":true,"collapsed":false,"items":[{"type":"link","label":"10.1 - Generic Data Types","href":"/rust-book-abridged/ch10/ch10-01-generic-data-types","docId":"ch10/ch10-01-generic-data-types"},{"type":"link","label":"10.2 - Traits: Defining Shared Behavior","href":"/rust-book-abridged/ch10/ch10-02-traits","docId":"ch10/ch10-02-traits"},{"type":"link","label":"10.3 - Validating References with Lifetimes","href":"/rust-book-abridged/ch10/ch10-03-lifetimes","docId":"ch10/ch10-03-lifetimes"}],"href":"/rust-book-abridged/category/10---generic-types-traits-and-lifetimes"},{"type":"link","label":"11 - Writing Automated Tests","href":"/rust-book-abridged/ch11-automated-tests","docId":"ch11-automated-tests"},{"type":"link","label":"12 - An I/O Project: Building a Command Line Program","href":"/rust-book-abridged/ch12-io-project-cli","docId":"ch12-io-project-cli"},{"type":"link","label":"13 - Functional Language Features: Iterators and Closures","href":"/rust-book-abridged/ch13-functional-language-features","docId":"ch13-functional-language-features"},{"type":"link","label":"14 - More about Cargo and Crates","href":"/rust-book-abridged/ch14-more-about-cargo","docId":"ch14-more-about-cargo"},{"type":"link","label":"15 - Smart Pointers","href":"/rust-book-abridged/ch15-smart-pointers","docId":"ch15-smart-pointers"},{"type":"link","label":"16 - Fearless Concurrency","href":"/rust-book-abridged/ch16-fearless-concurrency","docId":"ch16-fearless-concurrency"},{"type":"link","label":"17 - Object Oriented Features of Rust","href":"/rust-book-abridged/ch17-object-oriented-features","docId":"ch17-object-oriented-features"},{"type":"link","label":"18 - Patterns and Matching","href":"/rust-book-abridged/ch18-patterns-and-matching","docId":"ch18-patterns-and-matching"},{"type":"category","label":"19 - Advanced Features","collapsible":true,"collapsed":false,"items":[{"type":"link","label":"19.1 - Unsafe Rust","href":"/rust-book-abridged/ch19/ch19-01-unsafe","docId":"ch19/ch19-01-unsafe"},{"type":"link","label":"19.2 - Advanced Traits","href":"/rust-book-abridged/ch19/ch19-02-advanced-traits","docId":"ch19/ch19-02-advanced-traits"},{"type":"link","label":"19.3 - Advanced Types","href":"/rust-book-abridged/ch19/ch19-03-advanced-types","docId":"ch19/ch19-03-advanced-types"},{"type":"link","label":"19.4 - Advanced Functions and Closures","href":"/rust-book-abridged/ch19/ch19-04-advanced-functions-and-closures","docId":"ch19/ch19-04-advanced-functions-and-closures"},{"type":"link","label":"19.5 - Macros","href":"/rust-book-abridged/ch19/ch19-05-macros","docId":"ch19/ch19-05-macros"}],"href":"/rust-book-abridged/category/19---advanced-features"},{"type":"category","label":"20 - Multithreaded Web Server","collapsible":true,"collapsed":false,"items":[{"type":"link","label":"20.1 - Building a Single-Threaded Web Server","href":"/rust-book-abridged/ch20/ch20-01-single-threaded-web-server","docId":"ch20/ch20-01-single-threaded-web-server"},{"type":"link","label":"20.2 - Turning Our Single-Threaded Server into a Multithreaded Server","href":"/rust-book-abridged/ch20/ch20-02-multi-threaded-web-server","docId":"ch20/ch20-02-multi-threaded-web-server"},{"type":"link","label":"20.3 - Graceful Shutdown and Cleanup","href":"/rust-book-abridged/ch20/ch20-03-graceful-shutdown","docId":"ch20/ch20-03-graceful-shutdown"}],"href":"/rust-book-abridged/category/20---multithreaded-web-server"},{"type":"link","label":"21 - Async Programming","href":"/rust-book-abridged/ch21-async","docId":"ch21-async"},{"type":"category","label":"Appendix","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Appendix A: Keywords","href":"/rust-book-abridged/zz-appendix/appendix-01-keywords","docId":"zz-appendix/appendix-01-keywords"},{"type":"link","label":"Appendix B: Operators and Symbols","href":"/rust-book-abridged/zz-appendix/appendix-02-operators","docId":"zz-appendix/appendix-02-operators"},{"type":"link","label":"Appendix C: Derivable Traits","href":"/rust-book-abridged/zz-appendix/appendix-03-derivable-traits","docId":"zz-appendix/appendix-03-derivable-traits"},{"type":"link","label":"Appendix D - Useful Development Tools","href":"/rust-book-abridged/zz-appendix/appendix-04-useful-development-tools","docId":"zz-appendix/appendix-04-useful-development-tools"},{"type":"link","label":"Appendix E - Editions","href":"/rust-book-abridged/zz-appendix/appendix-05-editions","docId":"zz-appendix/appendix-05-editions"},{"type":"link","label":"Licenses","href":"/rust-book-abridged/zz-appendix/appendix-06-licenses","docId":"zz-appendix/appendix-06-licenses"}],"href":"/rust-book-abridged/category/appendix"}]},"docs":{"ch00-intro":{"id":"ch00-intro","title":"The Rust Book (Abridged)","description":"The Rust Book (Abridged)","sidebar":"tutorialSidebar"},"ch01-getting-started":{"id":"ch01-getting-started","title":"1 - Getting Started","description":"This chapter is going to get Rust installed, and explain how to use cargo to create and build a new project.","sidebar":"tutorialSidebar"},"ch02-guessing-game":{"id":"ch02-guessing-game","title":"2 - Programming a Guessing Game","description":"This chapter creates a little \\"guessing game\\" program. The program picks a random number, you try to guess the secret number, and the program will tell you if you\'re too high or too low. Hours of fun! We\'re going to introduce a bunch of concepts but not go into anything in too much detail in this chapter.","sidebar":"tutorialSidebar"},"ch03-common-programming-concepts":{"id":"ch03-common-programming-concepts","title":"3 - Common Programming Concepts","description":"In which we learn about variables, basic types, functions, comments, and control flow.","sidebar":"tutorialSidebar"},"ch04-ownership":{"id":"ch04-ownership","title":"4 - Ownership, References, and Slices","description":"Ownership is Rust\'s most unique feature and has deep implications for the rest of the language. It enables Rust to make memory safety guarantees without needing a garbage collector, so it\'s important to understand how ownership works.","sidebar":"tutorialSidebar"},"ch05-structs":{"id":"ch05-structs","title":"5 - Using Structs to Structure Related Data","description":"5.1 - Defining and Instantiating Structs","sidebar":"tutorialSidebar"},"ch06-enums-and-pattern-matching":{"id":"ch06-enums-and-pattern-matching","title":"6 - Enums and Pattern Matching","description":"6.1 - Defining an Enum","sidebar":"tutorialSidebar"},"ch07-packages-crates-modules":{"id":"ch07-packages-crates-modules","title":"7 - Managing Growing Projects with Packages, Crates, and Modules","description":"So far all of our examples have lived in a single file, but almost any non-trivial program would be too large to fit in a single file. Rust provides a number of tools to help us organize a project:","sidebar":"tutorialSidebar"},"ch08-common-collections":{"id":"ch08-common-collections","title":"8 - Common Collections","description":"Rust\'s standard library includes a number of collections which store data on the heap:","sidebar":"tutorialSidebar"},"ch09-error-handling":{"id":"ch09-error-handling","title":"9 - Error Handling","description":"Rust has a tiered error-handling scheme:","sidebar":"tutorialSidebar"},"ch10/ch10-01-generic-data-types":{"id":"ch10/ch10-01-generic-data-types","title":"10.1 - Generic Data Types","description":"In Function Definitions","sidebar":"tutorialSidebar"},"ch10/ch10-02-traits":{"id":"ch10/ch10-02-traits","title":"10.2 - Traits: Defining Shared Behavior","description":"A trait in Rust is very similar to what most other languages call an interface. A trait defines some set of behavior, and every struct that implements the trait needs to implement that behavior.","sidebar":"tutorialSidebar"},"ch10/ch10-03-lifetimes":{"id":"ch10/ch10-03-lifetimes","title":"10.3 - Validating References with Lifetimes","description":"This chapter explains lifetimes in a somewhat different, and slightly more technical way than the original \\"The Rust Programming Language\\" did. If you find the explanation here confusing, you might try reading the original, or check the end of this section for some additional reading. Lifetimes can be one of the more confusing parts of Rust if you\'re a newcomer. They\'re a result of Rust\'s unique ownership system, so there aren\'t any direct analogs in other languages. As a result, many attempts have been made to explain them, so if you have a hard time don\'t give up! Somewhere out there, someone will explain this in a way that clicks for you.","sidebar":"tutorialSidebar"},"ch11-automated-tests":{"id":"ch11-automated-tests","title":"11 - Writing Automated Tests","description":"11.1 - How to Write Tests","sidebar":"tutorialSidebar"},"ch12-io-project-cli":{"id":"ch12-io-project-cli","title":"12 - An I/O Project: Building a Command Line Program","description":"We know enough Rust now that we can actually write a useful program. We\'re going to make a copy of the Linux grep command. If you\'re a Windows user, or you\'re not much of a command-line person, the grep command basically works like this:","sidebar":"tutorialSidebar"},"ch13-functional-language-features":{"id":"ch13-functional-language-features","title":"13 - Functional Language Features: Iterators and Closures","description":"In this chapter we will cover closures, which are a like functions you can assign to variables or pass around as parameters. We\'ll also learn about iterators which are used for iterating over a collection of items.","sidebar":"tutorialSidebar"},"ch14-more-about-cargo":{"id":"ch14-more-about-cargo","title":"14 - More about Cargo and Crates","description":"14.1 - Customizing Builds with Release Profiles","sidebar":"tutorialSidebar"},"ch15-smart-pointers":{"id":"ch15-smart-pointers","title":"15 - Smart Pointers","description":"In C++, whenever we want to store an object on the heap, we new that object to allocate some memory. At some later point in time, we have to delete that memory. This is much like malloc and free in standard C.","sidebar":"tutorialSidebar"},"ch16-fearless-concurrency":{"id":"ch16-fearless-concurrency","title":"16 - Fearless Concurrency","description":"Concurrent programming has a lot of potential pit falls - race conditions, thread safe access to variables - in other languages these problems show up in production as tricky to reproduce problems. Access to memory is handled through Rust\'s type system and ownership rules, and it turns out these rules can do an excellent job of catching many concurrency problems at compile time too.","sidebar":"tutorialSidebar"},"ch17-object-oriented-features":{"id":"ch17-object-oriented-features","title":"17 - Object Oriented Features of Rust","description":"17.1 - Characteristics of Object Oriented Languages","sidebar":"tutorialSidebar"},"ch18-patterns-and-matching":{"id":"ch18-patterns-and-matching","title":"18 - Patterns and Matching","description":"18.1 - All the Places Patterns Can Be Used","sidebar":"tutorialSidebar"},"ch19/ch19-01-unsafe":{"id":"ch19/ch19-01-unsafe","title":"19.1 - Unsafe Rust","description":"This chapter is meant as an introduction to unsafe code, but if you find yourself actually writing unsafe code, it would be a good idea to read through the Rustonomicon. There are many things you can do in unsafe code that will result in undefined behavior, some of which you might surprise you if you\'re coming from a language like C/C++.","sidebar":"tutorialSidebar"},"ch19/ch19-02-advanced-traits":{"id":"ch19/ch19-02-advanced-traits","title":"19.2 - Advanced Traits","description":"For an introduction to traits, see chapter 10.","sidebar":"tutorialSidebar"},"ch19/ch19-03-advanced-types":{"id":"ch19/ch19-03-advanced-types","title":"19.3 - Advanced Types","description":"Using the Newtype Pattern for Type Safety and Abstraction","sidebar":"tutorialSidebar"},"ch19/ch19-04-advanced-functions-and-closures":{"id":"ch19/ch19-04-advanced-functions-and-closures","title":"19.4 - Advanced Functions and Closures","description":"Function Pointers","sidebar":"tutorialSidebar"},"ch19/ch19-05-macros":{"id":"ch19/ch19-05-macros","title":"19.5 - Macros","description":"If you\'re coming to Rust from C or C++, then you\'re no doubt already familiar with macros. We\'re going to give a quick introduction to macros here, but if you want to read more you should check out The Little Book of Rust Macros.","sidebar":"tutorialSidebar"},"ch20/ch20-01-single-threaded-web-server":{"id":"ch20/ch20-01-single-threaded-web-server","title":"20.1 - Building a Single-Threaded Web Server","description":"In this chapter we\'re going to build a simple HTTP server to put together a number of things we\'ve learned so far. As usual, the code for this project is available in the GitHub repo.","sidebar":"tutorialSidebar"},"ch20/ch20-02-multi-threaded-web-server":{"id":"ch20/ch20-02-multi-threaded-web-server","title":"20.2 - Turning Our Single-Threaded Server into a Multithreaded Server","description":"Simulating a Slow Request in the Current Server Implementation","sidebar":"tutorialSidebar"},"ch20/ch20-03-graceful-shutdown":{"id":"ch20/ch20-03-graceful-shutdown","title":"20.3 - Graceful Shutdown and Cleanup","description":"Right now when we hit CTRL-C to send an interrupt signal to our web server, it stops running, but it also stops any in-flight requests. Let\'s see if we can get our server to shut down gracefully.","sidebar":"tutorialSidebar"},"ch21-async":{"id":"ch21-async","title":"21 - Async Programming","description":"In this section we\'re going to re-implement our web server from chapter 20 using async functions. We\'re just going to give you enough here to get your feet wet. For further reading, check out Asynchronous Programming in Rust, and the Tokio Tutorial. As usual, if you\'re looking for the full source for this project, it\'s in the GitHub repo.","sidebar":"tutorialSidebar"},"zz-appendix/appendix-01-keywords":{"id":"zz-appendix/appendix-01-keywords","title":"Appendix A: Keywords","description":"This appendix was copied directly from \\"The Rust Programming Language\\".","sidebar":"tutorialSidebar"},"zz-appendix/appendix-02-operators":{"id":"zz-appendix/appendix-02-operators","title":"Appendix B: Operators and Symbols","description":"This appendix was copied directly from \\"The Rust Programming Language\\".","sidebar":"tutorialSidebar"},"zz-appendix/appendix-03-derivable-traits":{"id":"zz-appendix/appendix-03-derivable-traits","title":"Appendix C: Derivable Traits","description":"This appendix was copied directly from \\"The Rust Programming Language\\".","sidebar":"tutorialSidebar"},"zz-appendix/appendix-04-useful-development-tools":{"id":"zz-appendix/appendix-04-useful-development-tools","title":"Appendix D - Useful Development Tools","description":"This appendix was copied directly from \\"The Rust Programming Language\\".","sidebar":"tutorialSidebar"},"zz-appendix/appendix-05-editions":{"id":"zz-appendix/appendix-05-editions","title":"Appendix E - Editions","description":"This appendix was copied directly from \\"The Rust Programming Language\\".","sidebar":"tutorialSidebar"},"zz-appendix/appendix-06-licenses":{"id":"zz-appendix/appendix-06-licenses","title":"Licenses","description":"Ferris the Crab was created by Karen Rustad T\xf6lva, and is used here under public domain.","sidebar":"tutorialSidebar"}}}')}}]); \ No newline at end of file diff --git a/assets/js/96675f22.a990bd42.js b/assets/js/96675f22.a990bd42.js new file mode 100644 index 0000000..dd8844c --- /dev/null +++ b/assets/js/96675f22.a990bd42.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkrust_book_abridged=self.webpackChunkrust_book_abridged||[]).push([[233],{3905:(e,t,n)=>{n.d(t,{Zo:()=>m,kt:()=>c});var a=n(7294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var s=a.createContext({}),p=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},m=function(e){var t=p(e.components);return a.createElement(s.Provider,{value:t},e.children)},u="mdxType",h={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},d=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,r=e.originalType,s=e.parentName,m=l(e,["components","mdxType","originalType","parentName"]),u=p(n),d=i,c=u["".concat(s,".").concat(d)]||u[d]||h[d]||r;return n?a.createElement(c,o(o({ref:t},m),{},{components:n})):a.createElement(c,o({ref:t},m))}));function c(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var r=n.length,o=new Array(r);o[0]=d;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[u]="string"==typeof e?e:i,o[1]=l;for(var p=2;p{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>h,frontMatter:()=>r,metadata:()=>l,toc:()=>p});var a=n(7462),i=(n(7294),n(3905));const r={},o="10.2 - Traits: Defining Shared Behavior",l={unversionedId:"ch10/ch10-02-traits",id:"ch10/ch10-02-traits",title:"10.2 - Traits: Defining Shared Behavior",description:"A trait in Rust is very similar to what most other languages call an interface. A trait defines some set of behavior, and every struct that implements the trait needs to implement that behavior.",source:"@site/docs/ch10/ch10-02-traits.md",sourceDirName:"ch10",slug:"/ch10/ch10-02-traits",permalink:"/rust-book-abridged/ch10/ch10-02-traits",draft:!1,editUrl:"https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/docs/ch10/ch10-02-traits.md",tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"10.1 - Generic Data Types",permalink:"/rust-book-abridged/ch10/ch10-01-generic-data-types"},next:{title:"10.3 - Validating References with Lifetimes",permalink:"/rust-book-abridged/ch10/ch10-03-lifetimes"}},s={},p=[{value:"Defining a Trait",id:"defining-a-trait",level:2},{value:"Implementing a Trait on a Type",id:"implementing-a-trait-on-a-type",level:2},{value:"Default Implementations",id:"default-implementations",level:2},{value:"Traits as Parameters",id:"traits-as-parameters",level:2},{value:"Returning Types that Implement Traits",id:"returning-types-that-implement-traits",level:2},{value:"Using Trait Bounds to Conditionally Implement Methods",id:"using-trait-bounds-to-conditionally-implement-methods",level:2},{value:"From and Into",id:"from-and-into",level:2}],m={toc:p},u="wrapper";function h(e){let{components:t,...n}=e;return(0,i.kt)(u,(0,a.Z)({},m,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("h1",{id:"102---traits-defining-shared-behavior"},"10.2 - Traits: Defining Shared Behavior"),(0,i.kt)("p",null,"A ",(0,i.kt)("em",{parentName:"p"},"trait")," in Rust is very similar to what most other languages call an interface. A trait defines some set of behavior, and every struct that implements the trait needs to implement that behavior."),(0,i.kt)("h2",{id:"defining-a-trait"},"Defining a Trait"),(0,i.kt)("p",null,"Let's suppose we have two types, ",(0,i.kt)("inlineCode",{parentName:"p"},"Tweet")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"NewsArticle"),". We might want to be able to get a summary of tweet, and we might want to be able to get a summary of a news article, so it would make sense for both of these to implement a ",(0,i.kt)("inlineCode",{parentName:"p"},"summarize()")," function. We can define a trait called ",(0,i.kt)("inlineCode",{parentName:"p"},"Summary")," that defines the method signature that these types will need to implement:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-rust"},"pub trait Summary {\n fn summarize(&self) -> String;\n}\n")),(0,i.kt)("p",null,"Note that the trait only defines the method signatures - the contract, if you will - that the types need to implement. Each type is free to implement this function differently."),(0,i.kt)("admonition",{type:"info"},(0,i.kt)("p",{parentName:"admonition"},"When we name structs, we typically use a noun to name a struct. Functions are typically verbs. Traits in Rust are less consistently named."),(0,i.kt)("p",{parentName:"admonition"},'You might think from the name "trait" that these should be named after adjectives. Or perhaps since traits fill the same role as interfaces in other languages, a noun would be appropriate. But the trait for a type that implements the ',(0,i.kt)("inlineCode",{parentName:"p"},"read"),' method is neither "Readable" nor "Reader", but ',(0,i.kt)("inlineCode",{parentName:"p"},"Read"),". A type that can be copied has the ",(0,i.kt)("inlineCode",{parentName:"p"},"Copy"),' marker trait, not the "Copyable" trait.'),(0,i.kt)("p",{parentName:"admonition"},"From these examples - ",(0,i.kt)("inlineCode",{parentName:"p"},"Read")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"Copy")," - clearly traits in Rust should be named after verbs! But there are plenty of examples in the standard library that seem to defy this such as ",(0,i.kt)("inlineCode",{parentName:"p"},"Iterator"),", ",(0,i.kt)("inlineCode",{parentName:"p"},"Hasher"),", or ",(0,i.kt)("inlineCode",{parentName:"p"},"Sized"),", and the example in ",(0,i.kt)("a",{parentName:"p",href:"https://doc.rust-lang.org/rust-by-example/trait.html"},"Rust by Example")," is ",(0,i.kt)("inlineCode",{parentName:"p"},"Animal"),"."),(0,i.kt)("p",{parentName:"admonition"},"This ambiguous naming comes at least in part from the fact that traits are inspired in part by Haskell's typeclasses, which have similar naming weirdness. The best rule of thumb I've seen is that if a trait has a single well defined method (such as ",(0,i.kt)("inlineCode",{parentName:"p"},"write")," or ",(0,i.kt)("inlineCode",{parentName:"p"},"read"),") the the trait should be named after the method. Otherwise, the trait name should be a noun.")),(0,i.kt)("h2",{id:"implementing-a-trait-on-a-type"},"Implementing a Trait on a Type"),(0,i.kt)("p",null,'In languages like TypeScript and Go, if we have an interface, and we have a type that defines all the same methods that the interface declares, then the type implements that interface. There\'s no need to explicitly mark that the type implements the interface. This is called "duck typing", because, as the saying goes, "if it walks like a duck, and it quacks like a duck, then it must be a duck."'),(0,i.kt)("p",null,"Not so in Rust. Here we must explicitly declare that a type implements a trait. The syntax is ",(0,i.kt)("inlineCode",{parentName:"p"},"impl [TRAIT] for [STRUCT] {}"),", and inside the curly braces we place all the methods we need to implement:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-rust"},'pub struct NewsArticle {\n pub headline: String,\n pub location: String,\n pub author: String,\n pub content: String,\n}\n\nimpl Summary for NewsArticle {\n fn summarize(&self) -> String {\n format!("{}, by {} ({})", self.headline, self.author, self.location)\n }\n}\n\npub struct Tweet {\n pub username: String,\n pub content: String,\n pub reply: bool,\n pub retweet: bool,\n}\n\nimpl Summary for Tweet {\n fn summarize(&self) -> String {\n format!("{}: {}", self.username, self.content)\n }\n}\n')),(0,i.kt)("p",null,"This is very similar to defining a method on the struct directly, but the method is actually defined on the trait. If we want to call the ",(0,i.kt)("inlineCode",{parentName:"p"},"summarize")," function, we need to make sure the trait is in scope. In this example we have to ",(0,i.kt)("inlineCode",{parentName:"p"},"use")," both ",(0,i.kt)("inlineCode",{parentName:"p"},"Summary")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"Tweet"),", even though ",(0,i.kt)("inlineCode",{parentName:"p"},"Summary")," never appears in the code:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-rust"},'use aggregator::{Summary, Tweet};\n\nfn main() {\n let tweet = Tweet {\n username: String::from("horse_ebooks"),\n content: String::from(\n "of course, as you probably already know, people",\n ),\n reply: false,\n retweet: false,\n };\n\n println!("1 new tweet: {}", tweet.summarize());\n}\n')),(0,i.kt)("p",null,"Other crates can use the ",(0,i.kt)("inlineCode",{parentName:"p"},"Summary")," trait and implement it on their own types, just like you can implement traits from the standard library on your own types. One thing to note is that if you want to implement a trait on a type, then either the trait or the type (or both) must be local to your crate. You can't use a trait from one external crate, a type from another, and then implement the external trait on the external type in your crate."),(0,i.kt)("p",null,"This restriction is in place because of something called the ",(0,i.kt)("em",{parentName:"p"},"orphan rule"),". Let's suppose there's a ",(0,i.kt)("inlineCode",{parentName:"p"},"color")," crate out there. You implement a library crate that uses ",(0,i.kt)("inlineCode",{parentName:"p"},"color"),", but you notice one of the types in ",(0,i.kt)("inlineCode",{parentName:"p"},"color")," doesn't implement the ",(0,i.kt)("inlineCode",{parentName:"p"},"Display")," trait and you want to ",(0,i.kt)("inlineCode",{parentName:"p"},"println!")," a color, so you implement the ",(0,i.kt)("inlineCode",{parentName:"p"},"Display")," trait on that type. Now suppose I'm writing a separate library crate, and I do the same thing. Now suppose someone adds your crate and my crate to their application. At this point, the Rust compiler has two competing implementations for ",(0,i.kt)("inlineCode",{parentName:"p"},"Display"),' on this type, so which one does it use? Since Rust has no way to know which is the "correct" one, Rust just stops this from ever happening by forcing the crate to own at least one of the type or trait.'),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("p",{parentName:"admonition"},"The orphan rule is actually ",(0,i.kt)("a",{parentName:"p",href:"https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html#concrete-orphan-rules"},"slightly more complicated")," than mentioned above. Once generics start getting involved, it's possible to use a foreign trait and foreign type, given that one of the generic types is local. See the above link for full details.")),(0,i.kt)("h2",{id:"default-implementations"},"Default Implementations"),(0,i.kt)("p",null,"Remember how we said a trait just had signatures and no implementations? Well, we lied a little. Sometimes it's handy to be able to define default behavior for a method:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-rust"},'pub trait Summary {\n fn summarize(&self) -> String {\n String::from("(Read more...)")\n }\n}\n\n// We can implement this trait with an empty\n// impl block, taking the default function\n// definitions.\nimpl Summary for NewsArticle {}\n')),(0,i.kt)("p",null,"Default implementations are allowed to call other methods on the same trait. This allows a trait to provide a lot of functionality while only requiring implementers to implement part of the trait:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-rust"},'pub trait Summary {\n fn summarize_author(&self) -> String;\n\n fn summarize(&self) -> String {\n format!("(Read more from {}...)", self.summarize_author())\n }\n}\n')),(0,i.kt)("p",null,"Some implementations might only implement ",(0,i.kt)("inlineCode",{parentName:"p"},"summarize_author()"),", while some might implement both methods."),(0,i.kt)("h2",{id:"traits-as-parameters"},"Traits as Parameters"),(0,i.kt)("p",null,"When we define a generic function, we can limit what kinds of concrete types are allowed to be used in place of the generic type using a ",(0,i.kt)("em",{parentName:"p"},"trait bound"),":"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-rust"},'pub fn notify(item: &T) {\n println!("Breaking news! {}", item.summarize());\n}\n')),(0,i.kt)("p",null,"Here we're declaring a generic function, but we're setting bounds on the type of T. Whatever you pass in for T has to satisfy the ",(0,i.kt)("inlineCode",{parentName:"p"},"Summary")," trait. This is a common thing to do, so there's a shortcut to specify this using the ",(0,i.kt)("inlineCode",{parentName:"p"},"impl")," keyword:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-rust"},'// This is syntactic sugar for the example above.\npub fn notify(item: &impl Summary) {\n println!("Breaking news! {}", item.summarize());\n}\n')),(0,i.kt)("p",null,"We can specify more than one trait bound:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-rust"},"// Using a trait bound:\npub fn notify(item: &T) {...}\n\n// Using the `impl` syntax:\npub fn notify(item: &(impl Summary + Display)) {...}\n")),(0,i.kt)("p",null,"Here whatever we pass in for ",(0,i.kt)("inlineCode",{parentName:"p"},"T")," must satisfy both our own ",(0,i.kt)("inlineCode",{parentName:"p"},"Summary")," trait and the ",(0,i.kt)("inlineCode",{parentName:"p"},"Display")," trait from the standard library (so we can use ",(0,i.kt)("inlineCode",{parentName:"p"},"{}")," to display the item with ",(0,i.kt)("inlineCode",{parentName:"p"},"println!")," or ",(0,i.kt)("inlineCode",{parentName:"p"},"format!"),")."),(0,i.kt)("p",null,"This can get a bit hard to read if you have a lot of traits bounds. There ends up being a lot of clutter between the name of the function and the parameters. Borrowing a page from SQL, we can also write trait bounds using a ",(0,i.kt)("inlineCode",{parentName:"p"},"where")," clause. These two examples are equivalent:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-rust"},"fn some_function(t: &T, u: &U) -> i32 {...}\n\nfn some_function(t: &T, u: &U) -> i32\nwhere\n T: Display + Clone,\n U: Clone + Debug,\n{...}\n")),(0,i.kt)("h2",{id:"returning-types-that-implement-traits"},"Returning Types that Implement Traits"),(0,i.kt)("p",null,"We can hide the concrete type returned by a function using an ",(0,i.kt)("a",{parentName:"p",href:"https://rustc-dev-guide.rust-lang.org/opaque-types-type-alias-impl-trait.html"},(0,i.kt)("em",{parentName:"a"},"opaque type")),". This lets us hide the concrete type from the caller (and allows you to change the concrete type later without affecting callers):"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-rust"},'fn returns_summarizable() -> impl Summary {\n Tweet {\n username: String::from("horse_ebooks"),\n content: String::from(\n "of course, as you probably already know, people",\n ),\n reply: false,\n retweet: false,\n }\n}\n')),(0,i.kt)("p",null,"Note that even though this ",(0,i.kt)("inlineCode",{parentName:"p"},"impl")," syntax looks similar to the shortcut we used to specify a trait bound above, this is not at all the same. This function is not generic. There is still a single concrete type being returned by this function (in this case ",(0,i.kt)("inlineCode",{parentName:"p"},"Tweet"),"), but callers are limited to only using the interface provided by the trait (in this case ",(0,i.kt)("inlineCode",{parentName:"p"},"Summary"),")."),(0,i.kt)("p",null,"The concrete type here is inferred by the compiler, but it's important to realize there is still one. If you were to add an ",(0,i.kt)("inlineCode",{parentName:"p"},"if")," statement to this function, you would not be able to return a ",(0,i.kt)("inlineCode",{parentName:"p"},"Tweet")," in one branch and a ",(0,i.kt)("inlineCode",{parentName:"p"},"NewsArticle")," in the other. (We'll see how to overcome this with trait objects and dynamic dispatch in ",(0,i.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch17-object-oriented-features#172---using-trait-objects-that-allow-for-values-of-different-types"},"chapter 17"),".)"),(0,i.kt)("p",null,"This syntax is useful if we want to return something that has a concrete type that can't be written down, like a closure:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-rust"},'fn thing_returning_closure() -> impl Fn(i32) -> bool {\n println!("here\'s a closure for you!");\n |x: i32| x % 3 == 0\n}\n')),(0,i.kt)("p",null,"We haven't talked about iterators yet, but sometimes when using an iterator, the type inferred by the compiler can be quite long, and writing the full type out by hand would be a lot of work without much benefit. Being able to supply an opaque type here is much more concise."),(0,i.kt)("h2",{id:"using-trait-bounds-to-conditionally-implement-methods"},"Using Trait Bounds to Conditionally Implement Methods"),(0,i.kt)("p",null,"As we saw ",(0,i.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch10/ch10-01-generic-data-types#in-method-definitions"},"earlier"),", we can specify an implementation for a method on specific types of a generic type. We can similarly implement a method on specific trait bounds:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-rust"},'use std::fmt::Display;\n\nstruct Pair {\n x: T,\n y: T,\n}\n\nimpl Pair {\n fn new(x: T, y: T) -> Self {\n Self { x, y }\n }\n}\n\nimpl Pair {\n fn cmp_display(&self) {\n if self.x >= self.y {\n println!("The largest member is x = {}", self.x);\n } else {\n println!("The largest member is y = {}", self.y);\n }\n }\n}\n')),(0,i.kt)("p",null,"Here the ",(0,i.kt)("inlineCode",{parentName:"p"},"new()")," associated function is implemented on all generic types, but ",(0,i.kt)("inlineCode",{parentName:"p"},"cmp_display()")," is only defined on a ",(0,i.kt)("inlineCode",{parentName:"p"},"Pair")," if the inner type used for T implements both the ",(0,i.kt)("inlineCode",{parentName:"p"},"Display")," and the ",(0,i.kt)("inlineCode",{parentName:"p"},"PartialOrd")," traits."),(0,i.kt)("p",null,"We can also conditionally implement a trait for any type that implements some other trait! These are called ",(0,i.kt)("em",{parentName:"p"},"blanket implementations"),". This example comes from the standard library:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-rust"},"impl ToString for T {\n // --snip--\n}\n")),(0,i.kt)("p",null,"The implements the ",(0,i.kt)("inlineCode",{parentName:"p"},"ToString")," trait on any type that implements the ",(0,i.kt)("inlineCode",{parentName:"p"},"Display")," trait. Because of this, we can call ",(0,i.kt)("inlineCode",{parentName:"p"},"to_string()")," on any type that implements ",(0,i.kt)("inlineCode",{parentName:"p"},"Display"),"."),(0,i.kt)("h2",{id:"from-and-into"},(0,i.kt)("inlineCode",{parentName:"h2"},"From")," and ",(0,i.kt)("inlineCode",{parentName:"h2"},"Into")),(0,i.kt)("p",null,(0,i.kt)("inlineCode",{parentName:"p"},"From")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"Into")," are two related traits in rust. These are used to convert a type from one type to another. If you implement ",(0,i.kt)("inlineCode",{parentName:"p"},"From"),", you get ",(0,i.kt)("inlineCode",{parentName:"p"},"Into")," for free. We already mentioned ",(0,i.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch09-error-handling#propagating-errors"},"using the ",(0,i.kt)("inlineCode",{parentName:"a"},"From")," trait to convert Errors")," from one type to another. Let's see another example:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-rust"},'struct Millimeters(u32);\nstruct Meters(u32);\n\nimpl From for Millimeters {\n fn from(value: Meters) -> Self {\n return Millimeters(value.0 * 1000);\n }\n}\n\nimpl From for Meters {\n fn from(value: Millimeters) -> Self {\n return Meters(value.0 / 1000);\n }\n}\n\nfn main() {\n let one_meter = Meters(1);\n let millis = Millimeters::from(one_meter);\n println!("1 meter is {} millimeters", millis.0);\n\n let one_meter = Meters(1);\n let into_millis: Millimeters = one_meter.into();\n println!("1 meter is {} millimeters", into_millis.0);\n}\n')),(0,i.kt)("p",null,"Here ",(0,i.kt)("inlineCode",{parentName:"p"},"Millimeters::from(one_meter)")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"one_meter.into()")," both convert meters into millimeters. When we call ",(0,i.kt)("inlineCode",{parentName:"p"},"into")," the type the meter is converted to is inferred from the annotation on ",(0,i.kt)("inlineCode",{parentName:"p"},"into_millis"),"."),(0,i.kt)("p",null,"Continue to ",(0,i.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch10/ch10-03-lifetimes"},"10.3 - Lifetimes"),"."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/96675f22.e0297e82.js b/assets/js/96675f22.e0297e82.js deleted file mode 100644 index 3c4270b..0000000 --- a/assets/js/96675f22.e0297e82.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkrust_book_abridged=self.webpackChunkrust_book_abridged||[]).push([[233],{3905:(e,t,n)=>{n.d(t,{Zo:()=>m,kt:()=>d});var a=n(7294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var s=a.createContext({}),p=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},m=function(e){var t=p(e.components);return a.createElement(s.Provider,{value:t},e.children)},u="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},h=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,r=e.originalType,s=e.parentName,m=l(e,["components","mdxType","originalType","parentName"]),u=p(n),h=i,d=u["".concat(s,".").concat(h)]||u[h]||c[h]||r;return n?a.createElement(d,o(o({ref:t},m),{},{components:n})):a.createElement(d,o({ref:t},m))}));function d(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var r=n.length,o=new Array(r);o[0]=h;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[u]="string"==typeof e?e:i,o[1]=l;for(var p=2;p{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>c,frontMatter:()=>r,metadata:()=>l,toc:()=>p});var a=n(7462),i=(n(7294),n(3905));const r={},o="10.2 - Traits: Defining Shared Behavior",l={unversionedId:"ch10/ch10-02-traits",id:"ch10/ch10-02-traits",title:"10.2 - Traits: Defining Shared Behavior",description:"A trait in Rust is very similar to what most other languages call an interface. A trait defines some set of behavior, and every struct that implements the trait needs to implement that behavior.",source:"@site/docs/ch10/ch10-02-traits.md",sourceDirName:"ch10",slug:"/ch10/ch10-02-traits",permalink:"/rust-book-abridged/ch10/ch10-02-traits",draft:!1,editUrl:"https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/docs/ch10/ch10-02-traits.md",tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"10.1 - Generic Data Types",permalink:"/rust-book-abridged/ch10/ch10-01-generic-data-types"},next:{title:"10.3 - Validating References with Lifetimes",permalink:"/rust-book-abridged/ch10/ch10-03-lifetimes"}},s={},p=[{value:"Defining a Trait",id:"defining-a-trait",level:2},{value:"Implementing a Trait on a Type",id:"implementing-a-trait-on-a-type",level:2},{value:"Default Implementations",id:"default-implementations",level:2},{value:"Traits as Parameters",id:"traits-as-parameters",level:2},{value:"Returning Types that Implement Traits",id:"returning-types-that-implement-traits",level:2},{value:"Using Trait Bounds to Conditionally Implement Methods",id:"using-trait-bounds-to-conditionally-implement-methods",level:2},{value:"From and Into",id:"from-and-into",level:2}],m={toc:p},u="wrapper";function c(e){let{components:t,...n}=e;return(0,i.kt)(u,(0,a.Z)({},m,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("h1",{id:"102---traits-defining-shared-behavior"},"10.2 - Traits: Defining Shared Behavior"),(0,i.kt)("p",null,"A ",(0,i.kt)("em",{parentName:"p"},"trait")," in Rust is very similar to what most other languages call an interface. A trait defines some set of behavior, and every struct that implements the trait needs to implement that behavior."),(0,i.kt)("h2",{id:"defining-a-trait"},"Defining a Trait"),(0,i.kt)("p",null,"Let's suppose we have two types, ",(0,i.kt)("inlineCode",{parentName:"p"},"Tweet")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"NewsArticle"),". We might want to be able to get a summary of tweet, and we might want to be able to get a summary of a news article, so it would make sense for both of these to implement a ",(0,i.kt)("inlineCode",{parentName:"p"},"summarize()")," function. We can define a trait called ",(0,i.kt)("inlineCode",{parentName:"p"},"Summary")," that defines the method signature that these types will need to implement:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-rust"},"pub trait Summary {\n fn summarize(&self) -> String;\n}\n")),(0,i.kt)("p",null,"Note that the trait only defines the method signatures - the contract, if you will - that the types need to implement. Each type is free to implement this function differently."),(0,i.kt)("h2",{id:"implementing-a-trait-on-a-type"},"Implementing a Trait on a Type"),(0,i.kt)("p",null,'In languages like TypeScript and Go, if we have an interface, and we have a type that defines all the same methods that the interface declares, then the type implements that interface. There\'s no need to explicitly mark that the type implements the interface. This is called "duck typing", because, as the saying goes, "if it walks like a duck, and it quacks like a duck, then it must be a duck."'),(0,i.kt)("p",null,"Not so in Rust. Here we must explicitly declare that a type implements a trait. The syntax is ",(0,i.kt)("inlineCode",{parentName:"p"},"impl [TRAIT] for [STRUCT] {}"),", and inside the curly braces we place all the methods we need to implement:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-rust"},'pub struct NewsArticle {\n pub headline: String,\n pub location: String,\n pub author: String,\n pub content: String,\n}\n\nimpl Summary for NewsArticle {\n fn summarize(&self) -> String {\n format!("{}, by {} ({})", self.headline, self.author, self.location)\n }\n}\n\npub struct Tweet {\n pub username: String,\n pub content: String,\n pub reply: bool,\n pub retweet: bool,\n}\n\nimpl Summary for Tweet {\n fn summarize(&self) -> String {\n format!("{}: {}", self.username, self.content)\n }\n}\n')),(0,i.kt)("p",null,"This is very similar to defining a method on the struct directly, but the method is actually defined on the trait. If we want to call the ",(0,i.kt)("inlineCode",{parentName:"p"},"summarize")," function, we need to make sure the trait is in scope. In this example we have to ",(0,i.kt)("inlineCode",{parentName:"p"},"use")," both ",(0,i.kt)("inlineCode",{parentName:"p"},"Summary")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"Tweet"),", even though ",(0,i.kt)("inlineCode",{parentName:"p"},"Summary")," never appears in the code:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-rust"},'use aggregator::{Summary, Tweet};\n\nfn main() {\n let tweet = Tweet {\n username: String::from("horse_ebooks"),\n content: String::from(\n "of course, as you probably already know, people",\n ),\n reply: false,\n retweet: false,\n };\n\n println!("1 new tweet: {}", tweet.summarize());\n}\n')),(0,i.kt)("p",null,"Other crates can use the ",(0,i.kt)("inlineCode",{parentName:"p"},"Summary")," trait and implement it on their own types, just like you can implement traits from the standard library on your own types. One thing to note is that if you want to implement a trait on a type, then either the trait or the type (or both) must be local to your crate. You can't use a trait from one external crate, a type from another, and then implement the external trait on the external type in your crate."),(0,i.kt)("p",null,"This restriction is in place because of something called the ",(0,i.kt)("em",{parentName:"p"},"orphan rule"),". Let's suppose there's a ",(0,i.kt)("inlineCode",{parentName:"p"},"color")," crate out there. You implement a library crate that uses ",(0,i.kt)("inlineCode",{parentName:"p"},"color"),", but you notice one of the types in ",(0,i.kt)("inlineCode",{parentName:"p"},"color")," doesn't implement the ",(0,i.kt)("inlineCode",{parentName:"p"},"Display")," trait and you want to ",(0,i.kt)("inlineCode",{parentName:"p"},"println!")," a color, so you implement the ",(0,i.kt)("inlineCode",{parentName:"p"},"Display")," trait on that type. Now suppose I'm writing a separate library crate, and I do the same thing. Now suppose someone adds your crate and my crate to their application. At this point, the Rust compiler has two competing implementations for ",(0,i.kt)("inlineCode",{parentName:"p"},"Display"),' on this type, so which one does it use? Since Rust has no way to know which is the "correct" one, Rust just stops this from ever happening by forcing the crate to own at least one of the type or trait.'),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("p",{parentName:"admonition"},"The orphan rule is actually ",(0,i.kt)("a",{parentName:"p",href:"https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html#concrete-orphan-rules"},"slightly more complicated")," than mentioned above. Once generics start getting involved, it's possible to use a foreign trait and foreign type, given that one of the generic types is local. See the above link for full details.")),(0,i.kt)("h2",{id:"default-implementations"},"Default Implementations"),(0,i.kt)("p",null,"Remember how we said a trait just had signatures and no implementations? Well, we lied a little. Sometimes it's handy to be able to define default behavior for a method:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-rust"},'pub trait Summary {\n fn summarize(&self) -> String {\n String::from("(Read more...)")\n }\n}\n\n// We can implement this trait with an empty\n// impl block, taking the default function\n// definitions.\nimpl Summary for NewsArticle {}\n')),(0,i.kt)("p",null,"Default implementations are allowed to call other methods on the same trait. This allows a trait to provide a lot of functionality while only requiring implementers to implement part of the trait:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-rust"},'pub trait Summary {\n fn summarize_author(&self) -> String;\n\n fn summarize(&self) -> String {\n format!("(Read more from {}...)", self.summarize_author())\n }\n}\n')),(0,i.kt)("p",null,"Some implementations might only implement ",(0,i.kt)("inlineCode",{parentName:"p"},"summarize_author()"),", while some might implement both methods."),(0,i.kt)("h2",{id:"traits-as-parameters"},"Traits as Parameters"),(0,i.kt)("p",null,"When we define a generic function, we can limit what kinds of concrete types are allowed to be used in place of the generic type using a ",(0,i.kt)("em",{parentName:"p"},"trait bound"),":"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-rust"},'pub fn notify(item: &T) {\n println!("Breaking news! {}", item.summarize());\n}\n')),(0,i.kt)("p",null,"Here we're declaring a generic function, but we're setting bounds on the type of T. Whatever you pass in for T has to satisfy the ",(0,i.kt)("inlineCode",{parentName:"p"},"Summary")," trait. This is a common thing to do, so there's a shortcut to specify this using the ",(0,i.kt)("inlineCode",{parentName:"p"},"impl")," keyword:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-rust"},'// This is syntactic sugar for the example above.\npub fn notify(item: &impl Summary) {\n println!("Breaking news! {}", item.summarize());\n}\n')),(0,i.kt)("p",null,"We can specify more than one trait bound:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-rust"},"// Using a trait bound:\npub fn notify(item: &T) {...}\n\n// Using the `impl` syntax:\npub fn notify(item: &(impl Summary + Display)) {...}\n")),(0,i.kt)("p",null,"Here whatever we pass in for ",(0,i.kt)("inlineCode",{parentName:"p"},"T")," must satisfy both our own ",(0,i.kt)("inlineCode",{parentName:"p"},"Summary")," trait and the ",(0,i.kt)("inlineCode",{parentName:"p"},"Display")," trait from the standard library (so we can use ",(0,i.kt)("inlineCode",{parentName:"p"},"{}")," to display the item with ",(0,i.kt)("inlineCode",{parentName:"p"},"println!")," or ",(0,i.kt)("inlineCode",{parentName:"p"},"format!"),")."),(0,i.kt)("p",null,"This can get a bit hard to read if you have a lot of traits bounds. There ends up being a lot of clutter between the name of the function and the parameters. Borrowing a page from SQL, we can also write trait bounds using a ",(0,i.kt)("inlineCode",{parentName:"p"},"where")," clause. These two examples are equivalent:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-rust"},"fn some_function(t: &T, u: &U) -> i32 {...}\n\nfn some_function(t: &T, u: &U) -> i32\nwhere\n T: Display + Clone,\n U: Clone + Debug,\n{...}\n")),(0,i.kt)("h2",{id:"returning-types-that-implement-traits"},"Returning Types that Implement Traits"),(0,i.kt)("p",null,"We can hide the concrete type returned by a function using an ",(0,i.kt)("a",{parentName:"p",href:"https://rustc-dev-guide.rust-lang.org/opaque-types-type-alias-impl-trait.html"},(0,i.kt)("em",{parentName:"a"},"opaque type")),". This lets us hide the concrete type from the caller (and allows you to change the concrete type later without affecting callers):"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-rust"},'fn returns_summarizable() -> impl Summary {\n Tweet {\n username: String::from("horse_ebooks"),\n content: String::from(\n "of course, as you probably already know, people",\n ),\n reply: false,\n retweet: false,\n }\n}\n')),(0,i.kt)("p",null,"Note that even though this ",(0,i.kt)("inlineCode",{parentName:"p"},"impl")," syntax looks similar to the shortcut we used to specify a trait bound above, this is not at all the same. This function is not generic. There is still a single concrete type being returned by this function (in this case ",(0,i.kt)("inlineCode",{parentName:"p"},"Tweet"),"), but callers are limited to only using the interface provided by the trait (in this case ",(0,i.kt)("inlineCode",{parentName:"p"},"Summary"),")."),(0,i.kt)("p",null,"The concrete type here is inferred by the compiler, but it's important to realize there is still one. If you were to add an ",(0,i.kt)("inlineCode",{parentName:"p"},"if")," statement to this function, you would not be able to return a ",(0,i.kt)("inlineCode",{parentName:"p"},"Tweet")," in one branch and a ",(0,i.kt)("inlineCode",{parentName:"p"},"NewsArticle")," in the other. (We'll see how to overcome this with trait objects and dynamic dispatch in ",(0,i.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch17-object-oriented-features#172---using-trait-objects-that-allow-for-values-of-different-types"},"chapter 17"),".)"),(0,i.kt)("p",null,"This syntax is useful if we want to return something that has a concrete type that can't be written down, like a closure:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-rust"},'fn thing_returning_closure() -> impl Fn(i32) -> bool {\n println!("here\'s a closure for you!");\n |x: i32| x % 3 == 0\n}\n')),(0,i.kt)("p",null,"We haven't talked about iterators yet, but sometimes when using an iterator, the type inferred by the compiler can be quite long, and writing the full type out by hand would be a lot of work without much benefit. Being able to supply an opaque type here is much more concise."),(0,i.kt)("h2",{id:"using-trait-bounds-to-conditionally-implement-methods"},"Using Trait Bounds to Conditionally Implement Methods"),(0,i.kt)("p",null,"As we saw ",(0,i.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch10/ch10-01-generic-data-types#in-method-definitions"},"earlier"),", we can specify an implementation for a method on specific types of a generic type. We can similarly implement a method on specific trait bounds:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-rust"},'use std::fmt::Display;\n\nstruct Pair {\n x: T,\n y: T,\n}\n\nimpl Pair {\n fn new(x: T, y: T) -> Self {\n Self { x, y }\n }\n}\n\nimpl Pair {\n fn cmp_display(&self) {\n if self.x >= self.y {\n println!("The largest member is x = {}", self.x);\n } else {\n println!("The largest member is y = {}", self.y);\n }\n }\n}\n')),(0,i.kt)("p",null,"Here the ",(0,i.kt)("inlineCode",{parentName:"p"},"new()")," associated function is implemented on all generic types, but ",(0,i.kt)("inlineCode",{parentName:"p"},"cmp_display()")," is only defined on a ",(0,i.kt)("inlineCode",{parentName:"p"},"Pair")," if the inner type used for T implements both the ",(0,i.kt)("inlineCode",{parentName:"p"},"Display")," and the ",(0,i.kt)("inlineCode",{parentName:"p"},"PartialOrd")," traits."),(0,i.kt)("p",null,"We can also conditionally implement a trait for any type that implements some other trait! These are called ",(0,i.kt)("em",{parentName:"p"},"blanket implementations"),". This example comes from the standard library:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-rust"},"impl ToString for T {\n // --snip--\n}\n")),(0,i.kt)("p",null,"The implements the ",(0,i.kt)("inlineCode",{parentName:"p"},"ToString")," trait on any type that implements the ",(0,i.kt)("inlineCode",{parentName:"p"},"Display")," trait. Because of this, we can call ",(0,i.kt)("inlineCode",{parentName:"p"},"to_string()")," on any type that implements ",(0,i.kt)("inlineCode",{parentName:"p"},"Display"),"."),(0,i.kt)("h2",{id:"from-and-into"},(0,i.kt)("inlineCode",{parentName:"h2"},"From")," and ",(0,i.kt)("inlineCode",{parentName:"h2"},"Into")),(0,i.kt)("p",null,(0,i.kt)("inlineCode",{parentName:"p"},"From")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"Into")," are two related traits in rust. These are used to convert a type from one type to another. If you implement ",(0,i.kt)("inlineCode",{parentName:"p"},"From"),", you get ",(0,i.kt)("inlineCode",{parentName:"p"},"Into")," for free. We already mentioned ",(0,i.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch09-error-handling#propagating-errors"},"using the ",(0,i.kt)("inlineCode",{parentName:"a"},"From")," trait to convert Errors")," from one type to another. Let's see another example:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-rust"},'struct Millimeters(u32);\nstruct Meters(u32);\n\nimpl From for Millimeters {\n fn from(value: Meters) -> Self {\n return Millimeters(value.0 * 1000);\n }\n}\n\nimpl From for Meters {\n fn from(value: Millimeters) -> Self {\n return Meters(value.0 / 1000);\n }\n}\n\nfn main() {\n let one_meter = Meters(1);\n let millis = Millimeters::from(one_meter);\n println!("1 meter is {} millimeters", millis.0);\n\n let one_meter = Meters(1);\n let into_millis: Millimeters = one_meter.into();\n println!("1 meter is {} millimeters", into_millis.0);\n}\n')),(0,i.kt)("p",null,"Here ",(0,i.kt)("inlineCode",{parentName:"p"},"Millimeters::from(one_meter)")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"one_meter.into()")," both convert meters into millimeters. When we call ",(0,i.kt)("inlineCode",{parentName:"p"},"into")," the type the meter is converted to is inferred from the annotation on ",(0,i.kt)("inlineCode",{parentName:"p"},"into_millis"),"."),(0,i.kt)("p",null,"Continue to ",(0,i.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch10/ch10-03-lifetimes"},"10.3 - Lifetimes"),"."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/c670d531.78c57de5.js b/assets/js/c670d531.4757bcce.js similarity index 71% rename from assets/js/c670d531.78c57de5.js rename to assets/js/c670d531.4757bcce.js index 80ad7df..2bfb603 100644 --- a/assets/js/c670d531.78c57de5.js +++ b/assets/js/c670d531.4757bcce.js @@ -1 +1 @@ -"use strict";(self.webpackChunkrust_book_abridged=self.webpackChunkrust_book_abridged||[]).push([[528],{3905:(e,t,n)=>{n.d(t,{Zo:()=>m,kt:()=>d});var a=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function i(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var l=a.createContext({}),p=function(e){var t=a.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},m=function(e){var t=p(e.components);return a.createElement(l.Provider,{value:t},e.children)},c="mdxType",h={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},u=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,o=e.originalType,l=e.parentName,m=s(e,["components","mdxType","originalType","parentName"]),c=p(n),u=r,d=c["".concat(l,".").concat(u)]||c[u]||h[u]||o;return n?a.createElement(d,i(i({ref:t},m),{},{components:n})):a.createElement(d,i({ref:t},m))}));function d(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var o=n.length,i=new Array(o);i[0]=u;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[c]="string"==typeof e?e:r,i[1]=s;for(var p=2;p{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>i,default:()=>h,frontMatter:()=>o,metadata:()=>s,toc:()=>p});var a=n(7462),r=(n(7294),n(3905));const o={},i="4 - Ownership, References, and Slices",s={unversionedId:"ch04-ownership",id:"ch04-ownership",title:"4 - Ownership, References, and Slices",description:"Ownership is Rust's most unique feature and has deep implications for the rest of the language. It enables Rust to make memory safety guarantees without needing a garbage collector, so it's important to understand how ownership works.",source:"@site/docs/ch04-ownership.md",sourceDirName:".",slug:"/ch04-ownership",permalink:"/rust-book-abridged/ch04-ownership",draft:!1,editUrl:"https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/docs/ch04-ownership.md",tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"3 - Common Programming Concepts",permalink:"/rust-book-abridged/ch03-common-programming-concepts"},next:{title:"5 - Using Structs to Structure Related Data",permalink:"/rust-book-abridged/ch05-structs"}},l={},p=[{value:"4.1 - What is Ownership?",id:"41---what-is-ownership",level:2},{value:"Ownership Rules",id:"ownership-rules",level:3},{value:"Memory and Allocation",id:"memory-and-allocation",level:3},{value:"There Can Only Be One",id:"there-can-only-be-one",level:3},{value:"Stack-Only Data: Copy",id:"stack-only-data-copy",level:3},{value:"Ownership and Functions",id:"ownership-and-functions",level:3},{value:"4.2 - References and Borrowing",id:"42---references-and-borrowing",level:2},{value:"Mutable References",id:"mutable-references",level:3},{value:"Dereferencing",id:"dereferencing",level:2},{value:"Dangling References",id:"dangling-references",level:3},{value:"The Rules of References",id:"the-rules-of-references",level:3},{value:"4.3 - The Slice Type",id:"43---the-slice-type",level:2},{value:"String Literals as Slices",id:"string-literals-as-slices",level:3},{value:"String Slices as Parameters",id:"string-slices-as-parameters",level:3},{value:"Other Slices",id:"other-slices",level:3}],m={toc:p},c="wrapper";function h(e){let{components:t,...n}=e;return(0,r.kt)(c,(0,a.Z)({},m,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"4---ownership-references-and-slices"},"4 - Ownership, References, and Slices"),(0,r.kt)("blockquote",null,(0,r.kt)("p",{parentName:"blockquote"},"Ownership is Rust's most unique feature and has deep implications for the rest of the language. It enables Rust to make memory safety guarantees without needing a garbage collector, so it's important to understand how ownership works.")),(0,r.kt)("p",null,"-- ",(0,r.kt)("a",{parentName:"p",href:"https://doc.rust-lang.org/stable/book/ch04-00-understanding-ownership.html"},'"The Rust Programming Language" Chapter 4 - Understanding Ownership')),(0,r.kt)("h2",{id:"41---what-is-ownership"},"4.1 - What is Ownership?"),(0,r.kt)("p",null,"The idea of ownership is quite core to Rust. If you're coming from a language like Python or JavaScript, and you're not familiar with the idea of the ",(0,r.kt)("a",{parentName:"p",href:"https://www.geeksforgeeks.org/stack-vs-heap-memory-allocation/"},"the stack and heap")," it's worth reading up about them. We're going to assume you're familiar with them in this chapter."),(0,r.kt)("p",null,"In a language like C, we can manage memory by explicitly calling ",(0,r.kt)("inlineCode",{parentName:"p"},"malloc")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"free"),". All memory management is up to us, which means it's easy to make mistakes. In a language like Java or JavaScript, memory is allocated automatically without us having to think about it, so memory allocation is very safe, but this incurs a runtime cost in the form of garbage collection."),(0,r.kt)("p",null,"Rust is rather unique in how it manages memory. Aside from simple values such as ",(0,r.kt)("inlineCode",{parentName:"p"},"i32")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"f64"),", In Rust, every value is ",(0,r.kt)("em",{parentName:"p"},"owned")," by some variable called the ",(0,r.kt)("em",{parentName:"p"},"owner"),". Ownership of a particular value can be transferred from one variable to another, and in some cases memory can be ",(0,r.kt)("em",{parentName:"p"},"borrowed"),". Once the variable that owns the value is no longer around we say that value has been ",(0,r.kt)("em",{parentName:"p"},"dropped"),", and once that happens any memory it allocated can safely be freed. When a value is dropped, it can optionally run code in a ",(0,r.kt)("em",{parentName:"p"},"destructor")," (defined by implementing the ",(0,r.kt)("inlineCode",{parentName:"p"},"Drop")," trait)."),(0,r.kt)("h3",{id:"ownership-rules"},"Ownership Rules"),(0,r.kt)("p",null,"From the original Rust Book:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Each value in Rust has an owner."),(0,r.kt)("li",{parentName:"ul"},"There can only be one owner at a time."),(0,r.kt)("li",{parentName:"ul"},"When the owner goes out of scope, the value will be ",(0,r.kt)("em",{parentName:"li"},"dropped"),".")),(0,r.kt)("p",null,"The ",(0,r.kt)("em",{parentName:"p"},"scope"),' of a variable in Rust works much like it does in most other languages - inside a set of curly braces, any variable you declare can be accessed only after its declaration, and it goes "out of scope" once we hit the closing brace. The key thing about Rust is that once a variable goes out of scope, if that variable currently owns some memory, then that memory will be freed.'),(0,r.kt)("admonition",{type:"tip"},(0,r.kt)("p",{parentName:"admonition"},"A variable can only have one owner at a time, but in ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch15-smart-pointers",title:"Chapter 15: Smart Pointers"},"chapter 15")," we'll talk about smart pointers like ",(0,r.kt)("inlineCode",{parentName:"p"},"Rc")," that let us get around this restriction."),(0,r.kt)("p",{parentName:"admonition"},"We also say that each value in Rust has an owner, but it's possible to ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch15-smart-pointers#156---reference-cycles-can-leak-memory"},"leak memory")," in Rust, which would technically end with values that have no owners.")),(0,r.kt)("h3",{id:"memory-and-allocation"},"Memory and Allocation"),(0,r.kt)("p",null,"This is a trivial example demonstrating some memory being allocated on the heap and then freed:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'fn foo() {\n\n if (true) {\n // Create the variable `s` to own a String.\n // Remember that Strings can store an arbitrary\n // length of data, so this will allocate memory\n // on the heap.\n let s = String::from("hello");\n\n // Do stuff with s\n\n }\n // At this point `s` has fallen out of scope, so the\n // String that was owned by s will be dropped, and\n // the memory it allocated on the heap will be freed.\n}\n')),(0,r.kt)("p",null,'You might read that and scratch your head and think "If everything disappears when it goes out of scope, isn\'t this the same as just allocating everything on the stack?" And this ',(0,r.kt)("em",{parentName:"p"},"would")," be true, except that ownership can be ",(0,r.kt)("em",{parentName:"p"},"moved"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'fn main() {\n let outer_string = foo();\n println!("{}", outer_string);\n}\n\nfn foo() -> String {\n let inner_string = String::from("hello world");\n inner_string\n}\n')),(0,r.kt)("p",null,"Here the ",(0,r.kt)("inlineCode",{parentName:"p"},"foo")," function creates a ",(0,r.kt)("inlineCode",{parentName:"p"},"String")," (which allocates some memory on the heap) and ",(0,r.kt)("inlineCode",{parentName:"p"},"inner_string")," is the owner of that ",(0,r.kt)("inlineCode",{parentName:"p"},"String"),". The ",(0,r.kt)("inlineCode",{parentName:"p"},"foo")," function returns ",(0,r.kt)("inlineCode",{parentName:"p"},"inner_string"),", so ownership of the String (and the associated memory) is moved to ",(0,r.kt)("inlineCode",{parentName:"p"},"outer_string")," in the caller. When we reach the end of ",(0,r.kt)("inlineCode",{parentName:"p"},"main"),", then ",(0,r.kt)("inlineCode",{parentName:"p"},"outer_string")," falls out-of-scope. At this point the ",(0,r.kt)("inlineCode",{parentName:"p"},"String")," doesn't have an owner anymore, so it will be dropped."),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"When we move ownership of a variable, it's location in memory will change:"),(0,r.kt)("pre",{parentName:"admonition"},(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'fn main() {\n let x = String::from("hello world");\n println!("Address: {:p}", &x);\n let y = x;\n println!("Address: {:p}", &y);\n}\n')),(0,r.kt)("p",{parentName:"admonition"},"The above example will print different addresses for ",(0,r.kt)("inlineCode",{parentName:"p"},"x")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"y"),". In this example, the ",(0,r.kt)("inlineCode",{parentName:"p"},"String"),' has some memory stored on the heap (the "hello world" part) but also has some memory on the stack (a pointer to that value and a length, amongst other data). When we move ownership of ',(0,r.kt)("inlineCode",{parentName:"p"},"x")," to ",(0,r.kt)("inlineCode",{parentName:"p"},"y"),", we're also moving the data on the stack from one place to another with ",(0,r.kt)("inlineCode",{parentName:"p"},"memcpy"),", although the heap part stays in the same place."),(0,r.kt)("p",{parentName:"admonition"},"If you need a piece of data to stay in one place in memory, see ",(0,r.kt)("a",{parentName:"p",href:"https://doc.rust-lang.org/std/pin/index.html"},(0,r.kt)("inlineCode",{parentName:"a"},"std::pin")),".")),(0,r.kt)("h3",{id:"there-can-only-be-one"},"There Can Only Be One"),(0,r.kt)("p",null,"Remember that we said there can only be one owner at a time?"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'fn strings() -> String {\n // Create a string\n let s1 = String::from("hello");\n\n // Move ownership from s1 to s2\n let s2 = s1;\n\n // Can\'t use s1 anymore!\n println!("{}", s1);\n\n s2\n}\n')),(0,r.kt)("p",null,"This code fails to compile with the error:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-txt"},'error[E0382]: borrow of moved value: `s1`\n --\x3e src/main.rs:9:20\n |\n3 | let s1 = String::from("hello");\n | -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait\n...\n6 | let s2 = s1;\n | -- value moved here\n...\n9 | println!("{}", s1);\n | ^^ value borrowed here after move\n')),(0,r.kt)("p",null,"If you're coming from some other language, and you try to just pass values around and hope for the best without understanding ownership, you're going to see this error a lot."),(0,r.kt)("p",null,"In this example, we create a variable ",(0,r.kt)("inlineCode",{parentName:"p"},"s1"),", which owns the String. In most other languages, when we do ",(0,r.kt)("inlineCode",{parentName:"p"},"let s2 = s1;"),", we'd now have two variables that point to the same underlying object, but not so in Rust. In Rust, we ",(0,r.kt)("em",{parentName:"p"},"move")," ownership of the value from ",(0,r.kt)("inlineCode",{parentName:"p"},"s1")," to ",(0,r.kt)("inlineCode",{parentName:"p"},"s2"),", so ",(0,r.kt)("inlineCode",{parentName:"p"},"s1")," stops being valid and can't be used from that point forwards. This is exactly the same as when we returned a variable in the example above."),(0,r.kt)("p",null,"If you think about this at the memory level, when we create ",(0,r.kt)("inlineCode",{parentName:"p"},"s1"),", we allocate some memory. When we say ",(0,r.kt)("inlineCode",{parentName:"p"},"let s2 = s1;"),", we're not creating a second ",(0,r.kt)("inlineCode",{parentName:"p"},"String")," (we didn't call ",(0,r.kt)("inlineCode",{parentName:"p"},"clone")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"new"),"). If we allowed ",(0,r.kt)("inlineCode",{parentName:"p"},"s1")," to be valid after this point then ",(0,r.kt)("inlineCode",{parentName:"p"},"s1")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"s2")," would have to point to the same memory. When we reach the end of the function, we return ",(0,r.kt)("inlineCode",{parentName:"p"},"s2")," but not ",(0,r.kt)("inlineCode",{parentName:"p"},"s1"),", which means ",(0,r.kt)("inlineCode",{parentName:"p"},"s1")," is going out of scope and should be dropped, but since ",(0,r.kt)("inlineCode",{parentName:"p"},"s2")," is being moved and refers to the same underlying object ",(0,r.kt)("inlineCode",{parentName:"p"},"s1")," can't be dropped. Rust's answer to this problem is to never let this happen - only one owner at a time."),(0,r.kt)("p",null,"If we wanted to deep-copy the data in the String, we could use the ",(0,r.kt)("inlineCode",{parentName:"p"},"clone")," method to allocate more memory on the heap:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'fn strings() {\n let s1 = String::from("hello");\n let s2 = s1.clone();\n\n println!("{}", s1);\n}\n')),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"We can do something slightly tricky with a move like this too. We can take an immutable variable and turn it into a mutable one:"),(0,r.kt)("pre",{parentName:"admonition"},(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'fn main() {\n let x = String::from("hello world");\n let mut y = x;\n}\n')),(0,r.kt)("p",{parentName:"admonition"},"When ",(0,r.kt)("inlineCode",{parentName:"p"},"y")," takes ownership of ",(0,r.kt)("inlineCode",{parentName:"p"},"x"),", it owns that memory now and can do what it wants with it, so it's perfectly acceptable to redeclare the variable as ",(0,r.kt)("inlineCode",{parentName:"p"},"mut"),". If you have a favorite book and you keep it in pristine condition, but then you decide to give me that book then it becomes my book. I can dog ear pages and crack the spine, because I own it. If you lend me your book, that's a different story - and we'll talk about borrowing in just a little bit.")),(0,r.kt)("h3",{id:"stack-only-data-copy"},"Stack-Only Data: Copy"),(0,r.kt)("p",null,"Similar to Java or JavaScript or C or... actually most other languages, Rust has special handling for basic data types:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'fn integers() {\n let i1 = 1;\n let i2 = i1;\n\n println!("{}", i1);\n}\n')),(0,r.kt)("p",null,"This looks just like the string example above, but it compiles. This is because here ",(0,r.kt)("inlineCode",{parentName:"p"},"i1")," is an ",(0,r.kt)("inlineCode",{parentName:"p"},"i32"),", which takes up four bytes of memory. Since Rust knows this at compile time, it can allocate it on the stack instead of the heap, and making a copy of a four byte value on the stack to another four bytes of the stack is so cheap it is essentially free. So here, ",(0,r.kt)("inlineCode",{parentName:"p"},"let i2 = i1;")," doesn't move anything, it just makes a copy of the variable for you."),(0,r.kt)("p",null,"What types get copied like this? The quick answer to this is any basic type (integers, booleans, chars, etc...) and any tuple made up of basic types. More formally, the answer is any type that has the ",(0,r.kt)("inlineCode",{parentName:"p"},"Copy")," trait (see ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch10/ch10-01-generic-data-types",title:"Chapter 10: Generic Types, Traits, and Lifetimes"},"chapter 10")," for more information about traits). You can also implement it on your own data structures if they are made entirely of copyable types, or get Rust to ",(0,r.kt)("em",{parentName:"p"},"derive")," it for you (which means Rust will generate this code for you at compile time):"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"#[derive(Copy, Clone)]\npub struct MyStruct {\n pub foo: i32,\n}\n")),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"Structs with the ",(0,r.kt)("inlineCode",{parentName:"p"},"Copy")," trait are not allowed to implement the ",(0,r.kt)("inlineCode",{parentName:"p"},"Drop")," trait, so they can't run any custom code when they go out of scope.")),(0,r.kt)("h3",{id:"ownership-and-functions"},"Ownership and Functions"),(0,r.kt)("p",null,"We already saw that if you return a variable, then ownership of the variable is moved to the caller. We also move ownership when we pass a variable to a function:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'fn main() {\n let s = String::from("hello");\n takes_ownership(s);\n\n // Ownership of `s` was moved to `takes_ownership`\'s\n // `some_string`, so s is no longer valid here.\n}\n\nfn takes_ownership(some_string: String) {\n println!("{}", some_string);\n} // Here, some_string goes out of scope and `drop`\n // is called. The backing memory is freed.\n')),(0,r.kt)("h2",{id:"42---references-and-borrowing"},"4.2 - References and Borrowing"),(0,r.kt)("p",null,'If you wanted to pass a variable to a function, but also keep it usable afterwards, you could pass the variable to the function and then return it from the function. This would move the variable into the function, and then move it back so you can keep using it. As you can imagine, using a variable more than once is something we want to do pretty often, and if this was "the way" to do it, then Rust would be a very annoying language to work in. Instead we can let the function we call ',(0,r.kt)("em",{parentName:"p"},"borrow")," the variable by passing a reference:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'fn main() {\n let s1 = String::from("hello");\n\n let len = calculate_length(&s1);\n\n println!("The length of \'{}\' is {}.", s1, len);\n}\n\nfn calculate_length(s: &String) -> usize {\n s.len()\n}\n')),(0,r.kt)("p",null,"Two things to note here - when we call ",(0,r.kt)("inlineCode",{parentName:"p"},"calculate_length")," instead of passing ",(0,r.kt)("inlineCode",{parentName:"p"},"s1")," we're passing ",(0,r.kt)("inlineCode",{parentName:"p"},"&s1"),", and in the signature for ",(0,r.kt)("inlineCode",{parentName:"p"},"calculate_length")," we take a ",(0,r.kt)("inlineCode",{parentName:"p"},"&String")," instead of a ",(0,r.kt)("inlineCode",{parentName:"p"},"String"),'. What we\'re passing here is a "reference to a string". Essentially ',(0,r.kt)("inlineCode",{parentName:"p"},"&s1")," contains a pointer to the String held in ",(0,r.kt)("inlineCode",{parentName:"p"},"s1"),", and we're passing that pointer to ",(0,r.kt)("inlineCode",{parentName:"p"},"calculate_length"),". ",(0,r.kt)("inlineCode",{parentName:"p"},"calculate_length")," doesn't take ownership of the ",(0,r.kt)("inlineCode",{parentName:"p"},"String"),", it merely borrows it, so the ",(0,r.kt)("inlineCode",{parentName:"p"},"String")," won't be dropped when ",(0,r.kt)("inlineCode",{parentName:"p"},"s")," goes out of scope."),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"The syntax for getting a reference to a value - ",(0,r.kt)("inlineCode",{parentName:"p"},"&x")," - is exactly the same as getting a pointer to a value in C or Go, and references in Rust behave a lot like pointers. ",(0,r.kt)("a",{parentName:"p",href:"https://stackoverflow.com/questions/64167637/is-the-concept-of-reference-different-in-c-and-rust/64167719#64167719"},"This Stack Overflow answer")," talks about ways that Rust references compare to C/C++ pointers.")),(0,r.kt)("h3",{id:"mutable-references"},"Mutable References"),(0,r.kt)("p",null,"As with variables, we can have both immutable references (the default) and mutable references:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'fn main() {\n let mut s = String::from("hello");\n\n change(&mut s);\n}\n\nfn change(some_string: &mut String) {\n some_string.push_str(", world");\n}\n')),(0,r.kt)("p",null,"Mutable references come with a restriction: if you have a mutable reference to a value, you can have no other references to that value."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'let mut s = String::from("hello");\n\nlet r1 = &mut s;\nlet r2 = &mut s; // This is an error!\n\nprintln!("{}, {}", r1, r2);\n')),(0,r.kt)("p",null,"This restriction is imposed because it prevents data races. The compiler will stop us from creating data races at compile time!"),(0,r.kt)("p",null,"The scope of a reference lasts only until it's last use, not until the end of the block, so this is fine:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'let mut s = String::from("hello");\n\nlet r1 = &mut s;\nprintln!("{}", r1);\n\nlet r2 = &mut s; // r1 is now out-of-scope, so we can create r2.\nprintln!("{}", r2);\n')),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"Where you place the ",(0,r.kt)("inlineCode",{parentName:"p"},"mut")," keyword changes how a reference can be used:"),(0,r.kt)("pre",{parentName:"admonition"},(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"// x1 is a reference to y. We can't update x or y:\nlet x1 = &y;\n// x2 is a reference that can be used to change y:\nlet x2 = &mut y;\n// x3 is is a reference that currently points to,\n// an immutable y, but we could change x3 to point\n// somewhere else.\nlet mut x3 = &y;\n// x4 is a reference that can be used to change y,\n// and can also be updated to point somewhere else.\nlet mut x4 = &mut y;\n"))),(0,r.kt)("h2",{id:"dereferencing"},"Dereferencing"),(0,r.kt)("p",null,"Rust has a ",(0,r.kt)("inlineCode",{parentName:"p"},"*")," operator for dereferencing, very similar to C++ or Go:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"let num1 = 7; // num1 has type `i32`.\nlet num2 = &num1; // num2 has type `&i32`.\nlet num3 = *num2; // num3 has type `i32` again.\n")),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"*")," follows the pointer (see ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch15-smart-pointers#following-the-pointer-to-the-value"},"chapter 15"),"). If the reference is mutable, we can use the ",(0,r.kt)("inlineCode",{parentName:"p"},"*")," operator to modify what the reference points to:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'let mut val = 10;\nlet val_ref = &mut val;\n*val_ref = 5;\n\n// Prints 5, because we used `val_ref` to modify `val`.\nprintln!("{val}");\n')),(0,r.kt)("h3",{id:"dangling-references"},"Dangling References"),(0,r.kt)("p",null,"You can't return a reference to an object that will be dropped:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'fn dangle() -> &String {\n let s = String::from("hello");\n &s // This is an error.\n}\n')),(0,r.kt)("p",null,"Here ",(0,r.kt)("inlineCode",{parentName:"p"},"s")," goes out of scope at the end of the function, so the String will be dropped. That means if Rust let us return a reference to the String, it would be a reference to memory that had already been reclaimed."),(0,r.kt)("p",null,"There's no ",(0,r.kt)("inlineCode",{parentName:"p"},"null")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"nil")," in Rust. You can't have a null pointer like you could in C. (Instead there's something called an ",(0,r.kt)("inlineCode",{parentName:"p"},"Option")," which we'll talk about in ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch06-enums-and-pattern-matching",title:"Chapter 6: Enums and Pattern Matching"},"chapter 6"),".)"),(0,r.kt)("h3",{id:"the-rules-of-references"},"The Rules of References"),(0,r.kt)("p",null,"To sum up what we learned above:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"At any given time, you can have ",(0,r.kt)("em",{parentName:"li"},"either")," one mutable reference ",(0,r.kt)("em",{parentName:"li"},"or")," any number of immutable references."),(0,r.kt)("li",{parentName:"ul"},"References must always be valid. You can't have references to dropped memory or null pointers.")),(0,r.kt)("h2",{id:"43---the-slice-type"},"4.3 - The Slice Type"),(0,r.kt)("p",null,"A ",(0,r.kt)("em",{parentName:"p"},"slice")," is a reference to a contiguous sequence of elements in a collection. Slices are references so they don't take ownership. The type of a string slice is ",(0,r.kt)("inlineCode",{parentName:"p"},"&str"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'let s = String::from("hello world");\n\nlet hello = &s[0..5]; // Type of `hello` is `&str`.\nlet world = &s[6..11];\n')),(0,r.kt)("p",null,"The range syntax is ",(0,r.kt)("inlineCode",{parentName:"p"},"[inclusive..exclusive]"),". Or, in other words ",(0,r.kt)("inlineCode",{parentName:"p"},"[0..5]")," includes the zeroth character in the string, but omits the fifth. With the range syntax, you can omit the first number to start at 0, and omit the second number to end at the length of the string."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'let s = String::from("rust time");\n\nlet rust = &s[..4];\nlet time = &s[5..];\nlet rust_time = &s[..];\n')),(0,r.kt)("p",null,"Slices must occur at valid UTF-8 character boundaries. If you attempt to create a string slice in the middle of a multibyte character, your program will exit with an error. (Don't know what a multibyte character is? See ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch08-common-collections",title:"Chapter 8: Common Collections"},"chapter 8"),"!)"),(0,r.kt)("p",null,"Note that if you have a string slice, this counts as a reference, so you can't also have a mutable reference to that String:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'fn first_word(s: &String) -> &str {\n let bytes = s.as_bytes();\n\n for (i, &item) in bytes.iter().enumerate() {\n if item == b\' \' {\n return &s[0..i];\n }\n }\n\n &s[..]\n}\n\nfn main() {\n let mut s = String::from("hello world");\n\n // `word` ends up being a slice of `s`, so\n // `word` counts as a reference to `s`.\n let word = first_word(&s);\n\n s.clear(); // error!\n\n println!("the first word is: {}", word);\n}\n')),(0,r.kt)("p",null,"Inside ",(0,r.kt)("inlineCode",{parentName:"p"},"main"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"word")," is a string slice from ",(0,r.kt)("inlineCode",{parentName:"p"},"s"),", and therefore a reference to the memory the ",(0,r.kt)("inlineCode",{parentName:"p"},"String")," uses. The call to ",(0,r.kt)("inlineCode",{parentName:"p"},"s.clear()")," will fail to compile because to clear the string, we'd need to mutate it (",(0,r.kt)("inlineCode",{parentName:"p"},"clear")," is a method with a mutable reference to ",(0,r.kt)("inlineCode",{parentName:"p"},"self"),"). Since we can't create a mutable reference while ",(0,r.kt)("inlineCode",{parentName:"p"},"word")," is in scope, this fails to compile."),(0,r.kt)("h3",{id:"string-literals-as-slices"},"String Literals as Slices"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'let s = "Hello, world!";\n')),(0,r.kt)("p",null,"The type of ",(0,r.kt)("inlineCode",{parentName:"p"},"s")," here is ",(0,r.kt)("inlineCode",{parentName:"p"},"&str"),": it's a slice pointing to where this string is stored in the binary."),(0,r.kt)("h3",{id:"string-slices-as-parameters"},"String Slices as Parameters"),(0,r.kt)("p",null,"These two function signatures are very similar:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"fn first_word_string(s: &String) -> &str {...}\n\nfn first_word_str(s: &str) -> &str {...}\n")),(0,r.kt)("p",null,"The first takes a reference to a String, the second takes a string slice. The second one, though, is generally preferred. It's trivial to convert a string to a slice, so you can call the second with any ",(0,r.kt)("inlineCode",{parentName:"p"},"String"),", string slice, or string literal, or even a reference to a ",(0,r.kt)("inlineCode",{parentName:"p"},"String")," (see ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch15-smart-pointers",title:"Chapter 15: Smart Pointers"},"chapter 15")," for more on ",(0,r.kt)("em",{parentName:"p"},"deref coercion"),")."),(0,r.kt)("p",null,"In the reverse direction, it's a bit tedious to convert a string slice into a ",(0,r.kt)("inlineCode",{parentName:"p"},"String"),". As a result the first version, ",(0,r.kt)("inlineCode",{parentName:"p"},"first_word_string"),", is much less flexible."),(0,r.kt)("h3",{id:"other-slices"},"Other Slices"),(0,r.kt)("p",null,"Much like in Go, we can also create slices from arrays:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"let a = [1, 2, 3, 4, 5];\n\nlet slice = &a[1..3];\n\nassert_eq!(slice, &[2, 3]);\n")),(0,r.kt)("p",null,"The type of ",(0,r.kt)("inlineCode",{parentName:"p"},"slice")," here is ",(0,r.kt)("inlineCode",{parentName:"p"},"&[i32]"),"."),(0,r.kt)("p",null,"Continue to ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch05-structs",title:"Chapter 5: Using Structs to Structure Related Data"},"chapter 5"),"."))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkrust_book_abridged=self.webpackChunkrust_book_abridged||[]).push([[528],{3905:(e,t,n)=>{n.d(t,{Zo:()=>m,kt:()=>d});var a=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function i(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var l=a.createContext({}),p=function(e){var t=a.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},m=function(e){var t=p(e.components);return a.createElement(l.Provider,{value:t},e.children)},c="mdxType",h={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},u=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,o=e.originalType,l=e.parentName,m=s(e,["components","mdxType","originalType","parentName"]),c=p(n),u=r,d=c["".concat(l,".").concat(u)]||c[u]||h[u]||o;return n?a.createElement(d,i(i({ref:t},m),{},{components:n})):a.createElement(d,i({ref:t},m))}));function d(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var o=n.length,i=new Array(o);i[0]=u;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[c]="string"==typeof e?e:r,i[1]=s;for(var p=2;p{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>i,default:()=>h,frontMatter:()=>o,metadata:()=>s,toc:()=>p});var a=n(7462),r=(n(7294),n(3905));const o={},i="4 - Ownership, References, and Slices",s={unversionedId:"ch04-ownership",id:"ch04-ownership",title:"4 - Ownership, References, and Slices",description:"Ownership is Rust's most unique feature and has deep implications for the rest of the language. It enables Rust to make memory safety guarantees without needing a garbage collector, so it's important to understand how ownership works.",source:"@site/docs/ch04-ownership.md",sourceDirName:".",slug:"/ch04-ownership",permalink:"/rust-book-abridged/ch04-ownership",draft:!1,editUrl:"https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/docs/ch04-ownership.md",tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"3 - Common Programming Concepts",permalink:"/rust-book-abridged/ch03-common-programming-concepts"},next:{title:"5 - Using Structs to Structure Related Data",permalink:"/rust-book-abridged/ch05-structs"}},l={},p=[{value:"4.1 - What is Ownership?",id:"41---what-is-ownership",level:2},{value:"Ownership Rules",id:"ownership-rules",level:3},{value:"Memory and Allocation",id:"memory-and-allocation",level:3},{value:"There Can Only Be One",id:"there-can-only-be-one",level:3},{value:"Stack-Only Data: Copy",id:"stack-only-data-copy",level:3},{value:"Ownership and Functions",id:"ownership-and-functions",level:3},{value:"4.2 - References and Borrowing",id:"42---references-and-borrowing",level:2},{value:"Mutable References",id:"mutable-references",level:3},{value:"Dereferencing",id:"dereferencing",level:2},{value:"Dangling References",id:"dangling-references",level:3},{value:"The Rules of References",id:"the-rules-of-references",level:3},{value:"4.3 - The Slice Type",id:"43---the-slice-type",level:2},{value:"String Literals as Slices",id:"string-literals-as-slices",level:3},{value:"String Slices as Parameters",id:"string-slices-as-parameters",level:3},{value:"Other Slices",id:"other-slices",level:3}],m={toc:p},c="wrapper";function h(e){let{components:t,...n}=e;return(0,r.kt)(c,(0,a.Z)({},m,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"4---ownership-references-and-slices"},"4 - Ownership, References, and Slices"),(0,r.kt)("blockquote",null,(0,r.kt)("p",{parentName:"blockquote"},"Ownership is Rust's most unique feature and has deep implications for the rest of the language. It enables Rust to make memory safety guarantees without needing a garbage collector, so it's important to understand how ownership works.")),(0,r.kt)("p",null,"-- ",(0,r.kt)("a",{parentName:"p",href:"https://doc.rust-lang.org/stable/book/ch04-00-understanding-ownership.html"},'"The Rust Programming Language" Chapter 4 - Understanding Ownership')),(0,r.kt)("h2",{id:"41---what-is-ownership"},"4.1 - What is Ownership?"),(0,r.kt)("p",null,"The idea of ownership is quite core to Rust. If you're coming from a language like Python or JavaScript, and you're not familiar with the idea of the ",(0,r.kt)("a",{parentName:"p",href:"https://www.geeksforgeeks.org/stack-vs-heap-memory-allocation/"},"the stack and heap")," it's worth reading up about them. We're going to assume you're familiar with them in this chapter."),(0,r.kt)("p",null,"In a language like C, we can manage memory by explicitly calling ",(0,r.kt)("inlineCode",{parentName:"p"},"malloc")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"free"),". All memory management is up to us, which means it's easy to make mistakes. In a language like Java or JavaScript, memory is allocated automatically without us having to think about it, so memory allocation is very safe, but this incurs a runtime cost in the form of garbage collection."),(0,r.kt)("p",null,"Rust is rather unique in how it manages memory. Aside from simple values such as ",(0,r.kt)("inlineCode",{parentName:"p"},"i32")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"f64"),", In Rust, every value is ",(0,r.kt)("em",{parentName:"p"},"owned")," by some variable called the ",(0,r.kt)("em",{parentName:"p"},"owner"),". Ownership of a particular value can be transferred from one variable to another, and in some cases memory can be ",(0,r.kt)("em",{parentName:"p"},"borrowed"),". Once the variable that owns the value is no longer around we say that value has been ",(0,r.kt)("em",{parentName:"p"},"dropped"),", and once that happens any memory it allocated can safely be freed. When a value is dropped, it can optionally run code in a ",(0,r.kt)("em",{parentName:"p"},"destructor")," (defined by implementing the ",(0,r.kt)("inlineCode",{parentName:"p"},"Drop")," trait)."),(0,r.kt)("h3",{id:"ownership-rules"},"Ownership Rules"),(0,r.kt)("p",null,"From the original Rust Book:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Each value in Rust has an owner."),(0,r.kt)("li",{parentName:"ul"},"There can only be one owner at a time."),(0,r.kt)("li",{parentName:"ul"},"When the owner goes out of scope, the value will be ",(0,r.kt)("em",{parentName:"li"},"dropped"),".")),(0,r.kt)("p",null,"The ",(0,r.kt)("em",{parentName:"p"},"scope"),' of a variable in Rust works much like it does in most other languages - inside a set of curly braces, any variable you declare can be accessed only after its declaration, and it goes "out of scope" once we hit the closing brace. The key thing about Rust is that once a variable goes out of scope, if that variable currently owns some memory, then that memory will be freed.'),(0,r.kt)("admonition",{type:"tip"},(0,r.kt)("p",{parentName:"admonition"},"A variable can only have one owner at a time, but in ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch15-smart-pointers",title:"Chapter 15: Smart Pointers"},"chapter 15")," we'll talk about smart pointers like ",(0,r.kt)("inlineCode",{parentName:"p"},"Rc")," that let us get around this restriction."),(0,r.kt)("p",{parentName:"admonition"},"We also say that each value in Rust has an owner, but it's possible to ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch15-smart-pointers#156---reference-cycles-can-leak-memory"},"leak memory")," in Rust, which would technically end with values that have no owners.")),(0,r.kt)("h3",{id:"memory-and-allocation"},"Memory and Allocation"),(0,r.kt)("p",null,"This is a trivial example demonstrating some memory being allocated on the heap and then freed:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'fn foo() {\n\n if (true) {\n // Create the variable `s` to own a String.\n // Remember that Strings can store an arbitrary\n // length of data, so this will allocate memory\n // on the heap.\n let s = String::from("hello");\n\n // Do stuff with s\n\n }\n // At this point `s` has fallen out of scope, so the\n // String that was owned by s will be dropped, and\n // the memory it allocated on the heap will be freed.\n}\n')),(0,r.kt)("p",null,'You might read that and scratch your head and think "If everything disappears when it goes out of scope, isn\'t this the same as just allocating everything on the stack?" And this ',(0,r.kt)("em",{parentName:"p"},"would")," be true, except that ownership can be ",(0,r.kt)("em",{parentName:"p"},"moved"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'fn main() {\n let outer_string = foo();\n println!("{}", outer_string);\n}\n\nfn foo() -> String {\n let inner_string = String::from("hello world");\n inner_string\n}\n')),(0,r.kt)("p",null,"Here the ",(0,r.kt)("inlineCode",{parentName:"p"},"foo")," function creates a ",(0,r.kt)("inlineCode",{parentName:"p"},"String")," (which allocates some memory on the heap) and ",(0,r.kt)("inlineCode",{parentName:"p"},"inner_string")," is the owner of that ",(0,r.kt)("inlineCode",{parentName:"p"},"String"),". The ",(0,r.kt)("inlineCode",{parentName:"p"},"foo")," function returns ",(0,r.kt)("inlineCode",{parentName:"p"},"inner_string"),", so ownership of the String (and the associated memory) is moved to ",(0,r.kt)("inlineCode",{parentName:"p"},"outer_string")," in the caller. When we reach the end of ",(0,r.kt)("inlineCode",{parentName:"p"},"main"),", then ",(0,r.kt)("inlineCode",{parentName:"p"},"outer_string")," falls out-of-scope. At this point the ",(0,r.kt)("inlineCode",{parentName:"p"},"String")," doesn't have an owner anymore, so it will be dropped."),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"When we move ownership of a variable, it's location in memory will change:"),(0,r.kt)("pre",{parentName:"admonition"},(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'fn main() {\n let x = String::from("hello world");\n println!("Address: {:p}", &x);\n let y = x;\n println!("Address: {:p}", &y);\n}\n')),(0,r.kt)("p",{parentName:"admonition"},"The above example will print different addresses for ",(0,r.kt)("inlineCode",{parentName:"p"},"x")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"y"),". In this example, the ",(0,r.kt)("inlineCode",{parentName:"p"},"String"),' has some memory stored on the heap (the "hello world" part) but also has some memory on the stack (a pointer to that value and a length, amongst other data). When we move ownership of ',(0,r.kt)("inlineCode",{parentName:"p"},"x")," to ",(0,r.kt)("inlineCode",{parentName:"p"},"y"),", we're also moving the data on the stack from one place to another with ",(0,r.kt)("inlineCode",{parentName:"p"},"memcpy"),", although the heap part stays in the same place."),(0,r.kt)("p",{parentName:"admonition"},"If you need a piece of data to stay in one place in memory, see ",(0,r.kt)("a",{parentName:"p",href:"https://doc.rust-lang.org/std/pin/index.html"},(0,r.kt)("inlineCode",{parentName:"a"},"std::pin")),".")),(0,r.kt)("h3",{id:"there-can-only-be-one"},"There Can Only Be One"),(0,r.kt)("p",null,"Remember that we said there can only be one owner at a time?"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'fn strings() -> String {\n // Create a string\n let s1 = String::from("hello");\n\n // Move ownership from s1 to s2\n let s2 = s1;\n\n // Can\'t use s1 anymore!\n println!("{}", s1);\n\n s2\n}\n')),(0,r.kt)("p",null,"This code fails to compile with the error:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-txt"},'error[E0382]: borrow of moved value: `s1`\n --\x3e src/main.rs:9:20\n |\n3 | let s1 = String::from("hello");\n | -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait\n...\n6 | let s2 = s1;\n | -- value moved here\n...\n9 | println!("{}", s1);\n | ^^ value borrowed here after move\n')),(0,r.kt)("p",null,"If you're coming from some other language, and you try to just pass values around and hope for the best without understanding ownership, you're going to see this error a lot."),(0,r.kt)("p",null,"In this example, we create a variable ",(0,r.kt)("inlineCode",{parentName:"p"},"s1"),", which owns the String. In most other languages, when we do ",(0,r.kt)("inlineCode",{parentName:"p"},"let s2 = s1;"),", we'd now have two variables that point to the same underlying object, but not so in Rust. In Rust, we ",(0,r.kt)("em",{parentName:"p"},"move")," ownership of the value from ",(0,r.kt)("inlineCode",{parentName:"p"},"s1")," to ",(0,r.kt)("inlineCode",{parentName:"p"},"s2"),", so ",(0,r.kt)("inlineCode",{parentName:"p"},"s1")," stops being valid and can't be used from that point forwards. This is exactly the same as when we returned a variable in the example above."),(0,r.kt)("p",null,"If you think about this at the memory level, when we create ",(0,r.kt)("inlineCode",{parentName:"p"},"s1"),", we allocate some memory. When we say ",(0,r.kt)("inlineCode",{parentName:"p"},"let s2 = s1;"),", we're not creating a second ",(0,r.kt)("inlineCode",{parentName:"p"},"String")," (we didn't call ",(0,r.kt)("inlineCode",{parentName:"p"},"clone")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"new"),"). If we allowed ",(0,r.kt)("inlineCode",{parentName:"p"},"s1")," to be valid after this point then ",(0,r.kt)("inlineCode",{parentName:"p"},"s1")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"s2")," would have to point to the same memory. When we reach the end of the function, we return ",(0,r.kt)("inlineCode",{parentName:"p"},"s2")," but not ",(0,r.kt)("inlineCode",{parentName:"p"},"s1"),", which means ",(0,r.kt)("inlineCode",{parentName:"p"},"s1")," is going out of scope and should be dropped, but since ",(0,r.kt)("inlineCode",{parentName:"p"},"s2")," is being moved and refers to the same underlying object ",(0,r.kt)("inlineCode",{parentName:"p"},"s1")," can't be dropped. Rust's answer to this problem is to never let this happen - only one owner at a time."),(0,r.kt)("p",null,"If we wanted to deep-copy the data in the String, we could use the ",(0,r.kt)("inlineCode",{parentName:"p"},"clone")," method to allocate more memory on the heap:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'fn strings() {\n let s1 = String::from("hello");\n let s2 = s1.clone();\n\n println!("{}", s1);\n}\n')),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"We can do something slightly tricky with a move like this too. We can take an immutable variable and turn it into a mutable one:"),(0,r.kt)("pre",{parentName:"admonition"},(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'fn main() {\n let x = String::from("hello world");\n let mut y = x;\n}\n')),(0,r.kt)("p",{parentName:"admonition"},"When ",(0,r.kt)("inlineCode",{parentName:"p"},"y")," takes ownership of ",(0,r.kt)("inlineCode",{parentName:"p"},"x"),", it owns that memory now and can do what it wants with it, so it's perfectly acceptable to redeclare the variable as ",(0,r.kt)("inlineCode",{parentName:"p"},"mut"),". If you have a favorite book and you keep it in pristine condition, but then you decide to give me that book then it becomes my book. I can dog ear pages and crack the spine, because I own it. If you lend me your book, that's a different story - and we'll talk about borrowing in just a little bit.")),(0,r.kt)("h3",{id:"stack-only-data-copy"},"Stack-Only Data: Copy"),(0,r.kt)("p",null,"Similar to Java or JavaScript or C or... actually most other languages, Rust has special handling for basic data types:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'fn integers() {\n let i1 = 1;\n let i2 = i1;\n\n println!("{}", i1);\n}\n')),(0,r.kt)("p",null,"This looks just like the string example above, but it compiles. This is because here ",(0,r.kt)("inlineCode",{parentName:"p"},"i1")," is an ",(0,r.kt)("inlineCode",{parentName:"p"},"i32"),", which takes up four bytes of memory. Since Rust knows this at compile time, it can allocate it on the stack instead of the heap, and making a copy of a four byte value on the stack to another four bytes of the stack is so cheap it is essentially free. So here, ",(0,r.kt)("inlineCode",{parentName:"p"},"let i2 = i1;")," doesn't move anything, it just makes a copy of the variable for you."),(0,r.kt)("p",null,"What types get copied like this? The quick answer to this is any basic type (integers, booleans, chars, etc...) and any tuple made up of basic types. More formally, the answer is any type that has the ",(0,r.kt)("inlineCode",{parentName:"p"},"Copy")," trait (see ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch10/ch10-01-generic-data-types",title:"Chapter 10: Generic Types, Traits, and Lifetimes"},"chapter 10")," for more information about traits). You can also implement it on your own data structures if they are made entirely of copyable types, or get Rust to ",(0,r.kt)("em",{parentName:"p"},"derive")," it for you (which means Rust will generate this code for you at compile time):"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"#[derive(Copy, Clone)]\npub struct MyStruct {\n pub foo: i32,\n}\n")),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"Structs with the ",(0,r.kt)("inlineCode",{parentName:"p"},"Copy")," trait are not allowed to implement the ",(0,r.kt)("inlineCode",{parentName:"p"},"Drop")," trait, so they can't run any custom code when they go out of scope.")),(0,r.kt)("h3",{id:"ownership-and-functions"},"Ownership and Functions"),(0,r.kt)("p",null,"We already saw that if you return a variable, then ownership of the variable is moved to the caller. We also move ownership when we pass a variable to a function:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'fn main() {\n let s = String::from("hello");\n takes_ownership(s);\n\n // Ownership of `s` was moved to `takes_ownership`\'s\n // `some_string`, so s is no longer valid here.\n}\n\nfn takes_ownership(some_string: String) {\n println!("{}", some_string);\n} // Here, some_string goes out of scope and `drop`\n // is called. The backing memory is freed.\n')),(0,r.kt)("h2",{id:"42---references-and-borrowing"},"4.2 - References and Borrowing"),(0,r.kt)("p",null,'If you wanted to pass a variable to a function, but also keep it usable afterwards, you could pass the variable to the function and then return it from the function. This would move the variable into the function, and then move it back so you can keep using it. As you can imagine, using a variable more than once is something we want to do pretty often, and if this was "the way" to do it, then Rust would be a very annoying language to work in. Instead we can let the function we call ',(0,r.kt)("em",{parentName:"p"},"borrow")," the variable by passing a reference:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'fn main() {\n let s1 = String::from("hello");\n\n let len = calculate_length(&s1);\n\n println!("The length of \'{}\' is {}.", s1, len);\n}\n\nfn calculate_length(s: &String) -> usize {\n s.len()\n}\n')),(0,r.kt)("p",null,"Two things to note here - when we call ",(0,r.kt)("inlineCode",{parentName:"p"},"calculate_length")," instead of passing ",(0,r.kt)("inlineCode",{parentName:"p"},"s1")," we're passing ",(0,r.kt)("inlineCode",{parentName:"p"},"&s1"),", and in the signature for ",(0,r.kt)("inlineCode",{parentName:"p"},"calculate_length")," we take a ",(0,r.kt)("inlineCode",{parentName:"p"},"&String")," instead of a ",(0,r.kt)("inlineCode",{parentName:"p"},"String"),'. What we\'re passing here is a "reference to a string". Essentially ',(0,r.kt)("inlineCode",{parentName:"p"},"&s1")," contains a pointer to the String held in ",(0,r.kt)("inlineCode",{parentName:"p"},"s1"),", and we're passing that pointer to ",(0,r.kt)("inlineCode",{parentName:"p"},"calculate_length"),". ",(0,r.kt)("inlineCode",{parentName:"p"},"calculate_length")," doesn't take ownership of the ",(0,r.kt)("inlineCode",{parentName:"p"},"String"),", it merely borrows it, so the ",(0,r.kt)("inlineCode",{parentName:"p"},"String")," won't be dropped when ",(0,r.kt)("inlineCode",{parentName:"p"},"s")," goes out of scope."),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"The syntax for getting a reference to a value - ",(0,r.kt)("inlineCode",{parentName:"p"},"&x")," - is exactly the same as getting a pointer to a value in C or Go, and references in Rust behave a lot like pointers. ",(0,r.kt)("a",{parentName:"p",href:"https://stackoverflow.com/questions/64167637/is-the-concept-of-reference-different-in-c-and-rust/64167719#64167719"},"This Stack Overflow answer")," talks about ways that Rust references compare to C/C++ pointers.")),(0,r.kt)("h3",{id:"mutable-references"},"Mutable References"),(0,r.kt)("p",null,"As with variables, we can have both immutable references (the default) and mutable references:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'fn main() {\n let mut s = String::from("hello");\n\n change(&mut s);\n}\n\nfn change(some_string: &mut String) {\n some_string.push_str(", world");\n}\n')),(0,r.kt)("p",null,"Mutable references come with a restriction: if you have a mutable reference to a value, you can have no other references to that value."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'let mut s = String::from("hello");\n\nlet r1 = &mut s;\nlet r2 = &mut s; // This is an error!\n\nprintln!("{}, {}", r1, r2);\n')),(0,r.kt)("p",null,'This restriction is imposed because it prevents data races. The compiler will stop us from creating data races at compile time! Some people prefer to think about references in terms of "shared references" and "exclusive references" in stead of as "immutable" and "mutable".'),(0,r.kt)("p",null,"The scope of a reference lasts only until it's last use, not until the end of the block, so this is fine:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'let mut s = String::from("hello");\n\nlet r1 = &mut s;\nprintln!("{}", r1);\n\nlet r2 = &mut s; // r1 is now out-of-scope, so we can create r2.\nprintln!("{}", r2);\n')),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"Where you place the ",(0,r.kt)("inlineCode",{parentName:"p"},"mut")," keyword changes how a reference can be used:"),(0,r.kt)("pre",{parentName:"admonition"},(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"// x1 is a reference to y. We can't update x or y:\nlet x1 = &y;\n// x2 is a reference that can be used to change y:\nlet x2 = &mut y;\n// x3 is is a reference that currently points to,\n// an immutable y, but we could change x3 to point\n// somewhere else.\nlet mut x3 = &y;\n// x4 is a reference that can be used to change y,\n// and can also be updated to point somewhere else.\nlet mut x4 = &mut y;\n"))),(0,r.kt)("h2",{id:"dereferencing"},"Dereferencing"),(0,r.kt)("p",null,"Rust has a ",(0,r.kt)("inlineCode",{parentName:"p"},"*")," operator for dereferencing, very similar to C++ or Go:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"let num1 = 7; // num1 has type `i32`.\nlet num2 = &num1; // num2 has type `&i32`.\nlet num3 = *num2; // num3 has type `i32` again.\n")),(0,r.kt)("p",null,"The ",(0,r.kt)("inlineCode",{parentName:"p"},"*")," follows the pointer (see ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch15-smart-pointers#following-the-pointer-to-the-value"},"chapter 15"),"). If the reference is mutable, we can use the ",(0,r.kt)("inlineCode",{parentName:"p"},"*")," operator to modify what the reference points to:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'let mut val = 10;\nlet val_ref = &mut val;\n*val_ref = 5;\n\n// Prints 5, because we used `val_ref` to modify `val`.\nprintln!("{val}");\n')),(0,r.kt)("h3",{id:"dangling-references"},"Dangling References"),(0,r.kt)("p",null,"You can't return a reference to an object that will be dropped:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'fn dangle() -> &String {\n let s = String::from("hello");\n &s // This is an error.\n}\n')),(0,r.kt)("p",null,"Here ",(0,r.kt)("inlineCode",{parentName:"p"},"s")," goes out of scope at the end of the function, so the String will be dropped. That means if Rust let us return a reference to the String, it would be a reference to memory that had already been reclaimed."),(0,r.kt)("p",null,"There's no ",(0,r.kt)("inlineCode",{parentName:"p"},"null")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"nil")," in Rust. You can't have a null pointer like you could in C. (Instead there's something called an ",(0,r.kt)("inlineCode",{parentName:"p"},"Option")," which we'll talk about in ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch06-enums-and-pattern-matching",title:"Chapter 6: Enums and Pattern Matching"},"chapter 6"),".)"),(0,r.kt)("h3",{id:"the-rules-of-references"},"The Rules of References"),(0,r.kt)("p",null,"To sum up what we learned above:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"At any given time, you can have ",(0,r.kt)("em",{parentName:"li"},"either")," one mutable reference ",(0,r.kt)("em",{parentName:"li"},"or")," any number of immutable references."),(0,r.kt)("li",{parentName:"ul"},"References must always be valid. You can't have references to dropped memory or null pointers.")),(0,r.kt)("h2",{id:"43---the-slice-type"},"4.3 - The Slice Type"),(0,r.kt)("p",null,"A ",(0,r.kt)("em",{parentName:"p"},"slice")," is a reference to a contiguous sequence of elements in a collection. Slices are references so they don't take ownership. The type of a string slice is ",(0,r.kt)("inlineCode",{parentName:"p"},"&str"),"."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'let s = String::from("hello world");\n\nlet hello = &s[0..5]; // Type of `hello` is `&str`.\nlet world = &s[6..11];\n')),(0,r.kt)("p",null,"The range syntax is ",(0,r.kt)("inlineCode",{parentName:"p"},"[inclusive..exclusive]"),". Or, in other words ",(0,r.kt)("inlineCode",{parentName:"p"},"[0..5]")," includes the zeroth character in the string, but omits the fifth. With the range syntax, you can omit the first number to start at 0, and omit the second number to end at the length of the string."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'let s = String::from("rust time");\n\nlet rust = &s[..4];\nlet time = &s[5..];\nlet rust_time = &s[..];\n')),(0,r.kt)("p",null,"Slices must occur at valid UTF-8 character boundaries. If you attempt to create a string slice in the middle of a multibyte character, your program will exit with an error. (Don't know what a multibyte character is? See ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch08-common-collections",title:"Chapter 8: Common Collections"},"chapter 8"),"!)"),(0,r.kt)("p",null,"Note that if you have a string slice, this counts as a reference, so you can't also have a mutable reference to that String:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'fn first_word(s: &String) -> &str {\n let bytes = s.as_bytes();\n\n for (i, &item) in bytes.iter().enumerate() {\n if item == b\' \' {\n return &s[0..i];\n }\n }\n\n &s[..]\n}\n\nfn main() {\n let mut s = String::from("hello world");\n\n // `word` ends up being a slice of `s`, so\n // `word` counts as a reference to `s`.\n let word = first_word(&s);\n\n s.clear(); // error!\n\n println!("the first word is: {}", word);\n}\n')),(0,r.kt)("p",null,"Inside ",(0,r.kt)("inlineCode",{parentName:"p"},"main"),", ",(0,r.kt)("inlineCode",{parentName:"p"},"word")," is a string slice from ",(0,r.kt)("inlineCode",{parentName:"p"},"s"),", and therefore a reference to the memory the ",(0,r.kt)("inlineCode",{parentName:"p"},"String")," uses. The call to ",(0,r.kt)("inlineCode",{parentName:"p"},"s.clear()")," will fail to compile because to clear the string, we'd need to mutate it (",(0,r.kt)("inlineCode",{parentName:"p"},"clear")," is a method with a mutable reference to ",(0,r.kt)("inlineCode",{parentName:"p"},"self"),"). Since we can't create a mutable reference while ",(0,r.kt)("inlineCode",{parentName:"p"},"word")," is in scope, this fails to compile."),(0,r.kt)("h3",{id:"string-literals-as-slices"},"String Literals as Slices"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},'let s = "Hello, world!";\n')),(0,r.kt)("p",null,"The type of ",(0,r.kt)("inlineCode",{parentName:"p"},"s")," here is ",(0,r.kt)("inlineCode",{parentName:"p"},"&str"),": it's a slice pointing to where this string is stored in the binary."),(0,r.kt)("h3",{id:"string-slices-as-parameters"},"String Slices as Parameters"),(0,r.kt)("p",null,"These two function signatures are very similar:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"fn first_word_string(s: &String) -> &str {...}\n\nfn first_word_str(s: &str) -> &str {...}\n")),(0,r.kt)("p",null,"The first takes a reference to a String, the second takes a string slice. The second one, though, is generally preferred. It's trivial to convert a string to a slice, so you can call the second with any ",(0,r.kt)("inlineCode",{parentName:"p"},"String"),", string slice, or string literal, or even a reference to a ",(0,r.kt)("inlineCode",{parentName:"p"},"String")," (see ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch15-smart-pointers",title:"Chapter 15: Smart Pointers"},"chapter 15")," for more on ",(0,r.kt)("em",{parentName:"p"},"deref coercion"),")."),(0,r.kt)("p",null,"In the reverse direction, it's a bit tedious to convert a string slice into a ",(0,r.kt)("inlineCode",{parentName:"p"},"String"),". As a result the first version, ",(0,r.kt)("inlineCode",{parentName:"p"},"first_word_string"),", is much less flexible."),(0,r.kt)("h3",{id:"other-slices"},"Other Slices"),(0,r.kt)("p",null,"Much like in Go, we can also create slices from arrays:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"},"let a = [1, 2, 3, 4, 5];\n\nlet slice = &a[1..3];\n\nassert_eq!(slice, &[2, 3]);\n")),(0,r.kt)("p",null,"The type of ",(0,r.kt)("inlineCode",{parentName:"p"},"slice")," here is ",(0,r.kt)("inlineCode",{parentName:"p"},"&[i32]"),"."),(0,r.kt)("p",null,"Continue to ",(0,r.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch05-structs",title:"Chapter 5: Using Structs to Structure Related Data"},"chapter 5"),"."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/f538945b.1f93a5a9.js b/assets/js/f538945b.1f93a5a9.js new file mode 100644 index 0000000..7773263 --- /dev/null +++ b/assets/js/f538945b.1f93a5a9.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkrust_book_abridged=self.webpackChunkrust_book_abridged||[]).push([[800],{3905:(e,t,n)=>{n.d(t,{Zo:()=>p,kt:()=>f});var i=n(7294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);t&&(i=i.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,i)}return n}function o(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var l=i.createContext({}),h=function(e){var t=i.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},p=function(e){var t=h(e.components);return i.createElement(l.Provider,{value:t},e.children)},m="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return i.createElement(i.Fragment,{},t)}},u=i.forwardRef((function(e,t){var n=e.components,a=e.mdxType,r=e.originalType,l=e.parentName,p=s(e,["components","mdxType","originalType","parentName"]),m=h(n),u=a,f=m["".concat(l,".").concat(u)]||m[u]||c[u]||r;return n?i.createElement(f,o(o({ref:t},p),{},{components:n})):i.createElement(f,o({ref:t},p))}));function f(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var r=n.length,o=new Array(r);o[0]=u;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[m]="string"==typeof e?e:a,o[1]=s;for(var h=2;h{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>c,frontMatter:()=>r,metadata:()=>s,toc:()=>h});var i=n(7462),a=(n(7294),n(3905));const r={},o="10.3 - Validating References with Lifetimes",s={unversionedId:"ch10/ch10-03-lifetimes",id:"ch10/ch10-03-lifetimes",title:"10.3 - Validating References with Lifetimes",description:"This chapter explains lifetimes in a somewhat different, and slightly more technical way than the original \"The Rust Programming Language\" did. If you find the explanation here confusing, you might try reading the original, or check the end of this section for some additional reading. Lifetimes can be one of the more confusing parts of Rust if you're a newcomer. They're a result of Rust's unique ownership system, so there aren't any direct analogs in other languages. As a result, many attempts have been made to explain them, so if you have a hard time don't give up! Somewhere out there, someone will explain this in a way that clicks for you.",source:"@site/docs/ch10/ch10-03-lifetimes.md",sourceDirName:"ch10",slug:"/ch10/ch10-03-lifetimes",permalink:"/rust-book-abridged/ch10/ch10-03-lifetimes",draft:!1,editUrl:"https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/docs/ch10/ch10-03-lifetimes.md",tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"10.2 - Traits: Defining Shared Behavior",permalink:"/rust-book-abridged/ch10/ch10-02-traits"},next:{title:"11 - Writing Automated Tests",permalink:"/rust-book-abridged/ch11-automated-tests"}},l={},h=[{value:"Let's Pretend We're Compilers",id:"lets-pretend-were-compilers",level:2},{value:"Some Terminology",id:"some-terminology",level:2},{value:"Preventing Dangling References with Lifetimes",id:"preventing-dangling-references-with-lifetimes",level:2},{value:"Lifetime Inheritance",id:"lifetime-inheritance",level:2},{value:"Lifetime Annotation Syntax",id:"lifetime-annotation-syntax",level:2},{value:"Generic Lifetimes in Functions",id:"generic-lifetimes-in-functions",level:2},{value:"Lifetime Annotations in Struct Definitions",id:"lifetime-annotations-in-struct-definitions",level:2},{value:"Lifetime Elision",id:"lifetime-elision",level:2},{value:"Lifetime Annotations in Method Definitions",id:"lifetime-annotations-in-method-definitions",level:2},{value:"The Static Lifetime",id:"the-static-lifetime",level:2},{value:"Generic Type Parameters, Trait Bounds, and Lifetimes Together",id:"generic-type-parameters-trait-bounds-and-lifetimes-together",level:2},{value:"Further Reading",id:"further-reading",level:2}],p={toc:h},m="wrapper";function c(e){let{components:t,...n}=e;return(0,a.kt)(m,(0,i.Z)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"103---validating-references-with-lifetimes"},"10.3 - Validating References with Lifetimes"),(0,a.kt)("admonition",{type:"info"},(0,a.kt)("p",{parentName:"admonition"},"This chapter explains lifetimes in a somewhat different, and slightly more technical way than the original \"The Rust Programming Language\" did. If you find the explanation here confusing, you might try reading the original, or check the end of this section for some additional reading. Lifetimes can be one of the more confusing parts of Rust if you're a newcomer. They're a result of Rust's unique ownership system, so there aren't any direct analogs in other languages. As a result, many attempts have been made to explain them, so if you have a hard time don't give up! Somewhere out there, someone will explain this in a way that clicks for you.")),(0,a.kt)("h2",{id:"lets-pretend-were-compilers"},"Let's Pretend We're Compilers"),(0,a.kt)("p",null,"Let's pretend we're compilers (beep boop!), and we want to do some type checking. Here's some code that we want to check:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-rust"},'fn biggest(x: &i32, y: &i32) -> &i32 {\n if *x > *y {\n x\n } else {\n y\n }\n}\n\nfn main() {\n let i1 = 7;\n let result;\n\n {\n let i2 = 8;\n result = biggest(&i1, &i2);\n }\n\n // This shouldn\'t compile! But how does rust know that?\n println!("The bigger value is {}", result);\n}\n')),(0,a.kt)("p",null,"If we're going to type check the call to ",(0,a.kt)("inlineCode",{parentName:"p"},"biggest")," in ",(0,a.kt)("inlineCode",{parentName:"p"},"main"),", we're going to go look at the signature for the ",(0,a.kt)("inlineCode",{parentName:"p"},"biggest")," function, and compare it to how it is being used. We're only going to look at the signature, because it's the contract for this function and it should contain everything we need to know (and we're busy compilers and we can't possibly be expected to delve into the actual implementation of ",(0,a.kt)("inlineCode",{parentName:"p"},"biggest"),"). We're going to see that ",(0,a.kt)("inlineCode",{parentName:"p"},"biggest")," takes two ",(0,a.kt)("inlineCode",{parentName:"p"},"&i32"),", and returns an ",(0,a.kt)("inlineCode",{parentName:"p"},"&i32"),". We're passing in two references to ",(0,a.kt)("inlineCode",{parentName:"p"},"i32"),"s, and we're assigning the result to ",(0,a.kt)("inlineCode",{parentName:"p"},"result")," which we can infer to be an ",(0,a.kt)("inlineCode",{parentName:"p"},"&i32"),"! So this is fine! Job done, on to the next function."),(0,a.kt)("p",null,"Well... except it isn't. If you follow through the execution of this code, we're going to end up assigning ",(0,a.kt)("inlineCode",{parentName:"p"},"result = &i2"),", and then we'll try dereference ",(0,a.kt)("inlineCode",{parentName:"p"},"result")," after ",(0,a.kt)("inlineCode",{parentName:"p"},"i2")," has been dropped, creating a use-after-freed error. How are we supposed to know this doesn't compile? We, fellow compilers, are missing some critical information here."),(0,a.kt)("p",null,"Up until know we've been leaving something out when we talk about references. We've written references as ",(0,a.kt)("inlineCode",{parentName:"p"},"&i32"),", but that is actually not the entire type of a reference. From a type safety perspective, a reference contains three things; the type of the target (here ",(0,a.kt)("inlineCode",{parentName:"p"},"i32"),"), whether or not this is a mutable exclusive reference (",(0,a.kt)("inlineCode",{parentName:"p"},"&")," vs ",(0,a.kt)("inlineCode",{parentName:"p"},"&mut"),"), and finally some information about how long that underlying value it is referring to lives. That last value is called the reference's ",(0,a.kt)("em",{parentName:"p"},"lifetime"),". All three of these things are part of the reference's type, and only when all three are taken into consideration can we make a decision about whether the correct arguments are being passed into or out of a function."),(0,a.kt)("h2",{id:"some-terminology"},"Some Terminology"),(0,a.kt)("p",null,"Before we go any further, let's define some terminology. We already know the scope (or ",(0,a.kt)("em",{parentName:"p"},"lexical scope"),") of a variable is the ",(0,a.kt)("inlineCode",{parentName:"p"},"{}")," around where the variable is declared; it's the part of the code where that variable binding is valid. The actual value that a variable is bound to also has a ",(0,a.kt)("em",{parentName:"p"},"scope")," (or ",(0,a.kt)("em",{parentName:"p"},"liveness scope"),") of a slightly different nature - it's the portion of the code where that value is valid. The scope of a value starts where the value is created and ends where the value is dropped."),(0,a.kt)("p",null,"A reference in Rust has a ",(0,a.kt)("em",{parentName:"p"},"lifetime"),", which is part of the reference's type. You can naively think of the lifetime as being as long as the liveness scope of the value the reference points to, and that will get you pretty far, but it would be better to think of the lifetime as being ",(0,a.kt)("em",{parentName:"p"},"at most")," as long as the scope of the value."),(0,a.kt)("admonition",{type:"info"},(0,a.kt)("p",{parentName:"admonition"},'Some other Rust documentation and articles calls the liveness scope the "lifetime of the value". Why do we use the term "scope" instead? A reference has a lifetime (shorter than or equal to the liveness scope of the value it refers to), but a reference is itself a kind of value with it\'s own liveness scope, and a reference is bound to a variable with a lexical scope (the liveness scope and lexical scope of a reference are the same, since references are ',(0,a.kt)("inlineCode",{parentName:"p"},"Copy"),"). If we used lifetime to talk about liveness scopes, then references would have two different lifetimes, and this chapter would get confusing very quickly.")),(0,a.kt)("h2",{id:"preventing-dangling-references-with-lifetimes"},"Preventing Dangling References with Lifetimes"),(0,a.kt)("p",null,"Let's take a step back and look at a simplified example. Here's some rust code that doesn't compile:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-rust"},'fn main() {\n let r;\n\n {\n let x = 5; // -+-- \'b\n r = &x; // |\n } // -+ \ud83d\udca5 `x` is freed\n\n println!("r: {}", r);\n}\n')),(0,a.kt)("p",null,"We're trying to create a reference ",(0,a.kt)("inlineCode",{parentName:"p"},"r")," and then use that reference after the value it points to has been dropped, so this shouldn't compile. But again - how does the compiler know this shouldn't compile? The reason is because ",(0,a.kt)("inlineCode",{parentName:"p"},"x")," has a liveness scope that corresponds to the ",(0,a.kt)("inlineCode",{parentName:"p"},"'b")," we've marked in the comments. That means ",(0,a.kt)("inlineCode",{parentName:"p"},"r")," here is not ",(0,a.kt)("em",{parentName:"p"},"just")," an ",(0,a.kt)("inlineCode",{parentName:"p"},"&i32"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"r")," has a lifetime which is equal to the ",(0,a.kt)("inlineCode",{parentName:"p"},"'b")," we've annotated above. We can think about r as being of the type ",(0,a.kt)("inlineCode",{parentName:"p"},"&'b i32"),"."),(0,a.kt)("p",null,"Since we're trying to access this ",(0,a.kt)("inlineCode",{parentName:"p"},"r")," outside of the ",(0,a.kt)("inlineCode",{parentName:"p"},"'b")," (which is part of ",(0,a.kt)("inlineCode",{parentName:"p"},"r"),"'s type), this is going to fail to compile."),(0,a.kt)("h2",{id:"lifetime-inheritance"},"Lifetime Inheritance"),(0,a.kt)("p",null,"We're also going to introduce a concept here called ",(0,a.kt)("em",{parentName:"p"},"lifetime inheritance"),". Lifetimes are a lot like objects in OO languages, in that one lifetime inherits from another. A larger lifetime inherits from a smaller lifetime."),(0,a.kt)("p",null,"No, we didn't get that backwards, although it is slightly unintuitive. In an Object Oriented world, we might say that a ",(0,a.kt)("inlineCode",{parentName:"p"},"Car")," inherits from ",(0,a.kt)("inlineCode",{parentName:"p"},"Vehicle"),", which means that anywhere you want a vehicle, you're allowed to use a car, but if you need a car, not just any vehicle will do. Similarly in Rust, the longer lifetime inherits from the shorter one; if you need a shorter lifetime, you can always convert a longer lifetime into a shorter one, but you can't safely convert a shorter one into a longer one. From a type-safety perspective, we would say if you have a function that takes a reference with a shorter lifetime, you can always type-coerce a longer one into a shorter one, but the reverse is not true."),(0,a.kt)("h2",{id:"lifetime-annotation-syntax"},"Lifetime Annotation Syntax"),(0,a.kt)("p",null,"Since the lifetime is part of the type of a reference, there's a way to name that lifetime in the code called a ",(0,a.kt)("em",{parentName:"p"},"lifetime annotation"),". Lifetime annotations are always written as part of a generic function or a generic struct, and much like generic types, lifetime annotations usually consist of a single letter:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-rust"},"&i32 // a reference\n&'a i32 // a reference with an explicit lifetime\n&'a mut i32 // a mutable reference with an explicit lifetime\n")),(0,a.kt)("h2",{id:"generic-lifetimes-in-functions"},"Generic Lifetimes in Functions"),(0,a.kt)("p",null,"Let's go back to our ",(0,a.kt)("inlineCode",{parentName:"p"},"biggest")," example from before, and fix it:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-rust"},"fn biggest<'a>(x: &'a i32, y: &'a i32) -> &'a i32 {\n if *x > *y {\n x\n } else {\n y\n }\n}\n\nfn main() {\n let i1 = 7;\n let result;\n\n {\n let i2 = 8;\n result = biggest(&i1, &i2);\n }\n\n // This shouldn't compile! But how does rust know that?\n println!(\"The bigger value is {}\", result);\n}\n")),(0,a.kt)("p",null,"We've written a generic function here, that's generic over some lifetime ",(0,a.kt)("inlineCode",{parentName:"p"},"'a"),". When the compiler tries to call ",(0,a.kt)("inlineCode",{parentName:"p"},"biggest"),", it needs two references with that lifetime, and it's going to return a reference with that same lifetime."),(0,a.kt)("p",null,"When the compiler looks at our call to ",(0,a.kt)("inlineCode",{parentName:"p"},"biggest"),", it treats this just like it would any other generic function, and tries to infer a value for ",(0,a.kt)("inlineCode",{parentName:"p"},"'a"),". The compiler sees we are assigning the returned ",(0,a.kt)("inlineCode",{parentName:"p"},"&'a i32")," to ",(0,a.kt)("inlineCode",{parentName:"p"},"result"),", so the lifetime ",(0,a.kt)("inlineCode",{parentName:"p"},"'a")," must be at least as long as the liveness scope of ",(0,a.kt)("inlineCode",{parentName:"p"},"result"),". The reference to ",(0,a.kt)("inlineCode",{parentName:"p"},"i1")," meets this criteria, but the reference to ",(0,a.kt)("inlineCode",{parentName:"p"},"i2")," doesn't - we can't type-coerce a shorter reference into a longer one - so the compiler gives us the error ",(0,a.kt)("inlineCode",{parentName:"p"},"`i2` does not live long enough"),". If you want to think about this from a more informal standpoint, what we're doing here is telling the compiler \"there exists some lifetime ",(0,a.kt)("inlineCode",{parentName:"p"},"'a")," which we're going to return, and the lifetime scopes of the two values referred to in the arguments needs to be at least that long.\""),(0,a.kt)("p",null,"It's important to note here that the liveness scopes of the underlying values that get passed in to the function in the caller don't have to be exactly the same. As we mentioned above, a reference is in itself a value. When you pass a reference into a function, what's actually happening down in the compiler is that we're passing a copy of the reference itself. When you create a new reference, you can think of it as having a lifetime equal to the liveness scope of whatever it points to. When you pass a reference, you can think of the copied reference as being converted into a new type which possibly has a shorter lifetime. When you call ",(0,a.kt)("inlineCode",{parentName:"p"},"biggest")," above, the two references you pass in might have different lifetimes, but inside ",(0,a.kt)("inlineCode",{parentName:"p"},"biggest")," we'll have two copied references which both have the same lifetime, and the return value will have the same lifetime too. This may seem like kind of an overly pedantic way to think about this, but if you have a structure with multiple references in it, thinking about it this way can really help you work through some convoluted error messages from the borrow checker."),(0,a.kt)("admonition",{type:"info"},(0,a.kt)("p",{parentName:"admonition"},"One important thing to note here is that lifetime annotations don't actually change the lifetime of the references passed in, they only gives the borrow checker enough information to work out whether a call is valid.")),(0,a.kt)("p",null,"The way we annotate lifetimes depends on what the function is doing. If we changed ",(0,a.kt)("inlineCode",{parentName:"p"},"biggest()")," to only ever return the first parameter, we would annotate the lifetimes as:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-rust"},"fn biggest<'a>(x: &'a i32, y: &'b i32) -> &'a i32 {\n x\n}\n")),(0,a.kt)("p",null,"This tells the compiler that the lifetime of the return value is the same as the lifetime of the first parameter, and the second parameter doesn't relate to the return value at all. If you walk through our example above with this new definition of ",(0,a.kt)("inlineCode",{parentName:"p"},"biggest"),", you'll see that this would compile - the lifetimes of the returned reference and the reference to ",(0,a.kt)("inlineCode",{parentName:"p"},"i1")," are the same, and the liveness scope of ",(0,a.kt)("inlineCode",{parentName:"p"},"i2")," doesn't matter."),(0,a.kt)("p",null,"In ",(0,a.kt)("em",{parentName:"p"},"most")," cases you will want the annotation in the return value to match the annotation of at least one parameter. See ",(0,a.kt)("a",{parentName:"p",href:"#the-static-lifetime"},"the ",(0,a.kt)("inlineCode",{parentName:"a"},"'static")," lifetime")," below!"),(0,a.kt)("h2",{id:"lifetime-annotations-in-struct-definitions"},"Lifetime Annotations in Struct Definitions"),(0,a.kt)("p",null,"So far all the structs we've created in this book have owned all their types. If we want to store a reference in a struct, we can, but we need to explicitly annotate it's lifetime. Just like a function, we do this with the generic syntax:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-rust"},"struct ImportantExcerpt<'a> {\n part: &'a str,\n}\n\nfn main() {\n let novel = String::from(\"Call me Ishmael. Some years ago...\");\n let first_sentence = novel.split('.').next().expect(\"Could not find a '.'\");\n\n let i = ImportantExcerpt {\n part: first_sentence,\n };\n}\n")),(0,a.kt)("p",null,"Again, it's helpful to think about this like we would any other generic declaration. When we write ",(0,a.kt)("inlineCode",{parentName:"p"},"ImportantExcerpt<'a>")," we are saying \"there exists some lifetime which we'll call ",(0,a.kt)("inlineCode",{parentName:"p"},"'a"),"\" - we don't know what that lifetime is yet, and we won't know until someone creates an actual instance of this struct. When we write ",(0,a.kt)("inlineCode",{parentName:"p"},"part: &'a str"),', we are saying "this ref has that same lifetime ',(0,a.kt)("inlineCode",{parentName:"p"},"'a"),'" (and, since you can update the contents of a struct, if someone later writes a new value to this ref, it must have a lifetime of at least ',(0,a.kt)("inlineCode",{parentName:"p"},"'a"),"). At compile time, the compiler will fill in the generic lifetimes with real lifetimes from your program, and then verify that the constraints hold."),(0,a.kt)("p",null,"Here's an example where a struct requires two different lifetime annotations (borrowed from ",(0,a.kt)("a",{parentName:"p",href:"https://stackoverflow.com/questions/29861388/when-is-it-useful-to-define-multiple-lifetimes-in-a-struct/66791361#66791361"},"this Stack Overflow discussion")," which has some other good examples too):"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-rust"},"struct Point<'a, 'b> {\n x: &'a i32,\n y: &'b i32,\n}\n\nfn main() {\n let x1 = 1;\n let v;\n {\n let y1 = 2;\n let f = Point { x: &x1, y: &y1 };\n v = f.x;\n }\n println!(\"{}\", *v);\n}\n")),(0,a.kt)("p",null,"One interesting thing here is that we're copying a reference out of a struct and then using it after the struct has been dropped. If we look at this through the lens of type safety, when we create a ",(0,a.kt)("inlineCode",{parentName:"p"},"Point"),", the lifetime of ",(0,a.kt)("inlineCode",{parentName:"p"},"f.x")," which we're calling ",(0,a.kt)("inlineCode",{parentName:"p"},"'a")," is going to be the same as the liveness scope of ",(0,a.kt)("inlineCode",{parentName:"p"},"x1"),". When we assign ",(0,a.kt)("inlineCode",{parentName:"p"},"v = f.x"),", the compiler will infer the type of ",(0,a.kt)("inlineCode",{parentName:"p"},"v")," as ",(0,a.kt)("inlineCode",{parentName:"p"},"&'a i32"),". When we dereference ",(0,a.kt)("inlineCode",{parentName:"p"},"v"),", we're still inside ",(0,a.kt)("inlineCode",{parentName:"p"},"'a")," (since this is the liveness scope of ",(0,a.kt)("inlineCode",{parentName:"p"},"x"),"), so this is an acceptable borrow."),(0,a.kt)("p",null,"If we rewrote the above struct as:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-rust"},"struct Point<'a> {\n x: &'a i32,\n y: &'a i32,\n}\n")),(0,a.kt)("p",null,"then this example would fail to compile. At compile time, the compiler infers the type of ",(0,a.kt)("inlineCode",{parentName:"p"},"v")," to be a ",(0,a.kt)("inlineCode",{parentName:"p"},"&'a i32")," where ",(0,a.kt)("inlineCode",{parentName:"p"},"'a"),"'s lifetime as long as the ",(0,a.kt)("inlineCode",{parentName:"p"},"main")," function, and then tries to type-coerce the reference to ",(0,a.kt)("inlineCode",{parentName:"p"},"x1")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"y1")," to be the same. Since the liveness scope of ",(0,a.kt)("inlineCode",{parentName:"p"},"y1")," is shorter than the lifetime that ",(0,a.kt)("inlineCode",{parentName:"p"},"v")," requires, it can't be coerced, so again we get the compiler error ",(0,a.kt)("inlineCode",{parentName:"p"},"`y1` does not live long enough"),"."),(0,a.kt)("admonition",{type:"tip"},(0,a.kt)("p",{parentName:"admonition"},"Similar to trait bounds, we can add a ",(0,a.kt)("a",{parentName:"p",href:"https://doc.rust-lang.org/reference/trait-bounds.html#lifetime-bounds"},(0,a.kt)("em",{parentName:"a"},"lifetime bound"))," to a lifetime annotation in a function or a struct."),(0,a.kt)("pre",{parentName:"admonition"},(0,a.kt)("code",{parentName:"pre",className:"language-rust"},"struct Point<'a, 'b: 'a> {\n x: &'a f32,\n y: &'b f32,\n}\n")),(0,a.kt)("p",{parentName:"admonition"},"You can read ",(0,a.kt)("inlineCode",{parentName:"p"},"'b: 'a"),' as "',(0,a.kt)("inlineCode",{parentName:"p"},"'b")," outlives ",(0,a.kt)("inlineCode",{parentName:"p"},"'a"),'", and this implies that ',(0,a.kt)("inlineCode",{parentName:"p"},"'b")," must be at least as long as ",(0,a.kt)("inlineCode",{parentName:"p"},"'a"),". There are very few cases where you would need to do such a thing, though.")),(0,a.kt)("h2",{id:"lifetime-elision"},"Lifetime Elision"),(0,a.kt)("p",null,"Way back in ",(0,a.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch04-ownership",title:"Chapter 4: Ownership, References, and Slices"},"chapter 4"),", we wrote this function:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-rust"},"fn first_word(s: &str) -> &str {\n let bytes = s.as_bytes();\n\n for (i, &item) in bytes.iter().enumerate() {\n if item == b' ' {\n return &s[0..i];\n }\n }\n\n &s[..]\n}\n")),(0,a.kt)("p",null,"How come this compiles without lifetime annotations? Why don't we have to tell the compiler that the return value has the same lifetime as ",(0,a.kt)("inlineCode",{parentName:"p"},"s"),"? Actually, in the pre-1.0 days of Rust, lifetime annotations would have been mandatory here. But there are certain cases where Rust can work out the lifetime on it's own. We call this ",(0,a.kt)("em",{parentName:"p"},"lifetime elision"),', because we are allowed to elide the annotations here. (Elide is a fancy programmer word for "omit" or "hide" or "leave out").'),(0,a.kt)("p",null,"When the compiler comes across a function with one or more references that are missing annotations, the compiler assigns a different lifetime to every missing annotation. If there is exactly one input lifetime parameter, that lifetime is automatically assigned to all output parameters. If there is more than one input lifetime parameter but one of them is for ",(0,a.kt)("inlineCode",{parentName:"p"},"&self"),", then the lifetime of ",(0,a.kt)("inlineCode",{parentName:"p"},"self")," is assigned to all output parameters. Otherwise, the compiler will error."),(0,a.kt)("p",null,"In the case above, there's only one lifetime that ",(0,a.kt)("inlineCode",{parentName:"p"},"first_word")," could really be returning; if ",(0,a.kt)("inlineCode",{parentName:"p"},"first_word")," created a new ",(0,a.kt)("inlineCode",{parentName:"p"},"String")," and tried to return a reference to it, the new ",(0,a.kt)("inlineCode",{parentName:"p"},"String")," would be dropped when we leave the function and the reference would be invalid. The only sensible reference for it to return comes from ",(0,a.kt)("inlineCode",{parentName:"p"},"s"),", so Rust infers this for us."),(0,a.kt)("p",null,"Lifetime elision can sometimes get things wrong. We could write a function that returns a static string for example, which would have the special lifetime ",(0,a.kt)("inlineCode",{parentName:"p"},"'static"),". In this case, we'd have to explicitly annotate the lifetimes in this function. But more often than not, lifetime elision guesses correctly, and in the cases where it doesn't we'll safely get a compiler error."),(0,a.kt)("h2",{id:"lifetime-annotations-in-method-definitions"},"Lifetime Annotations in Method Definitions"),(0,a.kt)("p",null,"We can add lifetime annotations to methods using the exact same generic syntax we use for functions:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-rust"},"impl<'a> ImportantExcerpt<'a> {\n fn level(&self) -> i32 {\n 3\n }\n\n fn announce_and_return_part(&self, announcement: &str) -> &'a str {\n println!(\"Attention please: {}\", announcement);\n self.part\n }\n}\n")),(0,a.kt)("p",null,"Since ",(0,a.kt)("inlineCode",{parentName:"p"},"part")," has a lifetime of ",(0,a.kt)("inlineCode",{parentName:"p"},"'a"),", we can explicitly tell the compiler that the returned value here will have that same lifetime. If we left out the ",(0,a.kt)("inlineCode",{parentName:"p"},"'a")," and just returned ",(0,a.kt)("inlineCode",{parentName:"p"},"&str"),", then lifetime elision would make the return value have the same lifetime as ",(0,a.kt)("inlineCode",{parentName:"p"},"&self")," - this would probably be OK too in most cases, but there could be cases where the lifetime of ",(0,a.kt)("inlineCode",{parentName:"p"},"'a")," is longer than the liveness scope of ",(0,a.kt)("inlineCode",{parentName:"p"},"self"),"."),(0,a.kt)("h2",{id:"the-static-lifetime"},"The Static Lifetime"),(0,a.kt)("p",null,"As we've hinted at above, there's a special lifetime called the ",(0,a.kt)("inlineCode",{parentName:"p"},"'static")," lifetime:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-rust"},'let s: &\'static str = "I have a static lifetime.";\n')),(0,a.kt)("p",null,"This is a slice of a string that's part of the program's binary, so it will always be available. Since a ",(0,a.kt)("inlineCode",{parentName:"p"},"'static")," lifetime is the length of the whole program, and a larger lifetime can always be coerced to a smaller one, you can always safely pass a static reference wherever a reference is required."),(0,a.kt)("p",null,"You may see the ",(0,a.kt)("inlineCode",{parentName:"p"},"'static")," lifetime mentioned in error messages when Rust suggests a fix, but unless you actually want a reference that lasts the life of your program, likely the real problem is that you're trying to create a dangling reference or there's lifetime mismatch."),(0,a.kt)("p",null,"You will also sometimes see ",(0,a.kt)("inlineCode",{parentName:"p"},"'static")," used as a trait bound:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-rust"},"fn print_it( input: impl Debug + 'static ) {\n println!( \"'static value passed in is: {:?}\", input );\n}\n")),(0,a.kt)("p",null,"This means something similar but subtly different to the ",(0,a.kt)("inlineCode",{parentName:"p"},"'static")," lifetime annotation. What this means is that the receiver can hang on to the reference for as long as they like - the reference will not become invalid until they drop it. You can always pass an ",(0,a.kt)("inlineCode",{parentName:"p"},"owned")," value to satisfy a ",(0,a.kt)("inlineCode",{parentName:"p"},"'static")," trait bound. If you move a value into ",(0,a.kt)("inlineCode",{parentName:"p"},"print_it"),", then the value lasts at least as long as ",(0,a.kt)("inlineCode",{parentName:"p"},"print_it")," will, so as far as ",(0,a.kt)("inlineCode",{parentName:"p"},"print_it")," is concerned, the value may as well last forever. You'll see a lot of examples of functions that take ",(0,a.kt)("inlineCode",{parentName:"p"},"'static")," values when we start looking at spawning new threads and dealing with async functions."),(0,a.kt)("h2",{id:"generic-type-parameters-trait-bounds-and-lifetimes-together"},"Generic Type Parameters, Trait Bounds, and Lifetimes Together"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-rust"},"use std::fmt::Display;\n\nfn longest_with_an_announcement<'a, T>(\n x: &'a str,\n y: &'a str,\n announcement: T,\n) -> &'a str\nwhere\n T: Display,\n{\n println!(\"Announcement! {}\", announcement);\n if x.len() > y.len() {\n x\n } else {\n y\n }\n}\n")),(0,a.kt)("p",null,"This takes two string slices and returns whichever is longer. It also prints an announcement, which is passed in as a parameter and can be any type that implements the ",(0,a.kt)("inlineCode",{parentName:"p"},"Display")," trait. (If someone showed you this code before you started reading this book, I wonder what you would have thought of all these random ",(0,a.kt)("inlineCode",{parentName:"p"},"'a"),"s scattered throughout the code?)"),(0,a.kt)("h2",{id:"further-reading"},"Further Reading"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"There are some advanced cases where lifetime annotations are required that we haven't discussed here (for example trait bounds sometimes require ",(0,a.kt)("a",{parentName:"li",href:"https://doc.rust-lang.org/stable/reference/types/trait-object.html#trait-object-lifetime-bounds"},"lifetime annotations"),", but they are usually inferred). ",(0,a.kt)("a",{parentName:"li",href:"https://doc.rust-lang.org/reference/index.html"},"The Rust Reference")," is a good place to read about this sort of thing when you're a little more comfortable with the language."),(0,a.kt)("li",{parentName:"ul"},"The Rustonomicon has a section on ",(0,a.kt)("a",{parentName:"li",href:"https://doc.rust-lang.org/nomicon/subtyping.html"},"Subtyping and Variance")," which goes into the technical details of how references work within the type system in much greater detail than this chapter did."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://mobiarch.wordpress.com/2015/06/29/understanding-lifetime-in-rust-part-i/"},"This excellent two part blog post")," gives another take on explaining lifetimes."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://stackoverflow.com/questions/27785671/why-can-the-lifetimes-not-be-elided-in-a-struct-definition/27785916#27785916"},"This stack overflow answer")," has an excellent explanation of how lifetime annotations work in structs."),(0,a.kt)("li",{parentName:"ul"},"One thing we didn't talk about in this chapter is reborrows.")),(0,a.kt)("p",null,"Continue to ",(0,a.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch11-automated-tests",title:"Chapter 11: Writing Automated Tests"},"chapter 11"),"."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/f538945b.6f7f54ac.js b/assets/js/f538945b.6f7f54ac.js deleted file mode 100644 index 54166c1..0000000 --- a/assets/js/f538945b.6f7f54ac.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkrust_book_abridged=self.webpackChunkrust_book_abridged||[]).push([[800],{3905:(e,t,n)=>{n.d(t,{Zo:()=>p,kt:()=>u});var i=n(7294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);t&&(i=i.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,i)}return n}function o(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var l=i.createContext({}),h=function(e){var t=i.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},p=function(e){var t=h(e.components);return i.createElement(l.Provider,{value:t},e.children)},m="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return i.createElement(i.Fragment,{},t)}},f=i.forwardRef((function(e,t){var n=e.components,a=e.mdxType,r=e.originalType,l=e.parentName,p=s(e,["components","mdxType","originalType","parentName"]),m=h(n),f=a,u=m["".concat(l,".").concat(f)]||m[f]||c[f]||r;return n?i.createElement(u,o(o({ref:t},p),{},{components:n})):i.createElement(u,o({ref:t},p))}));function u(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var r=n.length,o=new Array(r);o[0]=f;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[m]="string"==typeof e?e:a,o[1]=s;for(var h=2;h{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>c,frontMatter:()=>r,metadata:()=>s,toc:()=>h});var i=n(7462),a=(n(7294),n(3905));const r={},o="10.3 - Validating References with Lifetimes",s={unversionedId:"ch10/ch10-03-lifetimes",id:"ch10/ch10-03-lifetimes",title:"10.3 - Validating References with Lifetimes",description:"TODO: This section needs some rework. If you want to get deep into how lifetimes work from the compiler's perspective, this is a good read.",source:"@site/docs/ch10/ch10-03-lifetimes.md",sourceDirName:"ch10",slug:"/ch10/ch10-03-lifetimes",permalink:"/rust-book-abridged/ch10/ch10-03-lifetimes",draft:!1,editUrl:"https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/docs/ch10/ch10-03-lifetimes.md",tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"10.2 - Traits: Defining Shared Behavior",permalink:"/rust-book-abridged/ch10/ch10-02-traits"},next:{title:"11 - Writing Automated Tests",permalink:"/rust-book-abridged/ch11-automated-tests"}},l={},h=[{value:"Preventing Dangling References with Lifetimes",id:"preventing-dangling-references-with-lifetimes",level:2},{value:"Generic Lifetimes in Functions",id:"generic-lifetimes-in-functions",level:2},{value:"Lifetime Annotation Syntax",id:"lifetime-annotation-syntax",level:2},{value:"Lifetime Annotations in Function Signatures",id:"lifetime-annotations-in-function-signatures",level:2},{value:"Thinking in Terms of Lifetimes",id:"thinking-in-terms-of-lifetimes",level:2},{value:"Lifetime Annotations in Struct Definitions",id:"lifetime-annotations-in-struct-definitions",level:2},{value:"Lifetime Elision",id:"lifetime-elision",level:2},{value:"Lifetime Annotations in Method Definitions",id:"lifetime-annotations-in-method-definitions",level:2},{value:"The Static Lifetime",id:"the-static-lifetime",level:2},{value:"Generic Type Parameters, Trait Bounds, and Lifetimes Together",id:"generic-type-parameters-trait-bounds-and-lifetimes-together",level:2},{value:"Further Reading",id:"further-reading",level:2}],p={toc:h},m="wrapper";function c(e){let{components:t,...n}=e;return(0,a.kt)(m,(0,i.Z)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"103---validating-references-with-lifetimes"},"10.3 - Validating References with Lifetimes"),(0,a.kt)("admonition",{type:"info"},(0,a.kt)("p",{parentName:"admonition"},"TODO: This section needs some rework. If you want to get deep into how lifetimes work from the compiler's perspective, ",(0,a.kt)("a",{parentName:"p",href:"https://doc.rust-lang.org/nomicon/subtyping.html"},"this")," is a good read.")),(0,a.kt)("p",null,"Every value in Rust has a ",(0,a.kt)("em",{parentName:"p"},"lifetime")," - a point in the code where the value is created, and a point in the code where the value is destroyed. Every reference in Rust has two lifetimes - the lifetime of the reference itself (from where it's created until where it is last used) and a lifetime of the value it points to. The lifetime of the reference obviously needs to be shorter than the lifetime of the value, otherwise the reference will point to freed memory."),(0,a.kt)("p",null,"Just as rustc infers the type of many of our parameters, in most cases Rust can infer the lifetime of a reference (usually from when it is created until it's last use in a function). Just as we can explicitly annotate a variable's type, we can also explicitly annotate the lifetime of a reference in cases where the compiler can't infer what we want."),(0,a.kt)("h2",{id:"preventing-dangling-references-with-lifetimes"},"Preventing Dangling References with Lifetimes"),(0,a.kt)("p",null,"The main point of ownership is to prevent dangling references - to prevent us from accessing memory after it has been freed. Here's an example:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-rust"},"fn main() {\n let r; // ---------+-- 'a\n // |\n { // |\n let x = 5; // -+-- 'b |\n r = &x; // | |\n } // -+ |\n // |\n println!(\"r: {}\", r); // |\n} // ---------+\n")),(0,a.kt)("p",null,"This won't compile. The variable ",(0,a.kt)("inlineCode",{parentName:"p"},"r")," is in scope for the entire ",(0,a.kt)("inlineCode",{parentName:"p"},"main()")," function, but it's a reference to ",(0,a.kt)("inlineCode",{parentName:"p"},"x")," which will be dropped when we reach the end of the inner scope. After we reach the end of that inner scope, ",(0,a.kt)("inlineCode",{parentName:"p"},"r")," is now a reference to freed memory, so Rust's ",(0,a.kt)("em",{parentName:"p"},"borrow checker")," won't let us use it."),(0,a.kt)("p",null,"More formally, we can say that ",(0,a.kt)("inlineCode",{parentName:"p"},"r")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"x")," have different lifetimes, which we've marked in the comments of this example, using the labels ",(0,a.kt)("inlineCode",{parentName:"p"},"'a")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"'b")," (strange names, but this is actually a bit of foreshadowing). The borrow checker sees that ",(0,a.kt)("inlineCode",{parentName:"p"},"r")," has a lifetime of ",(0,a.kt)("inlineCode",{parentName:"p"},"'a"),", but references memory that has the lifetime ",(0,a.kt)("inlineCode",{parentName:"p"},"'b"),", and since ",(0,a.kt)("inlineCode",{parentName:"p"},"'b")," is shorter than ",(0,a.kt)("inlineCode",{parentName:"p"},"'a"),", the borrow checker won't allow this."),(0,a.kt)("p",null,"This version fixes the bug:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-rust"},"fn main() {\n let x = 5; // ----------+-- 'b\n // |\n let r = &x; // --+-- 'a |\n // | |\n println!(\"r: {}\", r); // | |\n // --+ |\n} // ----------+\n")),(0,a.kt)("p",null,"Here ",(0,a.kt)("inlineCode",{parentName:"p"},"x")," has a larger lifetime than ",(0,a.kt)("inlineCode",{parentName:"p"},"r"),", so ",(0,a.kt)("inlineCode",{parentName:"p"},"r")," can be a valid reference to ",(0,a.kt)("inlineCode",{parentName:"p"},"x"),"."),(0,a.kt)("h2",{id:"generic-lifetimes-in-functions"},"Generic Lifetimes in Functions"),(0,a.kt)("p",null,"Now for an example that doesn't compile, for what might not at first be obvious reasons. We're going to pass two string slices to a ",(0,a.kt)("inlineCode",{parentName:"p"},"longest()")," function, and it will return back whichever is longer:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-rust"},'fn main() {\n let string1 = String::from("abcd");\n let string2 = "xyz";\n\n let result = longest(string1.as_str(), string2);\n println!("The longest string is {}", result);\n}\n\n// This doesn\'t work!\nfn longest(x: &str, y: &str) -> &str {\n if x.len() > y.len() {\n x\n } else {\n y\n }\n}\n')),(0,a.kt)("p",null,"If we try to compile this, we get an error from the borrow checker. When the Rust compiler checks a call to a function, it doesn't look at the contents of the function, only at the signature. The root of the problem here is that in this function, the rust compiler doesn't know ahead of time whether we're going to return ",(0,a.kt)("inlineCode",{parentName:"p"},"x")," or ",(0,a.kt)("inlineCode",{parentName:"p"},"y"),", and since these may have different lifetimes, the compiler can't know how long the returned reference will be valid for. Consider this example of calling this function:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-rust"},'fn main() {\n let string1 = String::from("abcd");\n let result;\n\n {\n let string2 = String::from("xyz");\n result = longest(&string1, &string2);\n }\n\n // This shouldn\'t compile! But how does rust know that?\n println!("The longest string is {}", result);\n}\n')),(0,a.kt)("p",null,"Here if ",(0,a.kt)("inlineCode",{parentName:"p"},"longest()")," returned the reference to ",(0,a.kt)("inlineCode",{parentName:"p"},"string1"),", it would still be valid by the time we get to the ",(0,a.kt)("inlineCode",{parentName:"p"},"println!"),", but if it returned the reference to ",(0,a.kt)("inlineCode",{parentName:"p"},"string2")," it would not. How is the borrow checker supposed to decide if the call to ",(0,a.kt)("inlineCode",{parentName:"p"},"longest()")," is valid? The answer is that it can't. At least, not without a little help from us."),(0,a.kt)("h2",{id:"lifetime-annotation-syntax"},"Lifetime Annotation Syntax"),(0,a.kt)("p",null,"We fix this problem by telling the compiler about the relationship between these references. We do this with ",(0,a.kt)("em",{parentName:"p"},"lifetime annotations"),". Lifetime references are of the form ",(0,a.kt)("inlineCode",{parentName:"p"},"'a"),":"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-rust"},"&i32 // a reference\n&'a i32 // a reference with an explicit lifetime\n&'a mut i32 // a mutable reference with an explicit lifetime\n")),(0,a.kt)("p",null,"A lifetime annotation on a single variable isn't very meaningful. Lifetime annotations really describe a constraint on the relationship of references between multiple variables."),(0,a.kt)("h2",{id:"lifetime-annotations-in-function-signatures"},"Lifetime Annotations in Function Signatures"),(0,a.kt)("p",null,"We can declare a lifetime annotation for a function in much the same way we add generic types. The lifetime annotation must start with a ",(0,a.kt)("inlineCode",{parentName:"p"},"'"),". Typically they are single characters, much like generic types. And just like generic types, these will be filled in with a real lifetime for each call to the function."),(0,a.kt)("p",null,"We can fix the ",(0,a.kt)("inlineCode",{parentName:"p"},"longest()")," function in our previous example with:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-rust"},"fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {\n if x.len() > y.len() {\n x\n } else {\n y\n }\n}\n")),(0,a.kt)("p",null,"Think about this a bit like a generic function (the syntax is similar for a good reason). We're saying here there exists some lifetime which we're going to call ",(0,a.kt)("inlineCode",{parentName:"p"},"'a"),", and the variables ",(0,a.kt)("inlineCode",{parentName:"p"},"x")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"y")," both life at least as long as this hypothetical ",(0,a.kt)("inlineCode",{parentName:"p"},"'a"),". They don't have to both be the same lifetime, they just both have to be valid at the start and end of ",(0,a.kt)("inlineCode",{parentName:"p"},"'a"),". Then in the case of this function we're making the claim that the value we return is going to be valid for this same lifetime. At compile time, the compiler will see how long the passed in ",(0,a.kt)("inlineCode",{parentName:"p"},"x")," lives, how long the passed in ",(0,a.kt)("inlineCode",{parentName:"p"},"y")," lives, and then it will verify that the result of this function isn't used anywhere outside of that lifetime."),(0,a.kt)("p",null,"Putting this a bit more succinctly, we're telling the compiler that the return value of ",(0,a.kt)("inlineCode",{parentName:"p"},"longest()")," will live at least as long as the shorter lifetime of ",(0,a.kt)("inlineCode",{parentName:"p"},"x")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"y"),". When the rust compiler analyzes a call to ",(0,a.kt)("inlineCode",{parentName:"p"},"longest()")," it can now mark it as an error if the two parameters passed in don't adhere to this constraint."),(0,a.kt)("admonition",{type:"info"},(0,a.kt)("p",{parentName:"admonition"},"Lifetime annotations don't actually change the lifetime of the references passed in, it only gives the borrow checker enough information to work out whether a call is valid.")),(0,a.kt)("p",null,"Returning to this example:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-rust"},'fn main() {\n let string1 = String::from("abcd");\n let result;\n\n {\n let string2 = String::from("xyz");\n result = longest(&string1, &string2);\n }\n\n // This doesn\'t compile!\n println!("The longest string is {}", result);\n}\n')),(0,a.kt)("p",null,"Here the compiler now knows that the return value of ",(0,a.kt)("inlineCode",{parentName:"p"},"longest()")," is only as long as the shorter of ",(0,a.kt)("inlineCode",{parentName:"p"},"&string1")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"&string2"),", so it knows that the use of ",(0,a.kt)("inlineCode",{parentName:"p"},"result")," in the ",(0,a.kt)("inlineCode",{parentName:"p"},"println!")," macro is invalid."),(0,a.kt)("h2",{id:"thinking-in-terms-of-lifetimes"},"Thinking in Terms of Lifetimes"),(0,a.kt)("p",null,"The way we annotate lifetimes depends on what the function is doing. If we changed ",(0,a.kt)("inlineCode",{parentName:"p"},"longest()")," to only ever return the first parameter, we could annotate the lifetimes as:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-rust"},"fn longest<'a>(x: &'a str, y: &str) -> &'a str {\n x\n}\n")),(0,a.kt)("p",null,"This tells rustc that the lifetime of the return value is the same as the lifetime of the first parameter."),(0,a.kt)("p",null,"The lifetime of the return value must have the same annotation as at least one of the parameters (or be ",(0,a.kt)("inlineCode",{parentName:"p"},"'static"),", which we'll discuss in a moment). If you created a reference to something you create inside the function and return it, whatever you created will be dropped at the end of the function, so the reference will be invalid."),(0,a.kt)("h2",{id:"lifetime-annotations-in-struct-definitions"},"Lifetime Annotations in Struct Definitions"),(0,a.kt)("p",null,"So far all the structs we've created in this book have owned all their types. If we want to store a reference in a struct, we can, but we need to explicitly annotate it's lifetime. Just like a function, we do this with the generic syntax:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-rust"},"struct ImportantExcerpt<'a> {\n part: &'a str,\n}\n\nfn main() {\n let novel = String::from(\"Call me Ishmael. Some years ago...\");\n let first_sentence = novel.split('.').next().expect(\"Could not find a '.'\");\n\n let i = ImportantExcerpt {\n part: first_sentence,\n };\n}\n")),(0,a.kt)("p",null,"Again, it's helpful to think about this like we would any other generic declaration. When we write ",(0,a.kt)("inlineCode",{parentName:"p"},"ImportantExcerpt<'a>")," we are saying \"there exists some lifetime which we'll call ",(0,a.kt)("inlineCode",{parentName:"p"},"'a"),"\" - we don't know what that lifetime is yet, and we won't know until someone creates an actual instance of this struct. When we write ",(0,a.kt)("inlineCode",{parentName:"p"},"part: &'a str"),', we are saying "when someone reads this ref, it has the lifetime ',(0,a.kt)("inlineCode",{parentName:"p"},"'a"),'" (and if someone later writes a new value to this ref, it must have a lifetime of at least ',(0,a.kt)("inlineCode",{parentName:"p"},"'a"),"). At compile time, the compiler will fill in the generic lifetimes with real lifetimes from your program, and then verify that the constraints hold."),(0,a.kt)("p",null,"Here this struct has only a single reference, and so it might seem odd that we have to give an explicit lifetime for it. You might think the compiler could automatically figure out the lifetime here (and perhaps one day in this trivial example it will - Rust is evolving pretty rapidly)."),(0,a.kt)("admonition",{type:"info"},(0,a.kt)("p",{parentName:"admonition"},"The original ",(0,a.kt)("a",{parentName:"p",href:"https://doc.rust-lang.org/stable/book/ch10-03-lifetime-syntax.html#lifetime-annotations-in-struct-definitions"},'"The Rust Programming Language"'),' here said that "this annotation means an instance of ',(0,a.kt)("inlineCode",{parentName:"p"},"ImportantExcerpt")," can't outlive the reference it holds in its part field,\" but I found that not a helpful way to think about this - of course a struct can't outlive any references stored inside it. I found ",(0,a.kt)("a",{parentName:"p",href:"https://stackoverflow.com/questions/27785671/why-can-the-lifetimes-not-be-elided-in-a-struct-definition/27785916#27785916"},"this answer on Stack Overflow")," to be a lot more illuminating.")),(0,a.kt)("p",null,"Here's an example where a struct requires two different lifetime annotations (borrowed from ",(0,a.kt)("a",{parentName:"p",href:"https://stackoverflow.com/questions/29861388/when-is-it-useful-to-define-multiple-lifetimes-in-a-struct/66791361#66791361"},"this Stack Overflow discussion")," which has some other good examples too):"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-rust"},"struct Point<'a, 'b> {\n x: &'a i32,\n y: &'b i32,\n}\n\nfn main() {\n let x = 1;\n let v;\n {\n let y = 2;\n let f = Point { x: &x, y: &y };\n v = f.x;\n }\n println!(\"{}\", *v);\n}\n")),(0,a.kt)("p",null,"The interesting thing here is that we're copying a reference out of a struct and then using it after the struct has been dropped. This is okay because in this case the lifetime of the reference is longer than that of the struct. There's no way the compiler could know this without lifetime annotations. We we create the ",(0,a.kt)("inlineCode",{parentName:"p"},"Point<'a, 'b>")," here, the compiler fills in ",(0,a.kt)("inlineCode",{parentName:"p"},"'a")," with the lifetime of ",(0,a.kt)("inlineCode",{parentName:"p"},"x = 1"),", so when we do ",(0,a.kt)("inlineCode",{parentName:"p"},"v = f.x")," the compiler knows v also has that same lifetime. Also in this example, if you tried to give both ",(0,a.kt)("inlineCode",{parentName:"p"},"x")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"y")," the same lifetime annotation, this would fail to compile."),(0,a.kt)("admonition",{type:"tip"},(0,a.kt)("p",{parentName:"admonition"},"Similar to trait bounds, we can add a ",(0,a.kt)("a",{parentName:"p",href:"https://doc.rust-lang.org/reference/trait-bounds.html#lifetime-bounds"},(0,a.kt)("em",{parentName:"a"},"lifetime bound"))," to a lifetime annotation in a function or a struct."),(0,a.kt)("pre",{parentName:"admonition"},(0,a.kt)("code",{parentName:"pre",className:"language-rust"},"struct Point<'a, 'b: 'a> {\n x: &'a f32,\n y: &'b f32,\n}\n")),(0,a.kt)("p",{parentName:"admonition"},"You can read ",(0,a.kt)("inlineCode",{parentName:"p"},"'b: 'a"),' as "',(0,a.kt)("inlineCode",{parentName:"p"},"'b")," outlives ",(0,a.kt)("inlineCode",{parentName:"p"},"'a"),'", and this implies that ',(0,a.kt)("inlineCode",{parentName:"p"},"'b")," must be at least as long as ",(0,a.kt)("inlineCode",{parentName:"p"},"'a"),". There are very few cases where you would need to do such a thing, though.")),(0,a.kt)("h2",{id:"lifetime-elision"},"Lifetime Elision"),(0,a.kt)("p",null,"Way back in ",(0,a.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch04-ownership",title:"Chapter 4: Ownership, References, and Slices"},"chapter 4"),", we wrote this function:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-rust"},"fn first_word(s: &str) -> &str {\n let bytes = s.as_bytes();\n\n for (i, &item) in bytes.iter().enumerate() {\n if item == b' ' {\n return &s[0..i];\n }\n }\n\n &s[..]\n}\n")),(0,a.kt)("p",null,"How come this compiles without lifetime annotations? Why don't we have to tell the compiler that the return value has the same lifetime as ",(0,a.kt)("inlineCode",{parentName:"p"},"s"),"? Actually, in the pre-1.0 days of Rust, lifetime annotations would have been mandatory here. But there are certain cases where Rust can now work out the lifetime on it's own. We call this ",(0,a.kt)("em",{parentName:"p"},"lifetime elision"),", and say that the compiler ",(0,a.kt)("em",{parentName:"p"},"elides")," these lifetime annotations for us."),(0,a.kt)("p",null,"What the compiler does is to assign a different lifetime to every reference in the parameter list (",(0,a.kt)("inlineCode",{parentName:"p"},"'a")," for the first one, ",(0,a.kt)("inlineCode",{parentName:"p"},"'b")," for the second, and so on...). If there is exactly one input lifetime parameter, that lifetime is automatically assigned to all output parameters. If there is more than one input lifetime parameter but one of them is for ",(0,a.kt)("inlineCode",{parentName:"p"},"&self"),", then the lifetime of ",(0,a.kt)("inlineCode",{parentName:"p"},"self")," is assigned to all output parameters. Otherwise, the compiler will error."),(0,a.kt)("p",null,"In the case above, there's only one lifetime that ",(0,a.kt)("inlineCode",{parentName:"p"},"first_word")," could really be returning; if ",(0,a.kt)("inlineCode",{parentName:"p"},"first_word")," created a new ",(0,a.kt)("inlineCode",{parentName:"p"},"String")," and tried to return a reference to it, the new ",(0,a.kt)("inlineCode",{parentName:"p"},"String")," would be dropped when we leave the function and the reference would be invalid. The only sensible reference for it to return comes from ",(0,a.kt)("inlineCode",{parentName:"p"},"s"),", so Rust infers this for us. (It ",(0,a.kt)("em",{parentName:"p"},"could")," be a static lifetime, but if it were we'd have to explicitly annotate it as such.)"),(0,a.kt)("h2",{id:"lifetime-annotations-in-method-definitions"},"Lifetime Annotations in Method Definitions"),(0,a.kt)("p",null,"We can add lifetime annotations to methods using the exact same generic syntax we use for generic structs:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-rust"},"impl<'a> ImportantExcerpt<'a> {\n fn level(&self) -> i32 {\n 3\n }\n\n fn announce_and_return_part(&self, announcement: &str) -> &str {\n println!(\"Attention please: {}\", announcement);\n self.part\n }\n}\n")),(0,a.kt)("p",null,"Here ",(0,a.kt)("inlineCode",{parentName:"p"},"'a")," refers to the lifetime of the struct itself, but thanks to lifetime elision, in ",(0,a.kt)("inlineCode",{parentName:"p"},"announce_and_return_part()"),", the return value is automatically given the same lifetime as ",(0,a.kt)("inlineCode",{parentName:"p"},"self,")," so we don't actually have to use it."),(0,a.kt)("h2",{id:"the-static-lifetime"},"The Static Lifetime"),(0,a.kt)("p",null,"There's a special lifetime called the ",(0,a.kt)("inlineCode",{parentName:"p"},"'static")," lifetime:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-rust"},'let s: &\'static str = "I have a static lifetime.";\n')),(0,a.kt)("p",null,"This is a slice of a string that's part of the program's binary, so it will always be available. you may see the ",(0,a.kt)("inlineCode",{parentName:"p"},"'static")," lifetime mentioned in error messages when Rust suggests a fix, but unless you actually want a reference that lasts the life of your program, likely the real problem is that you're trying to create a dangling reference or there's lifetime mismatch."),(0,a.kt)("h2",{id:"generic-type-parameters-trait-bounds-and-lifetimes-together"},"Generic Type Parameters, Trait Bounds, and Lifetimes Together"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-rust"},"use std::fmt::Display;\n\nfn longest_with_an_announcement<'a, T>(\n x: &'a str,\n y: &'a str,\n announcement: T,\n) -> &'a str\nwhere\n T: Display,\n{\n println!(\"Announcement! {}\", announcement);\n if x.len() > y.len() {\n x\n } else {\n y\n }\n}\n")),(0,a.kt)("p",null,"This takes two string slices and returns whichever is longer. It also prints an announcement, which is passed in as a parameter and can be any type that implements the ",(0,a.kt)("inlineCode",{parentName:"p"},"Display")," trait. (If someone showed you this code before you started reading this book, I wonder what would you have thought it meant?)"),(0,a.kt)("h2",{id:"further-reading"},"Further Reading"),(0,a.kt)("p",null,"There are some advanced cases where lifetime annotations are required that we haven't discussed here (for example trait bounds sometimes require ",(0,a.kt)("a",{parentName:"p",href:"https://doc.rust-lang.org/stable/reference/types/trait-object.html#trait-object-lifetime-bounds"},"lifetime annotations"),", but they are usually inferred). ",(0,a.kt)("a",{parentName:"p",href:"https://doc.rust-lang.org/reference/index.html"},"The Rust Reference")," is a good place to read about this sort of thing when you're a little more comfortable with the language."),(0,a.kt)("p",null,"Lifetimes and ownership are such a central and important part of Rust that I'll also direct you to ",(0,a.kt)("a",{parentName:"p",href:"https://mobiarch.wordpress.com/2015/06/29/understanding-lifetime-in-rust-part-i/"},"this excellent two part blog post")," on the subject."),(0,a.kt)("p",null,"Continue to ",(0,a.kt)("a",{parentName:"p",href:"/rust-book-abridged/ch11-automated-tests",title:"Chapter 11: Writing Automated Tests"},"chapter 11"),"."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/runtime~main.ce1f3172.js b/assets/js/runtime~main.801a8238.js similarity index 79% rename from assets/js/runtime~main.ce1f3172.js rename to assets/js/runtime~main.801a8238.js index 7a4221c..4848b44 100644 --- a/assets/js/runtime~main.ce1f3172.js +++ b/assets/js/runtime~main.801a8238.js @@ -1 +1 @@ -(()=>{"use strict";var e,a,r,t,f,c={},o={};function d(e){var a=o[e];if(void 0!==a)return a.exports;var r=o[e]={exports:{}};return c[e].call(r.exports,r,r.exports,d),r.exports}d.m=c,e=[],d.O=(a,r,t,f)=>{if(!r){var c=1/0;for(i=0;i=f)&&Object.keys(d.O).every((e=>d.O[e](r[b])))?r.splice(b--,1):(o=!1,f0&&e[i-1][2]>f;i--)e[i]=e[i-1];e[i]=[r,t,f]},d.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return d.d(a,{a:a}),a},r=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,d.t=function(e,t){if(1&t&&(e=this(e)),8&t)return e;if("object"==typeof e&&e){if(4&t&&e.__esModule)return e;if(16&t&&"function"==typeof e.then)return e}var f=Object.create(null);d.r(f);var c={};a=a||[null,r({}),r([]),r(r)];for(var o=2&t&&e;"object"==typeof o&&!~a.indexOf(o);o=r(o))Object.getOwnPropertyNames(o).forEach((a=>c[a]=()=>e[a]));return c.default=()=>e,d.d(f,c),f},d.d=(e,a)=>{for(var r in a)d.o(a,r)&&!d.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:a[r]})},d.f={},d.e=e=>Promise.all(Object.keys(d.f).reduce(((a,r)=>(d.f[r](e,a),a)),[])),d.u=e=>"assets/js/"+({32:"fee577c3",53:"935f2afb",71:"8e026a77",164:"3ae36f34",194:"c9ac6c0c",202:"6ac76007",204:"37e2aa61",233:"96675f22",253:"f00e3d18",256:"96a8cd58",257:"2d642e4b",293:"99017798",298:"450c2548",363:"7ef24fb9",380:"0837bd7b",392:"576f4107",455:"d5e00032",462:"25ec9f74",482:"194e270a",487:"2f9ac6b0",499:"2007ba17",514:"1be78505",528:"c670d531",553:"79380536",588:"d8a9c2da",607:"3877a77c",617:"44c64225",622:"e1f7aa73",651:"389e9a43",705:"c5b4ab6d",757:"980825f2",800:"f538945b",809:"829cc90e",814:"77d9c050",817:"14eb3368",849:"9bc4d0f4",853:"f887f21f",858:"4d2530c0",899:"fbf6ccac",900:"29b8f2a5",915:"fefb6c5f",918:"17896441",932:"0947c9df",946:"17eb3d7c",957:"2803f7dd"}[e]||e)+"."+{32:"5bc05bf3",53:"ff231fb8",71:"209504d6",164:"6e95c59d",194:"67da69c1",202:"8cd013ab",204:"aca53660",233:"e0297e82",253:"1ba0639b",256:"2f4340fa",257:"6a9ac9f0",293:"7cf6f8bb",298:"732cbea5",363:"645b5576",380:"86a1ce03",392:"73f91c22",455:"80e349f5",462:"a3b9db90",482:"805743fe",487:"331dc5cb",499:"34ced172",514:"3cd8f0d6",528:"78c57de5",553:"d486a9b1",588:"e0cfb1d9",607:"2f624ea3",617:"bc77b70a",622:"c13e8ea9",651:"c8b52388",705:"d81f3fa8",757:"a5ebd93a",800:"6f7f54ac",809:"95720db2",814:"1535341d",817:"6674e289",849:"023de4ee",853:"a8df9845",858:"afc2f5c6",899:"f4422f7f",900:"4f64cf5d",915:"b27c49fd",918:"a666c0dd",932:"ef31668f",946:"66a0bdd8",957:"49ea6a44",972:"b08bfb73"}[e]+".js",d.miniCssF=e=>{},d.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),d.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),t={},f="rust-book-abridged:",d.l=(e,a,r,c)=>{if(t[e])t[e].push(a);else{var o,b;if(void 0!==r)for(var n=document.getElementsByTagName("script"),i=0;i{o.onerror=o.onload=null,clearTimeout(s);var f=t[e];if(delete t[e],o.parentNode&&o.parentNode.removeChild(o),f&&f.forEach((e=>e(r))),a)return a(r)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:o}),12e4);o.onerror=l.bind(null,o.onerror),o.onload=l.bind(null,o.onload),b&&document.head.appendChild(o)}},d.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},d.p="/rust-book-abridged/",d.gca=function(e){return e={17896441:"918",79380536:"553",99017798:"293",fee577c3:"32","935f2afb":"53","8e026a77":"71","3ae36f34":"164",c9ac6c0c:"194","6ac76007":"202","37e2aa61":"204","96675f22":"233",f00e3d18:"253","96a8cd58":"256","2d642e4b":"257","450c2548":"298","7ef24fb9":"363","0837bd7b":"380","576f4107":"392",d5e00032:"455","25ec9f74":"462","194e270a":"482","2f9ac6b0":"487","2007ba17":"499","1be78505":"514",c670d531:"528",d8a9c2da:"588","3877a77c":"607","44c64225":"617",e1f7aa73:"622","389e9a43":"651",c5b4ab6d:"705","980825f2":"757",f538945b:"800","829cc90e":"809","77d9c050":"814","14eb3368":"817","9bc4d0f4":"849",f887f21f:"853","4d2530c0":"858",fbf6ccac:"899","29b8f2a5":"900",fefb6c5f:"915","0947c9df":"932","17eb3d7c":"946","2803f7dd":"957"}[e]||e,d.p+d.u(e)},(()=>{var e={303:0,532:0};d.f.j=(a,r)=>{var t=d.o(e,a)?e[a]:void 0;if(0!==t)if(t)r.push(t[2]);else if(/^(303|532)$/.test(a))e[a]=0;else{var f=new Promise(((r,f)=>t=e[a]=[r,f]));r.push(t[2]=f);var c=d.p+d.u(a),o=new Error;d.l(c,(r=>{if(d.o(e,a)&&(0!==(t=e[a])&&(e[a]=void 0),t)){var f=r&&("load"===r.type?"missing":r.type),c=r&&r.target&&r.target.src;o.message="Loading chunk "+a+" failed.\n("+f+": "+c+")",o.name="ChunkLoadError",o.type=f,o.request=c,t[1](o)}}),"chunk-"+a,a)}},d.O.j=a=>0===e[a];var a=(a,r)=>{var t,f,c=r[0],o=r[1],b=r[2],n=0;if(c.some((a=>0!==e[a]))){for(t in o)d.o(o,t)&&(d.m[t]=o[t]);if(b)var i=b(d)}for(a&&a(r);n{"use strict";var e,a,r,t,f,c={},o={};function d(e){var a=o[e];if(void 0!==a)return a.exports;var r=o[e]={exports:{}};return c[e].call(r.exports,r,r.exports,d),r.exports}d.m=c,e=[],d.O=(a,r,t,f)=>{if(!r){var c=1/0;for(i=0;i=f)&&Object.keys(d.O).every((e=>d.O[e](r[n])))?r.splice(n--,1):(o=!1,f0&&e[i-1][2]>f;i--)e[i]=e[i-1];e[i]=[r,t,f]},d.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return d.d(a,{a:a}),a},r=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,d.t=function(e,t){if(1&t&&(e=this(e)),8&t)return e;if("object"==typeof e&&e){if(4&t&&e.__esModule)return e;if(16&t&&"function"==typeof e.then)return e}var f=Object.create(null);d.r(f);var c={};a=a||[null,r({}),r([]),r(r)];for(var o=2&t&&e;"object"==typeof o&&!~a.indexOf(o);o=r(o))Object.getOwnPropertyNames(o).forEach((a=>c[a]=()=>e[a]));return c.default=()=>e,d.d(f,c),f},d.d=(e,a)=>{for(var r in a)d.o(a,r)&&!d.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:a[r]})},d.f={},d.e=e=>Promise.all(Object.keys(d.f).reduce(((a,r)=>(d.f[r](e,a),a)),[])),d.u=e=>"assets/js/"+({32:"fee577c3",53:"935f2afb",71:"8e026a77",164:"3ae36f34",194:"c9ac6c0c",202:"6ac76007",204:"37e2aa61",233:"96675f22",253:"f00e3d18",256:"96a8cd58",257:"2d642e4b",293:"99017798",298:"450c2548",363:"7ef24fb9",380:"0837bd7b",392:"576f4107",455:"d5e00032",462:"25ec9f74",482:"194e270a",487:"2f9ac6b0",499:"2007ba17",514:"1be78505",528:"c670d531",553:"79380536",588:"d8a9c2da",607:"3877a77c",617:"44c64225",622:"e1f7aa73",651:"389e9a43",705:"c5b4ab6d",757:"980825f2",800:"f538945b",809:"829cc90e",814:"77d9c050",817:"14eb3368",849:"9bc4d0f4",853:"f887f21f",858:"4d2530c0",899:"fbf6ccac",900:"29b8f2a5",915:"fefb6c5f",918:"17896441",932:"0947c9df",946:"17eb3d7c",957:"2803f7dd"}[e]||e)+"."+{32:"5bc05bf3",53:"e94cfbb0",71:"209504d6",164:"6e95c59d",194:"67da69c1",202:"8cd013ab",204:"aca53660",233:"a990bd42",253:"1ba0639b",256:"2f4340fa",257:"6a9ac9f0",293:"7cf6f8bb",298:"732cbea5",363:"645b5576",380:"37ea0968",392:"73f91c22",455:"80e349f5",462:"2326306c",482:"805743fe",487:"331dc5cb",499:"34ced172",514:"3cd8f0d6",528:"4757bcce",553:"d486a9b1",588:"e0cfb1d9",607:"2f624ea3",617:"bc77b70a",622:"c13e8ea9",651:"c8b52388",705:"d81f3fa8",757:"a5ebd93a",800:"1f93a5a9",809:"75d46d74",814:"1535341d",817:"6674e289",849:"023de4ee",853:"a8df9845",858:"afc2f5c6",899:"f4422f7f",900:"4f64cf5d",915:"b27c49fd",918:"a666c0dd",932:"ef31668f",946:"66a0bdd8",957:"49ea6a44",972:"b08bfb73"}[e]+".js",d.miniCssF=e=>{},d.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),d.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),t={},f="rust-book-abridged:",d.l=(e,a,r,c)=>{if(t[e])t[e].push(a);else{var o,n;if(void 0!==r)for(var b=document.getElementsByTagName("script"),i=0;i{o.onerror=o.onload=null,clearTimeout(s);var f=t[e];if(delete t[e],o.parentNode&&o.parentNode.removeChild(o),f&&f.forEach((e=>e(r))),a)return a(r)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:o}),12e4);o.onerror=l.bind(null,o.onerror),o.onload=l.bind(null,o.onload),n&&document.head.appendChild(o)}},d.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},d.p="/rust-book-abridged/",d.gca=function(e){return e={17896441:"918",79380536:"553",99017798:"293",fee577c3:"32","935f2afb":"53","8e026a77":"71","3ae36f34":"164",c9ac6c0c:"194","6ac76007":"202","37e2aa61":"204","96675f22":"233",f00e3d18:"253","96a8cd58":"256","2d642e4b":"257","450c2548":"298","7ef24fb9":"363","0837bd7b":"380","576f4107":"392",d5e00032:"455","25ec9f74":"462","194e270a":"482","2f9ac6b0":"487","2007ba17":"499","1be78505":"514",c670d531:"528",d8a9c2da:"588","3877a77c":"607","44c64225":"617",e1f7aa73:"622","389e9a43":"651",c5b4ab6d:"705","980825f2":"757",f538945b:"800","829cc90e":"809","77d9c050":"814","14eb3368":"817","9bc4d0f4":"849",f887f21f:"853","4d2530c0":"858",fbf6ccac:"899","29b8f2a5":"900",fefb6c5f:"915","0947c9df":"932","17eb3d7c":"946","2803f7dd":"957"}[e]||e,d.p+d.u(e)},(()=>{var e={303:0,532:0};d.f.j=(a,r)=>{var t=d.o(e,a)?e[a]:void 0;if(0!==t)if(t)r.push(t[2]);else if(/^(303|532)$/.test(a))e[a]=0;else{var f=new Promise(((r,f)=>t=e[a]=[r,f]));r.push(t[2]=f);var c=d.p+d.u(a),o=new Error;d.l(c,(r=>{if(d.o(e,a)&&(0!==(t=e[a])&&(e[a]=void 0),t)){var f=r&&("load"===r.type?"missing":r.type),c=r&&r.target&&r.target.src;o.message="Loading chunk "+a+" failed.\n("+f+": "+c+")",o.name="ChunkLoadError",o.type=f,o.request=c,t[1](o)}}),"chunk-"+a,a)}},d.O.j=a=>0===e[a];var a=(a,r)=>{var t,f,c=r[0],o=r[1],n=r[2],b=0;if(c.some((a=>0!==e[a]))){for(t in o)d.o(o,t)&&(d.m[t]=o[t]);if(n)var i=n(d)}for(a&&a(r);b 10 - Generic Types, Traits, and Lifetimes | The rs Book - +
-
- +
+ \ No newline at end of file diff --git a/category/19---advanced-features/index.html b/category/19---advanced-features/index.html index 2d673f7..60442b4 100644 --- a/category/19---advanced-features/index.html +++ b/category/19---advanced-features/index.html @@ -4,13 +4,13 @@ 19 - Advanced Features | The rs Book - +
- + \ No newline at end of file diff --git a/category/20---multithreaded-web-server/index.html b/category/20---multithreaded-web-server/index.html index 3511581..a5e0e2a 100644 --- a/category/20---multithreaded-web-server/index.html +++ b/category/20---multithreaded-web-server/index.html @@ -4,13 +4,13 @@ 20 - Multithreaded Web Server | The rs Book - +
- + \ No newline at end of file diff --git a/category/appendix/index.html b/category/appendix/index.html index a03a1c7..fca4f33 100644 --- a/category/appendix/index.html +++ b/category/appendix/index.html @@ -4,13 +4,13 @@ Appendix | The rs Book - +
- + \ No newline at end of file diff --git a/ch01-getting-started/index.html b/ch01-getting-started/index.html index 23555c1..0792294 100644 --- a/ch01-getting-started/index.html +++ b/ch01-getting-started/index.html @@ -4,13 +4,13 @@ 1 - Getting Started | The rs Book - +

1 - Getting Started

This chapter is going to get Rust installed, and explain how to use cargo to create and build a new project.

1.1 - Installation

On Linux or MacOS:

curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
xcode-select --install

On Windows, you can still use rustup, but check the official documentation for how to go about installing it.

Verify your installation with rustc --version. Upgrade with rustup update.

1.2 - Hello, World!

Tradition dictates that we can't learn a new programming language without starting with a program that prints "Hello, world!". Let's create a file called "main.rs":

main.rs
fn main() {
println!("Hello, world!");
}

Indenting in Rust is done with four spaces, not tabs, and statements end with ";". Here we're calling the println! macro - you can tell its a macro because it ends with !. You can run this with:

$ rustc main.rs
$ ./main
Hello, world!

rustc compiles the source to an executable called main, and then ./main runs the executable.

1.3 - Hello, Cargo!

Cargo is Rust's build system and package manager. It's a bit like npm in JavaScript, or the go command in Go.

Creating a Project with Cargo

$ cargo new hello_cargo
$ cd hello_cargo

This creates a Cargo.toml file and a src/main.rs file inside a new folder, and also initializes the new folder as a git repo. Cargo.toml is a toml file (which looks a lot like an old-fashioned Windows .ini file). The [package] section of Cargo.toml describes metadata about the current package, and the [dependencies] section lists any libraries (aka crates) that your project depends on.

We can build and run this project with:

$ cargo build
$ ./target/debug/hello_cargo

Or with this shortcut, which is equivalent to the above:

$ cargo run

Note that cargo build creates a Cargo.lock file - this is a dependency lockfile which means if you share this project with a friend, and they cargo build, they'll get exactly the same dependencies that you did. It's a good idea to commit the lockfile to your source code management tool.

You can verify that a project compiles without producing an executable with cargo check, which is often much faster than cargo build. You can build a "release version" of your code with cargo build --release which will generate an executable in target/release instead of target/debug. The release version will be missing symbols and some runtime safety checks, and will be better optimized.

Continue to chapter 2.

- + \ No newline at end of file diff --git a/ch02-guessing-game/index.html b/ch02-guessing-game/index.html index c3838a8..e41e032 100644 --- a/ch02-guessing-game/index.html +++ b/ch02-guessing-game/index.html @@ -4,13 +4,13 @@ 2 - Programming a Guessing Game | The rs Book - +

2 - Programming a Guessing Game

This chapter creates a little "guessing game" program. The program picks a random number, you try to guess the secret number, and the program will tell you if you're too high or too low. Hours of fun! We're going to introduce a bunch of concepts but not go into anything in too much detail in this chapter.

Create the project with:

$ cargo new guessing_game
$ cd guessing_game

To start, let's just worry about getting some user input. Open up src/main.rs in your favorite text editor and copy/paste the following:

src/main.rs
use std::io;

fn main() {
println!("Guess the number!");

println!("Please input your guess.");

let mut guess = String::new();

io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");

println!("You guessed: {guess}");
}

You can run this with cargo run, and it should ask you to enter a value and then print out what you entered.

In order to read user input, we're using the io library from the standard library, but to reference io more conveniently we bring it into scope. We do this with the first line, use std::io. This is a bit like an import statement in python or Java, but note that that don't need to explicitly import io to use it. We could remove the use line and replace io::stdin() with std::io::stdin(). There are a number of symbols that Rust brings into scope for you from the standard library automatically - things that get used in almost every program you're going to write. This set is called the prelude.

Storing Values with Variables

    let mut guess = String::new();

This creates new String and binds it to a mutable variable called guess. By default variables in Rust are immutable. Obviously if this were an immutable string then the read_line function would have a difficult time storing anything it it, so we use the mut keyword to make it mutable. In the call to String::new(), the :: part tells us that new is an associated function implemented on the String type. Many types in Rust implement a new constructor like this.

    io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");

read_line reads some input from stdin, and stores it in guess. We pass in &mut guess instead of just guess. The & means we pass a reference to the object that the guess variable points to, and mut means that read_line is allowed to mutate that variable.

Passing by reference here works very similar to passing by pointer in Go or C, or passing an object in Java or JavaScript - the called function/method can modify the passed in object, and those changes will be visible in the caller's scope. References also have a lot of implications for ownership. We'll go into references in much greater detail in chapter 4.

info

If you're coming from a C/C++ background, Rust references are a little bit like C++ references, and a little bit like C pointers. We'll go into this in more detail in chapter 4. You might also assume that without the & Rust would pass a copy of guess, but this isn't true. When we pass a value without using a reference in Rust, we actually transfer ownership of the value to the called function. This is getting way ahead of ourselves though - again, we'll get there in chapter 4.

Handling Potential Failure with Result

In Rust, when a function can fail it returns a Result (see chapter 9). read_line can theoretically fail - here we're reading from stdin which is probably not going to fail, but if we were reading from a file or a network connection it could.

Result is an enum (see chapter 6) which will either be an Ok variant in the success case or an Err variant to signal an error has occurred. Enums are a bit unique in Rust in that an enum can carry extra information with it. If the Result is an Err, it will contain the reason why this operation failed. If the Result is an Ok it could contain some information (although here it doesn't).

We're kind of glossing over the error handling here by calling expect on the Result, which will cause a panic if there's an error. If you were to remove the call to expect, then the program would still compile, but you'd get a warning that you have a possible error case that you might not have handled correctly.

Printing Values with println! Placeholders

The last line is:

    println!("You guessed: {guess}");

println! here is a macro that writes some string to stdout. It's very similar to printf in C or Go. In one of those languages we could rewrite the above as printf("You guessed: %s", guess).

The {} is a placeholder. You can place a single variable directly in the placeholder, but if you have an expression you'd have to use {} as the placeholder and then pass your expression as a second parameter:

    println!("1 + 2 = {}", 1 + 2);

Generating a Secret Number

We now have a program that asks you to guess a number, but we're not yet generating a secret number to guess. Since Rust has no random number generator in the standard library, we'll rely on the "rand" crate from crates.io. To add rand to our project we can run:

$ cargo add rand

Once you do this, if you open up Cargo.toml, you'll see rand listed as one of our dependencies:

[dependencies]
rand = "0.8.5"

Just like node.js dependencies, this uses semantic versioning. Here "0.8.5" is short for "^0.8.5", which means Cargo will install a version >= 0.8.5 and < 0.9.0. When you run cargo build or cargo run, cargo will automatically download this dependency (and any transitive dependencies it relies on) and add them to Cargo.lock. If a new patch version of rand comes out and you want to upgrade to it, you can update the lock file with cargo update. If a new minor or major version comes out, you'd have to update the Cargo.toml file.

You can run cargo doc --open to generate HTML documentation for all the crates you're using (and all of their dependencies) and also for all of your code (see chapter 14).

Now that we have the "rand" crate, let's update src/main.rs:

src/main.rs
use std::io;
use rand::Rng;

fn main() {
println!("Guess the number!");

let secret_number = rand::thread_rng()
.gen_range(1..=100);

println!("The secret number is: {secret_number}");

println!("Please input your guess.");

let mut guess = String::new();

io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");

println!("You guessed: {guess}");
}

This line generates our random number:

    let secret_number = rand::thread_rng()
.gen_range(1..=100);

rand::thread_rng() returns an object that implements the Rng trait (see chapter 10). A trait is very similar to what other languages would call an "interface". We call the gen_range method (from the Rng trait) passing in a range expression to generate a random number between 1 and 100 inclusive (if the range express was 1..100 it would be 1 to 99 inclusive).

Notice we use rand::Rng to bring Rng into scope. This might seem a little strange, because if you read through this code we never use Rng directly. In Rust, though, in order to call the gen_range method on an object that has the Rng trait we need to have the Rng trait in scope.

Comparing the Guess to the Secret Number

Now that we have a guess from our user and a random number from rand, we can compare them. Add use std::cmp::Ordering to the top of the file, and then we can:

    println!("You guessed: {guess}");

let guess: u32 = guess.trim().parse().expect("Please type a number!");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}

comp compares two comparable things and returns an Ordering, which is another enum (like Result above). We use a match statement to decide what to do with the Ordering. A match statement is very similar to a switch/case statement in other languages. A match expression in Rust is made up of arms, each of which consists of a pattern to match against, and the code that should be run if the value fits that arm's pattern, with a => between them. Patterns and matches will be covered in more detail in chapter 6 and chapter 18.

Notice the line in the block above where we call parse. We're creating a new variable here called guess of type u32, but we already had a variable named guess of type String. This is OK, as Rust lets us shadow the old variable.

We are annotating the new guess variable with : u32. This makes guess an unsigned 32 bit integer. If you're coming from another language, you might find the need to annotate the type here surprising. Shouldn't Rust be inferring the type automatically based on what parse returns? Actually, exactly the opposite is happening. parse is a generic function, that can return different types depending on what we want it to return, and the Rust compiler here is inferring from the fact that we're assigning to a u32 that it should call the version of parse that returns a u32. In fact, annotating the type of guess here also changes the type of secret_number. By default secret_number would have been an i32 - a signed 32 bit number - because this is the default Rust picks for a number unless we give it reason not to. But, because we're calling cmp to compare secret_number and guess, Rust's type inference engine makes secret_number a u32 as well, so we're not comparing mismatched types. The type inference engine in Rust is magic!

Allowing Multiple Guesses with Looping

The loop keyword creates an infinite loop, and the break keyword can be used to break out of it:

    loop {
println!("Please input your guess.");

// --snip--

match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break; // Break out of this loop.
}
}
}

Handling Invalid Input

When prompted for a guess, if you enter something that isn't a number such as "hello" then the program will crash. This is because parse won't be able the parse the number, so will return a Result of type Err, and the expect call will cause a panic. We can use a match statement just like we did for cmp to handle the Result from parse more gracefully:

        let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};

Here we're using match as an expression instead of just as flow control. If the user enters a valid number, parse will return an Ok which will match the first arm of the match. This will cause the whole match expression to evaluate to num, which will be assigned back to guess. If the input is invalid, we continue to the top of the loop and ask for the number again.

The Err may contain some information, but we're assigning it to the special _ variable because we don't care what kind of error this is.

Here's the final program:

src/main.rs
use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
println!("Guess the number!");

let secret_number = rand::thread_rng().gen_range(1..=100);

loop {
println!("Please input your guess.");

let mut guess = String::new();

io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");

let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};

println!("You guessed: {guess}");

match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
}
}

Now that you have a rough idea of what a Rust program looks like, and we have some terminology down, let's start looking at some basic Rust syntax in more detail.

- + \ No newline at end of file diff --git a/ch03-common-programming-concepts/index.html b/ch03-common-programming-concepts/index.html index af1e247..34b3504 100644 --- a/ch03-common-programming-concepts/index.html +++ b/ch03-common-programming-concepts/index.html @@ -4,13 +4,13 @@ 3 - Common Programming Concepts | The rs Book - +
-

3 - Common Programming Concepts

In which we learn about variables, basic types, functions, comments, and control flow.

3.1 - Variables and Mutability

Variables are declared with the let keyword. By default, variables are immutable unless they are declared mut. This program will fail to compile with the error cannot assign twice to immutable variable `x` :

fn main() {
let x = 5;
x = 6; // This will error!

let mut y = 5;
y = 6; // This is okay.
}

Immutability in Rust is similar to const in JavaScript, or final in Java. The value the reference points to can't be modified (mostly):

fn main() {
let foo = String::from("foo");
foo.clear(); // This will error!
}

Here clear will try to empty the string, but will fail with the error cannot borrow `foo` as mutable, as it is not declared as mutable. If you go look at the source code for the clear method it is defined as requiring self to be a mutable reference (self is a bit like this in other languages).

Variables cannot be declared at the global scope unless they are static.

info

You may have noticed that that "mostly" above when we were talking about immutable variables. Immutability prevents us from directly modifying members of a struct, however in chapter 15 we're going to find out how you can modify parts of immutable objects through a concept call interior mutability, and that we can share mutable objects across multiple places in the code with smart pointers like Rc<T> and Arc<T>. A Rust mutex is an example of an object that is immutable, but you're allowed to change the value in it if you own the lock.

Constants

Rust also has the concept of a constant which at first sounds a lot like an immutable variable:

const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;

Constants are subtly different from immutable variables. They are stored directly in the program binary, so they cannot be mut and the value of the constant has to be something that can be determined at compile time. The Rust Reference has a section on constant evaluation that lays out all the rules for what operators you're allowed to use and what you're not, but here the compiler can convert 60 * 60 * 3 into 10800 for us and store that in the executable.

Constants must always be annotated, and can be declared in the global scope.

Static Variables

Static variables or global variables are declared with the static keyword and are named in SCREAMING_SNAKE_CASE:

static HELLO_WORLD: &str = "Hello, world!";

fn main() {
println!("name is: {}", HELLO_WORLD);
}

Static variables can be mutable, but to access or modify them we need to talk about unsafe code, which we'll do later.

Shadowing

As we saw in chapter 2, a variable declaration can shadow another:

fn main() {
let x = 5;

let x = x + 1;

{
let x = x * 2;
println!("The value of x in the inner scope is: {x}");
}

println!("The value of x is: {x}");
}

There are a total of three variables in this function, all named "x". Variables last until the end of the block they were declared in, so this program prints out:

The value of x in the inner scope is: 12
The value of x is: 6

When shadowing a variable, the new variable does not have to have the same type as the one it is shadowing.

3.2 - Data Types

Keep in mind that Rust is a statically typed language, so the type of every variable (and how much space it will occupy in memory, if it is stored on the stack) must be known at compile time. Rust's type inference is amazing, so frequently we don't have to tell Rust what type a variable is, but sometimes a variable's type is ambiguous in which case we need to annotate it (e.g. let guess: 32 = ...).

A "scalar type" represents a single value. There are four kinds of scalar types in Rust: integers, floating-point numbers, Booleans, and characters.

Integer Types

Integer types:

Length (bits)SignedUnsigned
8i8u8
16i16u16
32i32u32
64i64u64
128i128u128
archisizeusize

Signed integers are stored using two's complement. isize and usize depend on your architecture, so they'll be 32 bit numbers on a 32 bit architecture, or 64 bit on a 64 bit architecture.

Integer literals can be written using any of the methods below. Integer literals in Rust can use an _ as a visual separator (similar to how we might write "1,000" in English, we can write "1_000" in Rust).

Number literalsExample
Decimal98_222
Hex0xff
OctalOo77
Binary0b1111_0000
Byte (u8)b'A'

If you try to overflow an integer (e.g. you try to store 256 in a u8), what happens (by default) depends on whether you compiled your program with --release or not. In debug mode Rust adds runtime checks to ensure you don't overflow a value, so your program will panic and crash (see chapter 9 for more about panics). With the --release flag, the integer will overflow as you would expect it to in another language like C or Java (the largest value a u8 can hold is 255, so 256 wraps to 0).

The standard library has functions that let you explicitly define how you want to handle overflows if you don't want to panic. For example wrapping_add will add two numbers and let them wrap around. There are wrapping_*, checked_*, overflowing_*, and saturating_* functions for integer arithmetic.

tip

We can change how overflows are handled at runtime for development and release through release profiles.

Floating-Point Types

There are two floating point types, f64 (the default) and f32. Floating-point numbers are stored using the IEEE-754 standard.

Number Operators

Rust has the operators you'd expect: +, -, *, /, and % for modulus. See the Rust Book Appendix B for a complete list of all the operators in Rust.

Boolean type

Booleans are of type bool and can be true or false:

let t = true;
let f: bool = false;

Character Type

A char in Rust is a four-byte unicode scalar value.

let c = 'z';
let z: char = 'ℤ';
let heart_eyed_cat = '😻';
let space_woman_zwj = '👩🏻‍🚀'; // <== This doesn't work!

That last example doesn't work. Our female astronaut friend might look like a single character, but she's actually two emoji joined together with a zero-width-joiner (ZWJ). We'll talk a lot more about UTF-8 and Unicode in chapter 8.

&str and String

You'll see two different string types in Rust: str and String. String is similar to a Vector - it's a data type that stores a list of characters in a variable-length chunk of memory on the heap. Any time you accept input from the user or read a string from a file, it's going to end up in a String.

The type &str (almost always seen in it's borrowed form) is also known as a string slice (which we'll learn more about in the next chapter), and is both a pointer to the string's data and a length for the string. Any string literal in Rust is a &str, since the actual string is stored somewhere in the executable and we just have an immutable reference to it. A string slice can be used as a view into a String.

Compound Types

Compound types group multiple values into one type. Rust has two primitive compound types, the tuple and the array.

Tuple Type

let tup: (i32, f64, u8) = (800, 6.4, 1);

// Destructuring assignment
let (x, y, z) = tup;

// Access individual elements
let a = tup.0;
let b = tup.1;
let c = tup.2;

An empty tuple is written () and is called a "unit". This represents an empty value or an empty return type. Functions without a return type implicitly return this.

Array Type

Every element in an array must be the same type, and arrays must be fixed length. If you're looking for a "variable length" array, you want a vector from the standard library (see Chapter 8). If you declare a variable as an array in a function, then the contents of that variable will end up on the stack, while for a vector contents will end up on the heap. (Although you can put the contents of an array on the heap by using a smart pointer like a Box<T> - see chapter 15).

let a = [1, 2, 3, 4, 5];

// Destructuring assignment. Must use all elements!
let [x, y, z, _, _] = a;

// Access individual elements
let first = a[0];
let second = a[1];

// Create array of type i32, length 5.
let b: [i32; 5] = [1, 2, 3, 4, 5];

// Create array of five zeros.
let c = [0; 5]

Array accesses are checked at runtime. Trying to access an index which is out-of-bounds will cause a panic.

If you're coming to Rust from JavaScript, it's worth pointing out that JavaScript "arrays" are not quite like arrays in any other programming language. The Rust Vec type, or vector, is much closer to a JavaScript array than a Rust array is. We'll talk about vectors in chapter 8.

struct type

We can define our own compound types using the struct keyword:

struct User {
name: String,
age: u32,
}

3.3 - Functions

Functions are defined by fn keyword. Parameters are required to have a type annotation, and are annotated with : type just like variables (and just like in Typescript).

fn another_function(x: i32, y: i32) {
println!("The point is at: {x}, {y}");
}

If you end a function with an expression instead of a statement, then the function will return the value of that expression. Return types must be explicitly declared with an arrow (->).

// Returns 1
fn implicit_return() -> i32 {
1
}

// Also returns 1, but using `return` is not
// idiomatic in Rust unless you want to return
// from the middle of a function.
fn explicit_return() -> i32 {
return 1;
}

// The semicolon makes this a statement instead
// of an expression, so this returns `()`.
fn no_return() {
1;
}

Assignments are always statements (i.e. let x = 6 does not evaluate to 6), as are function definitions (i.e. you can't do let x = fn foo() {}). Functions can be called before they are defined. In chapter 10 we'll learn about using generics with functions.

Rust also has closures, which are inline functions that can be assigned to variables or passed as parameters. We'll learn about them in detail in chapter 13, but the syntax is:

let my_closure = |param1, param2| { /* function body goes here */ };

3.4 - Comments

// This is a comment.  Multi-line comments
// generally are written this way.

/* You can use this style of comment too. */

/// This is a doc comment - see chapter 14.

3.5 - Control Flow

if Expression

if statements don't have braces around the condition, (much like Go, and much unlike Java, JavaScript, or C):

if number < 5 {
println!("less than 5");
} else if number > 10 {
println!("greater than 10");
} else {
println!("greater than 4, less than 11");
}

Note that if can be used as an expression. In this case each "arm" of the if must be the same type:

// This is OK
let number = if condition { 5 } else { 6 };

// This breaks! `if` and `else` have
// incompatible types
let wat = if condition { 5 } else { "six" };

// But this is OK.
loop {
let wat = if condition { 5 } else { break };
}

Repetition with Loops

Rust has three kinds of loops: loop, while, and for. The break and continue statements work exactly as they do in other languages: break will stop the loop, and continue will stop execution of the current iteration and start the next one. Note that loops can be used as expressions.

loop {
println!("Infinite loop!")
}

// Loops can be used as expressions, with `break`
// returning the value from the block.
let mut counter = 0;
let x = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};

// Loops can be labeled with a single quote
// followed by the label and the a colon.
'outer: loop {
'inner: loop {
break 'outer;
}
}

// A while loop looks a lot like a
// while loop in every other language.
let mut number = 0;
while number < 10 {
number++;
}

For loops in Rust are always of the format for [var] in [iterator] {}:

// Iterate over an array
let a = [1, 2, 3, 4, 5];
for element in a {
println!("value is {element}");
}

// Count from 1 to 5
for element in (1..6) {
println!("value is {element}");
}

We'll see more about iterators in chapter 13.

Now that we know some basics, it's time to learn about ownership.

- +

3 - Common Programming Concepts

In which we learn about variables, basic types, functions, comments, and control flow.

3.1 - Variables and Mutability

Variables are declared with the let keyword. By default, variables are immutable unless they are declared mut. This program will fail to compile with the error cannot assign twice to immutable variable `x` :

fn main() {
let x = 5;
x = 6; // This will error!

let mut y = 5;
y = 6; // This is okay.
}

Immutability in Rust is similar to const in JavaScript, or final in Java. The value the reference points to can't be modified (mostly - see the info box below):

fn main() {
let foo = String::from("foo");
foo.clear(); // This will error!
}

Here clear will try to empty the string, but will fail with the error cannot borrow `foo` as mutable, as it is not declared as mutable. If you go look at the source code for the clear method it is defined as requiring self to be a mutable reference (self is a bit like this in other languages).

Variables cannot be declared at the global scope unless they are static.

info

You may have noticed that that "mostly" above when we were talking about immutable variables. Immutability prevents us from directly modifying members of a struct, however in chapter 15 we're going to find out that sometimes you can modify individual parts of an immutable struct through a concept call interior mutability. A mutex is an example of an object that is immutable, but you're allowed to change the value in it if you own the lock.

Constants

Rust also has the concept of a constant which at first sounds a lot like an immutable variable:

const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;

Constants are subtly different from immutable variables. They are stored directly in the program binary, so they cannot be mut and the value of the constant has to be something that can be determined at compile time. The Rust Reference has a section on constant evaluation that lays out all the rules for what operators you're allowed to use and what you're not, but here the compiler can convert 60 * 60 * 3 into 10800 for us and store that in the executable.

Constants must always be annotated, and can be declared in the global scope.

Static Variables

Static variables or global variables are declared with the static keyword and are named in SCREAMING_SNAKE_CASE:

static HELLO_WORLD: &str = "Hello, world!";

fn main() {
println!("name is: {}", HELLO_WORLD);
}

Static variables can be mutable, but to access or modify them we need to talk about unsafe code, which we'll do later.

Shadowing

As we saw in chapter 2, a variable declaration can shadow another:

fn main() {
let x = 5;

let x = x + 1;

{
let x = x * 2;
println!("The value of x in the inner scope is: {x}");
}

println!("The value of x is: {x}");
}

There are a total of three variables in this function, all named "x". Variables last until the end of the block they were declared in, so this program prints out:

The value of x in the inner scope is: 12
The value of x is: 6

When shadowing a variable, the new variable does not have to have the same type as the one it is shadowing.

3.2 - Data Types

Keep in mind that Rust is a statically typed language, so the type of every variable (and how much space it will occupy in memory, if it is stored on the stack) must be known at compile time. Rust's type inference is amazing, so frequently we don't have to tell Rust what type a variable is, but sometimes a variable's type is ambiguous in which case we need to annotate it (e.g. let guess: 32 = ...).

A "scalar type" represents a single value. There are four kinds of scalar types in Rust: integers, floating-point numbers, Booleans, and characters.

Integer Types

Integer types:

Length (bits)SignedUnsigned
8i8u8
16i16u16
32i32u32
64i64u64
128i128u128
archisizeusize

Signed integers are stored using two's complement. isize and usize depend on your architecture, so they'll be 32 bit numbers on a 32 bit architecture, or 64 bit on a 64 bit architecture.

Integer literals can be written using any of the methods below. Integer literals in Rust can use an _ as a visual separator (similar to how we might write "1,000" in English, we can write "1_000" in Rust).

Number literalsExample
Decimal98_222
Hex0xff
OctalOo77
Binary0b1111_0000
Byte (u8)b'A'

If you try to overflow an integer (e.g. you try to store 256 in a u8), what happens (by default) depends on whether you compiled your program with --release or not. In debug mode Rust adds runtime checks to ensure you don't overflow a value, so your program will panic and crash (see chapter 9 for more about panics). With the --release flag, the integer will overflow as you would expect it to in another language like C or Java (the largest value a u8 can hold is 255, so 256 wraps to 0).

The standard library has functions that let you explicitly define how you want to handle overflows if you don't want to panic. For example wrapping_add will add two numbers and let them wrap around. There are wrapping_*, checked_*, overflowing_*, and saturating_* functions for integer arithmetic.

tip

We can change how overflows are handled at runtime for development and release through release profiles.

Floating-Point Types

There are two floating point types, f64 (the default) and f32. Floating-point numbers are stored using the IEEE-754 standard.

Number Operators

Rust has the operators you'd expect: +, -, *, /, and % for modulus. See the Rust Book Appendix B for a complete list of all the operators in Rust.

Boolean type

Booleans are of type bool and can be true or false:

let t = true;
let f: bool = false;

Character Type

A char in Rust is a four-byte unicode scalar value.

let c = 'z';
let z: char = 'ℤ';
let heart_eyed_cat = '😻';
let space_woman_zwj = '👩🏻‍🚀'; // <== This doesn't work!

That last example doesn't work. Our female astronaut friend might look like a single character, but she's actually two emoji joined together with a zero-width-joiner (ZWJ). We'll talk a lot more about UTF-8 and Unicode in chapter 8.

&str and String

You'll see two different string types in Rust: str and String. String is similar to a Vector - it's a data type that stores a list of characters in a variable-length chunk of memory on the heap. Any time you accept input from the user or read a string from a file, it's going to end up in a String.

The type &str (almost always seen in it's borrowed form) is also known as a string slice (which we'll learn more about in the next chapter), and is both a pointer to the string's data and a length for the string. Any string literal in Rust is a &str, since the actual string is stored somewhere in the executable and we just have an immutable reference to it. A string slice can be used as a view into a String.

Compound Types

Compound types group multiple values into one type. Rust has two primitive compound types, the tuple and the array.

Tuple Type

let tup: (i32, f64, u8) = (800, 6.4, 1);

// Destructuring assignment
let (x, y, z) = tup;

// Access individual elements
let a = tup.0;
let b = tup.1;
let c = tup.2;

An empty tuple is written () and is called a "unit". This represents an empty value or an empty return type. Functions without a return type implicitly return this.

Array Type

Every element in an array must be the same type, and arrays must be fixed length. If you're looking for a "variable length" array, you want a vector from the standard library (see Chapter 8). If you declare a variable as an array in a function, then the contents of that variable will end up on the stack, while for a vector contents will end up on the heap. (Although you can put the contents of an array on the heap by using a smart pointer like a Box<T> - see chapter 15).

let a = [1, 2, 3, 4, 5];

// Destructuring assignment. Must use all elements!
let [x, y, z, _, _] = a;

// Access individual elements
let first = a[0];
let second = a[1];

// Create array of type i32, length 5.
let b: [i32; 5] = [1, 2, 3, 4, 5];

// Create array of five zeros.
let c = [0; 5]

Array accesses are checked at runtime. Trying to access an index which is out-of-bounds will cause a panic.

If you're coming to Rust from JavaScript, it's worth pointing out that JavaScript "arrays" are not quite like arrays in any other programming language. The Rust Vec type, or vector, is much closer to a JavaScript array than a Rust array is. We'll talk about vectors in chapter 8.

struct type

We can define our own compound types using the struct keyword:

struct User {
name: String,
age: u32,
}

3.3 - Functions

Functions are defined by fn keyword. Parameters are required to have a type annotation, and are annotated with : type just like variables (and just like in Typescript).

fn another_function(x: i32, y: i32) {
println!("The point is at: {x}, {y}");
}

If you end a function with an expression instead of a statement, then the function will return the value of that expression. Return types must be explicitly declared with an arrow (->).

// Returns 1
fn implicit_return() -> i32 {
1
}

// Also returns 1, but using `return` is not
// idiomatic in Rust unless you want to return
// from the middle of a function.
fn explicit_return() -> i32 {
return 1;
}

// The semicolon makes this a statement instead
// of an expression, so this returns `()`.
fn no_return() {
1;
}

Assignments are always statements (i.e. let x = 6 does not evaluate to 6), as are function definitions (i.e. you can't do let x = fn foo() {}). Functions can be called before they are defined. In chapter 10 we'll learn about using generics with functions.

Rust also has closures, which are inline functions that can be assigned to variables or passed as parameters. We'll learn about them in detail in chapter 13, but the syntax is:

let my_closure = |param1, param2| { /* function body goes here */ };

3.4 - Comments

// This is a comment.  Multi-line comments
// generally are written this way.

/* You can use this style of comment too. */

/// This is a doc comment for the "next thing", in
/// this case for the `foo` function. Markdown is
/// allowed here. See chapter 14 for more details.
fn foo() {}

mod bar {
//! This is a doc comment for the "parent thing",
//! in this case the "bar" module.
}

3.5 - Control Flow

if Expression

if statements don't have braces around the condition, (much like Go, and much unlike Java, JavaScript, or C):

if number < 5 {
println!("less than 5");
} else if number > 10 {
println!("greater than 10");
} else {
println!("greater than 4, less than 11");
}

Note that if can be used as an expression. In this case each "arm" of the if must be the same type:

// This is OK
let number = if condition { 5 } else { 6 };

// This breaks! `if` and `else` have
// incompatible types
let wat = if condition { 5 } else { "six" };

// But this is OK.
loop {
let wat = if condition { 5 } else { break };
}

Repetition with Loops

Rust has three kinds of loops: loop, while, and for. The break and continue statements work exactly as they do in other languages: break will stop the loop, and continue will stop execution of the current iteration and start the next one. Note that loops can be used as expressions.

loop {
println!("Infinite loop!")
}

// Loops can be used as expressions, with `break`
// returning the value from the block.
let mut counter = 0;
let x = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};

// Loops can be labeled with a single quote
// followed by the label and the a colon.
'outer: loop {
'inner: loop {
break 'outer;
}
}

// A while loop looks a lot like a
// while loop in every other language.
let mut number = 0;
while number < 10 {
number++;
}

For loops in Rust are always of the format for [var] in [iterator] {}:

// Iterate over an array
let a = [1, 2, 3, 4, 5];
for element in a {
println!("value is {element}");
}

// Count from 1 to 5
for element in (1..6) {
println!("value is {element}");
}

We'll see more about iterators in chapter 13.

Now that we know some basics, it's time to learn about ownership.

+ \ No newline at end of file diff --git a/ch04-ownership/index.html b/ch04-ownership/index.html index 2fb7b1b..d281def 100644 --- a/ch04-ownership/index.html +++ b/ch04-ownership/index.html @@ -4,13 +4,13 @@ 4 - Ownership, References, and Slices | The rs Book - +
-

4 - Ownership, References, and Slices

Ownership is Rust's most unique feature and has deep implications for the rest of the language. It enables Rust to make memory safety guarantees without needing a garbage collector, so it's important to understand how ownership works.

-- "The Rust Programming Language" Chapter 4 - Understanding Ownership

4.1 - What is Ownership?

The idea of ownership is quite core to Rust. If you're coming from a language like Python or JavaScript, and you're not familiar with the idea of the the stack and heap it's worth reading up about them. We're going to assume you're familiar with them in this chapter.

In a language like C, we can manage memory by explicitly calling malloc and free. All memory management is up to us, which means it's easy to make mistakes. In a language like Java or JavaScript, memory is allocated automatically without us having to think about it, so memory allocation is very safe, but this incurs a runtime cost in the form of garbage collection.

Rust is rather unique in how it manages memory. Aside from simple values such as i32 or f64, In Rust, every value is owned by some variable called the owner. Ownership of a particular value can be transferred from one variable to another, and in some cases memory can be borrowed. Once the variable that owns the value is no longer around we say that value has been dropped, and once that happens any memory it allocated can safely be freed. When a value is dropped, it can optionally run code in a destructor (defined by implementing the Drop trait).

Ownership Rules

From the original Rust Book:

  • Each value in Rust has an owner.
  • There can only be one owner at a time.
  • When the owner goes out of scope, the value will be dropped.

The scope of a variable in Rust works much like it does in most other languages - inside a set of curly braces, any variable you declare can be accessed only after its declaration, and it goes "out of scope" once we hit the closing brace. The key thing about Rust is that once a variable goes out of scope, if that variable currently owns some memory, then that memory will be freed.

tip

A variable can only have one owner at a time, but in chapter 15 we'll talk about smart pointers like Rc<T> that let us get around this restriction.

We also say that each value in Rust has an owner, but it's possible to leak memory in Rust, which would technically end with values that have no owners.

Memory and Allocation

This is a trivial example demonstrating some memory being allocated on the heap and then freed:

fn foo() {

if (true) {
// Create the variable `s` to own a String.
// Remember that Strings can store an arbitrary
// length of data, so this will allocate memory
// on the heap.
let s = String::from("hello");

// Do stuff with s

}
// At this point `s` has fallen out of scope, so the
// String that was owned by s will be dropped, and
// the memory it allocated on the heap will be freed.
}

You might read that and scratch your head and think "If everything disappears when it goes out of scope, isn't this the same as just allocating everything on the stack?" And this would be true, except that ownership can be moved:

fn main() {
let outer_string = foo();
println!("{}", outer_string);
}

fn foo() -> String {
let inner_string = String::from("hello world");
inner_string
}

Here the foo function creates a String (which allocates some memory on the heap) and inner_string is the owner of that String. The foo function returns inner_string, so ownership of the String (and the associated memory) is moved to outer_string in the caller. When we reach the end of main, then outer_string falls out-of-scope. At this point the String doesn't have an owner anymore, so it will be dropped.

info

When we move ownership of a variable, it's location in memory will change:

fn main() {
let x = String::from("hello world");
println!("Address: {:p}", &x);
let y = x;
println!("Address: {:p}", &y);
}

The above example will print different addresses for x and y. In this example, the String has some memory stored on the heap (the "hello world" part) but also has some memory on the stack (a pointer to that value and a length, amongst other data). When we move ownership of x to y, we're also moving the data on the stack from one place to another with memcpy, although the heap part stays in the same place.

If you need a piece of data to stay in one place in memory, see std::pin.

There Can Only Be One

Remember that we said there can only be one owner at a time?

fn strings() -> String {
// Create a string
let s1 = String::from("hello");

// Move ownership from s1 to s2
let s2 = s1;

// Can't use s1 anymore!
println!("{}", s1);

s2
}

This code fails to compile with the error:

error[E0382]: borrow of moved value: `s1`
--> src/main.rs:9:20
|
3 | let s1 = String::from("hello");
| -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
...
6 | let s2 = s1;
| -- value moved here
...
9 | println!("{}", s1);
| ^^ value borrowed here after move

If you're coming from some other language, and you try to just pass values around and hope for the best without understanding ownership, you're going to see this error a lot.

In this example, we create a variable s1, which owns the String. In most other languages, when we do let s2 = s1;, we'd now have two variables that point to the same underlying object, but not so in Rust. In Rust, we move ownership of the value from s1 to s2, so s1 stops being valid and can't be used from that point forwards. This is exactly the same as when we returned a variable in the example above.

If you think about this at the memory level, when we create s1, we allocate some memory. When we say let s2 = s1;, we're not creating a second String (we didn't call clone or new). If we allowed s1 to be valid after this point then s1 and s2 would have to point to the same memory. When we reach the end of the function, we return s2 but not s1, which means s1 is going out of scope and should be dropped, but since s2 is being moved and refers to the same underlying object s1 can't be dropped. Rust's answer to this problem is to never let this happen - only one owner at a time.

If we wanted to deep-copy the data in the String, we could use the clone method to allocate more memory on the heap:

fn strings() {
let s1 = String::from("hello");
let s2 = s1.clone();

println!("{}", s1);
}
info

We can do something slightly tricky with a move like this too. We can take an immutable variable and turn it into a mutable one:

fn main() {
let x = String::from("hello world");
let mut y = x;
}

When y takes ownership of x, it owns that memory now and can do what it wants with it, so it's perfectly acceptable to redeclare the variable as mut. If you have a favorite book and you keep it in pristine condition, but then you decide to give me that book then it becomes my book. I can dog ear pages and crack the spine, because I own it. If you lend me your book, that's a different story - and we'll talk about borrowing in just a little bit.

Stack-Only Data: Copy

Similar to Java or JavaScript or C or... actually most other languages, Rust has special handling for basic data types:

fn integers() {
let i1 = 1;
let i2 = i1;

println!("{}", i1);
}

This looks just like the string example above, but it compiles. This is because here i1 is an i32, which takes up four bytes of memory. Since Rust knows this at compile time, it can allocate it on the stack instead of the heap, and making a copy of a four byte value on the stack to another four bytes of the stack is so cheap it is essentially free. So here, let i2 = i1; doesn't move anything, it just makes a copy of the variable for you.

What types get copied like this? The quick answer to this is any basic type (integers, booleans, chars, etc...) and any tuple made up of basic types. More formally, the answer is any type that has the Copy trait (see chapter 10 for more information about traits). You can also implement it on your own data structures if they are made entirely of copyable types, or get Rust to derive it for you (which means Rust will generate this code for you at compile time):

#[derive(Copy, Clone)]
pub struct MyStruct {
pub foo: i32,
}
info

Structs with the Copy trait are not allowed to implement the Drop trait, so they can't run any custom code when they go out of scope.

Ownership and Functions

We already saw that if you return a variable, then ownership of the variable is moved to the caller. We also move ownership when we pass a variable to a function:

fn main() {
let s = String::from("hello");
takes_ownership(s);

// Ownership of `s` was moved to `takes_ownership`'s
// `some_string`, so s is no longer valid here.
}

fn takes_ownership(some_string: String) {
println!("{}", some_string);
} // Here, some_string goes out of scope and `drop`
// is called. The backing memory is freed.

4.2 - References and Borrowing

If you wanted to pass a variable to a function, but also keep it usable afterwards, you could pass the variable to the function and then return it from the function. This would move the variable into the function, and then move it back so you can keep using it. As you can imagine, using a variable more than once is something we want to do pretty often, and if this was "the way" to do it, then Rust would be a very annoying language to work in. Instead we can let the function we call borrow the variable by passing a reference:

fn main() {
let s1 = String::from("hello");

let len = calculate_length(&s1);

println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &String) -> usize {
s.len()
}

Two things to note here - when we call calculate_length instead of passing s1 we're passing &s1, and in the signature for calculate_length we take a &String instead of a String. What we're passing here is a "reference to a string". Essentially &s1 contains a pointer to the String held in s1, and we're passing that pointer to calculate_length. calculate_length doesn't take ownership of the String, it merely borrows it, so the String won't be dropped when s goes out of scope.

info

The syntax for getting a reference to a value - &x - is exactly the same as getting a pointer to a value in C or Go, and references in Rust behave a lot like pointers. This Stack Overflow answer talks about ways that Rust references compare to C/C++ pointers.

Mutable References

As with variables, we can have both immutable references (the default) and mutable references:

fn main() {
let mut s = String::from("hello");

change(&mut s);
}

fn change(some_string: &mut String) {
some_string.push_str(", world");
}

Mutable references come with a restriction: if you have a mutable reference to a value, you can have no other references to that value.

let mut s = String::from("hello");

let r1 = &mut s;
let r2 = &mut s; // This is an error!

println!("{}, {}", r1, r2);

This restriction is imposed because it prevents data races. The compiler will stop us from creating data races at compile time!

The scope of a reference lasts only until it's last use, not until the end of the block, so this is fine:

let mut s = String::from("hello");

let r1 = &mut s;
println!("{}", r1);

let r2 = &mut s; // r1 is now out-of-scope, so we can create r2.
println!("{}", r2);
info

Where you place the mut keyword changes how a reference can be used:

// x1 is a reference to y.  We can't update x or y:
let x1 = &y;
// x2 is a reference that can be used to change y:
let x2 = &mut y;
// x3 is is a reference that currently points to,
// an immutable y, but we could change x3 to point
// somewhere else.
let mut x3 = &y;
// x4 is a reference that can be used to change y,
// and can also be updated to point somewhere else.
let mut x4 = &mut y;

Dereferencing

Rust has a * operator for dereferencing, very similar to C++ or Go:

let num1 = 7; // num1 has type `i32`.
let num2 = &num1; // num2 has type `&i32`.
let num3 = *num2; // num3 has type `i32` again.

The * follows the pointer (see chapter 15). If the reference is mutable, we can use the * operator to modify what the reference points to:

let mut val = 10;
let val_ref = &mut val;
*val_ref = 5;

// Prints 5, because we used `val_ref` to modify `val`.
println!("{val}");

Dangling References

You can't return a reference to an object that will be dropped:

fn dangle() -> &String {
let s = String::from("hello");
&s // This is an error.
}

Here s goes out of scope at the end of the function, so the String will be dropped. That means if Rust let us return a reference to the String, it would be a reference to memory that had already been reclaimed.

There's no null or nil in Rust. You can't have a null pointer like you could in C. (Instead there's something called an Option which we'll talk about in chapter 6.)

The Rules of References

To sum up what we learned above:

  • At any given time, you can have either one mutable reference or any number of immutable references.
  • References must always be valid. You can't have references to dropped memory or null pointers.

4.3 - The Slice Type

A slice is a reference to a contiguous sequence of elements in a collection. Slices are references so they don't take ownership. The type of a string slice is &str.

let s = String::from("hello world");

let hello = &s[0..5]; // Type of `hello` is `&str`.
let world = &s[6..11];

The range syntax is [inclusive..exclusive]. Or, in other words [0..5] includes the zeroth character in the string, but omits the fifth. With the range syntax, you can omit the first number to start at 0, and omit the second number to end at the length of the string.

let s = String::from("rust time");

let rust = &s[..4];
let time = &s[5..];
let rust_time = &s[..];

Slices must occur at valid UTF-8 character boundaries. If you attempt to create a string slice in the middle of a multibyte character, your program will exit with an error. (Don't know what a multibyte character is? See chapter 8!)

Note that if you have a string slice, this counts as a reference, so you can't also have a mutable reference to that String:

fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();

for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}

&s[..]
}

fn main() {
let mut s = String::from("hello world");

// `word` ends up being a slice of `s`, so
// `word` counts as a reference to `s`.
let word = first_word(&s);

s.clear(); // error!

println!("the first word is: {}", word);
}

Inside main, word is a string slice from s, and therefore a reference to the memory the String uses. The call to s.clear() will fail to compile because to clear the string, we'd need to mutate it (clear is a method with a mutable reference to self). Since we can't create a mutable reference while word is in scope, this fails to compile.

String Literals as Slices

let s = "Hello, world!";

The type of s here is &str: it's a slice pointing to where this string is stored in the binary.

String Slices as Parameters

These two function signatures are very similar:

fn first_word_string(s: &String) -> &str {...}

fn first_word_str(s: &str) -> &str {...}

The first takes a reference to a String, the second takes a string slice. The second one, though, is generally preferred. It's trivial to convert a string to a slice, so you can call the second with any String, string slice, or string literal, or even a reference to a String (see chapter 15 for more on deref coercion).

In the reverse direction, it's a bit tedious to convert a string slice into a String. As a result the first version, first_word_string, is much less flexible.

Other Slices

Much like in Go, we can also create slices from arrays:

let a = [1, 2, 3, 4, 5];

let slice = &a[1..3];

assert_eq!(slice, &[2, 3]);

The type of slice here is &[i32].

Continue to chapter 5.

- +

4 - Ownership, References, and Slices

Ownership is Rust's most unique feature and has deep implications for the rest of the language. It enables Rust to make memory safety guarantees without needing a garbage collector, so it's important to understand how ownership works.

-- "The Rust Programming Language" Chapter 4 - Understanding Ownership

4.1 - What is Ownership?

The idea of ownership is quite core to Rust. If you're coming from a language like Python or JavaScript, and you're not familiar with the idea of the the stack and heap it's worth reading up about them. We're going to assume you're familiar with them in this chapter.

In a language like C, we can manage memory by explicitly calling malloc and free. All memory management is up to us, which means it's easy to make mistakes. In a language like Java or JavaScript, memory is allocated automatically without us having to think about it, so memory allocation is very safe, but this incurs a runtime cost in the form of garbage collection.

Rust is rather unique in how it manages memory. Aside from simple values such as i32 or f64, In Rust, every value is owned by some variable called the owner. Ownership of a particular value can be transferred from one variable to another, and in some cases memory can be borrowed. Once the variable that owns the value is no longer around we say that value has been dropped, and once that happens any memory it allocated can safely be freed. When a value is dropped, it can optionally run code in a destructor (defined by implementing the Drop trait).

Ownership Rules

From the original Rust Book:

  • Each value in Rust has an owner.
  • There can only be one owner at a time.
  • When the owner goes out of scope, the value will be dropped.

The scope of a variable in Rust works much like it does in most other languages - inside a set of curly braces, any variable you declare can be accessed only after its declaration, and it goes "out of scope" once we hit the closing brace. The key thing about Rust is that once a variable goes out of scope, if that variable currently owns some memory, then that memory will be freed.

tip

A variable can only have one owner at a time, but in chapter 15 we'll talk about smart pointers like Rc<T> that let us get around this restriction.

We also say that each value in Rust has an owner, but it's possible to leak memory in Rust, which would technically end with values that have no owners.

Memory and Allocation

This is a trivial example demonstrating some memory being allocated on the heap and then freed:

fn foo() {

if (true) {
// Create the variable `s` to own a String.
// Remember that Strings can store an arbitrary
// length of data, so this will allocate memory
// on the heap.
let s = String::from("hello");

// Do stuff with s

}
// At this point `s` has fallen out of scope, so the
// String that was owned by s will be dropped, and
// the memory it allocated on the heap will be freed.
}

You might read that and scratch your head and think "If everything disappears when it goes out of scope, isn't this the same as just allocating everything on the stack?" And this would be true, except that ownership can be moved:

fn main() {
let outer_string = foo();
println!("{}", outer_string);
}

fn foo() -> String {
let inner_string = String::from("hello world");
inner_string
}

Here the foo function creates a String (which allocates some memory on the heap) and inner_string is the owner of that String. The foo function returns inner_string, so ownership of the String (and the associated memory) is moved to outer_string in the caller. When we reach the end of main, then outer_string falls out-of-scope. At this point the String doesn't have an owner anymore, so it will be dropped.

info

When we move ownership of a variable, it's location in memory will change:

fn main() {
let x = String::from("hello world");
println!("Address: {:p}", &x);
let y = x;
println!("Address: {:p}", &y);
}

The above example will print different addresses for x and y. In this example, the String has some memory stored on the heap (the "hello world" part) but also has some memory on the stack (a pointer to that value and a length, amongst other data). When we move ownership of x to y, we're also moving the data on the stack from one place to another with memcpy, although the heap part stays in the same place.

If you need a piece of data to stay in one place in memory, see std::pin.

There Can Only Be One

Remember that we said there can only be one owner at a time?

fn strings() -> String {
// Create a string
let s1 = String::from("hello");

// Move ownership from s1 to s2
let s2 = s1;

// Can't use s1 anymore!
println!("{}", s1);

s2
}

This code fails to compile with the error:

error[E0382]: borrow of moved value: `s1`
--> src/main.rs:9:20
|
3 | let s1 = String::from("hello");
| -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
...
6 | let s2 = s1;
| -- value moved here
...
9 | println!("{}", s1);
| ^^ value borrowed here after move

If you're coming from some other language, and you try to just pass values around and hope for the best without understanding ownership, you're going to see this error a lot.

In this example, we create a variable s1, which owns the String. In most other languages, when we do let s2 = s1;, we'd now have two variables that point to the same underlying object, but not so in Rust. In Rust, we move ownership of the value from s1 to s2, so s1 stops being valid and can't be used from that point forwards. This is exactly the same as when we returned a variable in the example above.

If you think about this at the memory level, when we create s1, we allocate some memory. When we say let s2 = s1;, we're not creating a second String (we didn't call clone or new). If we allowed s1 to be valid after this point then s1 and s2 would have to point to the same memory. When we reach the end of the function, we return s2 but not s1, which means s1 is going out of scope and should be dropped, but since s2 is being moved and refers to the same underlying object s1 can't be dropped. Rust's answer to this problem is to never let this happen - only one owner at a time.

If we wanted to deep-copy the data in the String, we could use the clone method to allocate more memory on the heap:

fn strings() {
let s1 = String::from("hello");
let s2 = s1.clone();

println!("{}", s1);
}
info

We can do something slightly tricky with a move like this too. We can take an immutable variable and turn it into a mutable one:

fn main() {
let x = String::from("hello world");
let mut y = x;
}

When y takes ownership of x, it owns that memory now and can do what it wants with it, so it's perfectly acceptable to redeclare the variable as mut. If you have a favorite book and you keep it in pristine condition, but then you decide to give me that book then it becomes my book. I can dog ear pages and crack the spine, because I own it. If you lend me your book, that's a different story - and we'll talk about borrowing in just a little bit.

Stack-Only Data: Copy

Similar to Java or JavaScript or C or... actually most other languages, Rust has special handling for basic data types:

fn integers() {
let i1 = 1;
let i2 = i1;

println!("{}", i1);
}

This looks just like the string example above, but it compiles. This is because here i1 is an i32, which takes up four bytes of memory. Since Rust knows this at compile time, it can allocate it on the stack instead of the heap, and making a copy of a four byte value on the stack to another four bytes of the stack is so cheap it is essentially free. So here, let i2 = i1; doesn't move anything, it just makes a copy of the variable for you.

What types get copied like this? The quick answer to this is any basic type (integers, booleans, chars, etc...) and any tuple made up of basic types. More formally, the answer is any type that has the Copy trait (see chapter 10 for more information about traits). You can also implement it on your own data structures if they are made entirely of copyable types, or get Rust to derive it for you (which means Rust will generate this code for you at compile time):

#[derive(Copy, Clone)]
pub struct MyStruct {
pub foo: i32,
}
info

Structs with the Copy trait are not allowed to implement the Drop trait, so they can't run any custom code when they go out of scope.

Ownership and Functions

We already saw that if you return a variable, then ownership of the variable is moved to the caller. We also move ownership when we pass a variable to a function:

fn main() {
let s = String::from("hello");
takes_ownership(s);

// Ownership of `s` was moved to `takes_ownership`'s
// `some_string`, so s is no longer valid here.
}

fn takes_ownership(some_string: String) {
println!("{}", some_string);
} // Here, some_string goes out of scope and `drop`
// is called. The backing memory is freed.

4.2 - References and Borrowing

If you wanted to pass a variable to a function, but also keep it usable afterwards, you could pass the variable to the function and then return it from the function. This would move the variable into the function, and then move it back so you can keep using it. As you can imagine, using a variable more than once is something we want to do pretty often, and if this was "the way" to do it, then Rust would be a very annoying language to work in. Instead we can let the function we call borrow the variable by passing a reference:

fn main() {
let s1 = String::from("hello");

let len = calculate_length(&s1);

println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &String) -> usize {
s.len()
}

Two things to note here - when we call calculate_length instead of passing s1 we're passing &s1, and in the signature for calculate_length we take a &String instead of a String. What we're passing here is a "reference to a string". Essentially &s1 contains a pointer to the String held in s1, and we're passing that pointer to calculate_length. calculate_length doesn't take ownership of the String, it merely borrows it, so the String won't be dropped when s goes out of scope.

info

The syntax for getting a reference to a value - &x - is exactly the same as getting a pointer to a value in C or Go, and references in Rust behave a lot like pointers. This Stack Overflow answer talks about ways that Rust references compare to C/C++ pointers.

Mutable References

As with variables, we can have both immutable references (the default) and mutable references:

fn main() {
let mut s = String::from("hello");

change(&mut s);
}

fn change(some_string: &mut String) {
some_string.push_str(", world");
}

Mutable references come with a restriction: if you have a mutable reference to a value, you can have no other references to that value.

let mut s = String::from("hello");

let r1 = &mut s;
let r2 = &mut s; // This is an error!

println!("{}, {}", r1, r2);

This restriction is imposed because it prevents data races. The compiler will stop us from creating data races at compile time! Some people prefer to think about references in terms of "shared references" and "exclusive references" in stead of as "immutable" and "mutable".

The scope of a reference lasts only until it's last use, not until the end of the block, so this is fine:

let mut s = String::from("hello");

let r1 = &mut s;
println!("{}", r1);

let r2 = &mut s; // r1 is now out-of-scope, so we can create r2.
println!("{}", r2);
info

Where you place the mut keyword changes how a reference can be used:

// x1 is a reference to y.  We can't update x or y:
let x1 = &y;
// x2 is a reference that can be used to change y:
let x2 = &mut y;
// x3 is is a reference that currently points to,
// an immutable y, but we could change x3 to point
// somewhere else.
let mut x3 = &y;
// x4 is a reference that can be used to change y,
// and can also be updated to point somewhere else.
let mut x4 = &mut y;

Dereferencing

Rust has a * operator for dereferencing, very similar to C++ or Go:

let num1 = 7; // num1 has type `i32`.
let num2 = &num1; // num2 has type `&i32`.
let num3 = *num2; // num3 has type `i32` again.

The * follows the pointer (see chapter 15). If the reference is mutable, we can use the * operator to modify what the reference points to:

let mut val = 10;
let val_ref = &mut val;
*val_ref = 5;

// Prints 5, because we used `val_ref` to modify `val`.
println!("{val}");

Dangling References

You can't return a reference to an object that will be dropped:

fn dangle() -> &String {
let s = String::from("hello");
&s // This is an error.
}

Here s goes out of scope at the end of the function, so the String will be dropped. That means if Rust let us return a reference to the String, it would be a reference to memory that had already been reclaimed.

There's no null or nil in Rust. You can't have a null pointer like you could in C. (Instead there's something called an Option which we'll talk about in chapter 6.)

The Rules of References

To sum up what we learned above:

  • At any given time, you can have either one mutable reference or any number of immutable references.
  • References must always be valid. You can't have references to dropped memory or null pointers.

4.3 - The Slice Type

A slice is a reference to a contiguous sequence of elements in a collection. Slices are references so they don't take ownership. The type of a string slice is &str.

let s = String::from("hello world");

let hello = &s[0..5]; // Type of `hello` is `&str`.
let world = &s[6..11];

The range syntax is [inclusive..exclusive]. Or, in other words [0..5] includes the zeroth character in the string, but omits the fifth. With the range syntax, you can omit the first number to start at 0, and omit the second number to end at the length of the string.

let s = String::from("rust time");

let rust = &s[..4];
let time = &s[5..];
let rust_time = &s[..];

Slices must occur at valid UTF-8 character boundaries. If you attempt to create a string slice in the middle of a multibyte character, your program will exit with an error. (Don't know what a multibyte character is? See chapter 8!)

Note that if you have a string slice, this counts as a reference, so you can't also have a mutable reference to that String:

fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();

for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}

&s[..]
}

fn main() {
let mut s = String::from("hello world");

// `word` ends up being a slice of `s`, so
// `word` counts as a reference to `s`.
let word = first_word(&s);

s.clear(); // error!

println!("the first word is: {}", word);
}

Inside main, word is a string slice from s, and therefore a reference to the memory the String uses. The call to s.clear() will fail to compile because to clear the string, we'd need to mutate it (clear is a method with a mutable reference to self). Since we can't create a mutable reference while word is in scope, this fails to compile.

String Literals as Slices

let s = "Hello, world!";

The type of s here is &str: it's a slice pointing to where this string is stored in the binary.

String Slices as Parameters

These two function signatures are very similar:

fn first_word_string(s: &String) -> &str {...}

fn first_word_str(s: &str) -> &str {...}

The first takes a reference to a String, the second takes a string slice. The second one, though, is generally preferred. It's trivial to convert a string to a slice, so you can call the second with any String, string slice, or string literal, or even a reference to a String (see chapter 15 for more on deref coercion).

In the reverse direction, it's a bit tedious to convert a string slice into a String. As a result the first version, first_word_string, is much less flexible.

Other Slices

Much like in Go, we can also create slices from arrays:

let a = [1, 2, 3, 4, 5];

let slice = &a[1..3];

assert_eq!(slice, &[2, 3]);

The type of slice here is &[i32].

Continue to chapter 5.

+ \ No newline at end of file diff --git a/ch05-structs/index.html b/ch05-structs/index.html index 23bffe4..6840e69 100644 --- a/ch05-structs/index.html +++ b/ch05-structs/index.html @@ -4,13 +4,13 @@ 5 - Using Structs to Structure Related Data | The rs Book - +

5 - Using Structs to Structure Related Data

5.1 - Defining and Instantiating Structs

If you're coming from Go, then a struct in Rust is very similar to a struct in Go. It has public and private fields and you can define methods on a struct. If you're coming from JavaScript or Java, then a struct in Rust is similar to a class, except that a struct can't inherit from another struct. In any of these languages, a trait is very similar to an interface.

If you're coming from C/C++, then a Rust struct is sort of like a C struct except you can add methods to it like a C++ class. If you're coming from some other language, I'm going to assume the concept of a struct or "object" isn't totally foreign to you.

Here's what a struct looks like in Rust:

struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}

fn main() {
// Create an instance of User
let mut myUser = User {
active: true,
username: String::from("jwalton"),
email: String::from("jwalton@example.com"),
sign_in_count: 1,
};

// Access fields with `.`
println!("Name: {}", myUser.username);

// Variable must be declared as `mut` if we want to be
// able to modify the structure.
myUser.email = String::from("other_email@example,com");
}

Note if you want to modify the contents of a struct, is has to be marked as mut. You can't mark individual fields as mutable - either the whole structure is, or none of it is.

tip

If you're curious about how Rust structures are laid out in memory, check the Rust Reference's section on Type Layout.

Using the Field Init Shorthand

Much like in JavaScript, we can initialize fields with a shorthand:

fn build_user(email: String, username: String) -> User {
User {
active: true,
// Instead of `username: username,` we can do:
username,
email,
sign_in_count: 1,
}
}

Creating Instances from Other Instances with Struct Update Syntax

Rust has something called the struct update syntax which allows us to copy fields from another struct (and which is very similar to the spread operator in JavaScript). This example will set the email of user2, and then copy all other fields from user1:

let user2 = User {
email: String::from("yet_another_email@example.com"),
..user1
}

When you store a field in a structure, or use the struct update syntax as in this example, from an ownership perspective you are moving that field. In this example, once we create user2, we can no longer use user1 because its username field has been moved. If we had given user2 an email and a username, then all the remaining fields we assigned from user1 would be basic data types that implement the Copy trait. In this case, nothing would move, so user1 would still be valid.

Using Tuple Structs Without Named Fields to Create Different Types

Tuple structs are basically named tuples:

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn main() {
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
}

Note here that Color and Point are two different types, even though they have the same structure. If you have a function that accepted a Color, the compiler will complain if you try to pass in a Point.

Unit-Like Structs Without Any Fields

You can define a struct without any fields. These are used when you want to implement some trait (see chapter 10) but you don't have any data you actually want to store in your struct:

struct AlwaysEqual;

fn main() {
let subject = AlwaysEqual;
}

You don't even need the {} to create an instance of AlwaysEqual.

Ownership of Struct Data

In the User struct above, we used a String type in the struct for the username and email. This means that the struct owns that String. When the struct is dropped, those two strings will be dropped too. We could instead have used an &String or &str, in which case the struct would store a reference to the string, and wouldn't own the string directly. The struct would be borrowing the string. We're not going to show an example of this though, because to do this we need something called a lifetime annotation, which we'll talk about in chapter 10.

5.2 - An Example Program Using Structs

A quick example of a program that uses a struct:

struct Rectangle {
width: u32,
height: u32,
}

fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};

println!(
"The area of the rectangle is {} square pixels.",
area(&rect1)
);
}

fn area(rectangle: &Rectangle) -> u32 {
rectangle.width * rectangle.height
}

area takes an immutable reference to the Rectangle struct. We know when we call area, it won't modify our struct (even if rect1 was declared as mutable in the caller). Passing a reference means the caller will retain ownership. Also, accessing fields on the borrowed struct doesn't move them.

Adding Useful Functionality with Derived Traits

It would be cool if we could "print" a rectangle:

    let rect1 = Rectangle {
width: 30,
height: 50,
};

println!("A rectangle: {}", rect1); // This will error.

In Java and JavaScript we could to this with a toString method. In Go we could implement the Stringer interface. In Rust we have two different traits we can implement: std::fmt::Display and Debug. The Debug trait is one that's intended, as the name suggests, for debugging and it's the one we want here.

Instead of implementing this trait ourselves, we can derive this trait, which is a fancy way of saying we can let Rust generate the code for this trait for us:

#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}

fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};

println!("A rectangle: {:?}", rect1);
}

If you run this, it will print:

A rectangle: Rectangle { width: 30, height: 50 }

The placeholder in println! has changed from {} to {:?}, which lets println! know we want the debug output format. We could also use {:#?} to "pretty print" the output.

There's also a handy macro called dbg! which will pretty-print the value, and the file name and source line. dbg!(&rect1); would print something like:

[src/main.rs:13] &rect1 = Rectangle {
width: 30,
height: 50,
}

Note that unlike println!, dbg! takes ownership of the value passed in, so we pass a reference to rect1 instead of passing rect1 directly to prevent this. There are a number of other derivable traits - see Appendix C. And, again, to learn more about traits see chapter 10.

5.3 - Method Syntax

Methods are functions defined on a struct. Their first parameter is always self, which represents the instance of the struct the method is being called on (similar to this in other languages).

#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}

impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}

// Returns true if `other` Rectangle can fit inside this one.
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}

fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};

println!(
"The area of the rectangle is {} square pixels.",
rect1.area()
);
}

The impl (implementation) block defines methods and associated functions on the Rectangle type. area takes a reference to &self, which is actually a short form for self: &Self.

A method will generally have one of three different parameters for self:

  • fn method(&self) is a method with an immutable borrow of self. Like any other parameter, it is immutable by default.
  • fn method(& mut self) is a method with a mutable borrow of self, which means this method can change fields on self.
  • fn method(mut self) is a method that takes ownership of self. These methods you won't see as often, because once you call such a method the receiver is no longer valid, but these methods are often used to transform a value into some other structure. An example is the map method on iterator, which destroys the original iterator and returns a new one.

You can have a method on a struct with the same name as one of the fields. This is most commonly used to add a getter method to a struct. You can make it so a rectangle has a private width: u32 field, and a public width(): u32 method, which effectively makes width read-only. (What are public and private fields and methods? You'll have to wait for chapter 7.)

Automatic Referencing and Dereferencing

You may have noticed that area takes a ref to self, but we called it as rect1.area() and not (&rect1).area(). Much like in Go, Rust has automatic referencing and dereferencing. When you call a method on a struct, Rust will automatically add in the &, &mut, or * so the object matches the signature of the method.

Continue to chapter 6.

- + \ No newline at end of file diff --git a/ch06-enums-and-pattern-matching/index.html b/ch06-enums-and-pattern-matching/index.html index 2f37cc4..aaebd25 100644 --- a/ch06-enums-and-pattern-matching/index.html +++ b/ch06-enums-and-pattern-matching/index.html @@ -4,13 +4,13 @@ 6 - Enums and Pattern Matching | The rs Book - +

6 - Enums and Pattern Matching

6.1 - Defining an Enum

An enum allows you to define a type by enumerating its possible variants. If you're coming from some other language, you probably think of an enum as basically a fancy way to assign names to a set of numbers. A Rust enum goes beyond this, as each variant of an enum can have some data associated with it, much like a struct.

// Define an enum.
enum IpAddrKind {
V4,
V6,
}

// Use one of the variants from the enum.
let ip_address_kind = IpAddrKind::V4;

IpAddrKind::V4 and IpAddrKind::V6 are both of type IpAddrKind.

We can also attach data to an enum:

enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(u8, u8, u8),
}

fn main() {
let m1 = Message::Quit;
let m2 = Message::Move { x: 7, y: 9 };
let m3 = Message::Write(String::from("Hello"));
let m4 = Message::ChangeColor(0, 255, 255);
}

Note that the different variants can take different types of associated data, and the data can either have named values like a struct or a set of values like a name tuple. Importantly though, all are of type Message.

We can also defined methods on an enum:

impl Message {
fn call(&self) {
// method body would be defined here
}
}

let m = Message::Write(String::from("hello"));
m.call();

The call function here might use a match statement to perform different actions based on the variant of the Message enum.

The Option Enum and Its Advantages Over Null Values

As mentioned earlier in this book, Rust has no null value. In most languages, you can dereference a null pointer, and it will cause your program to crash at runtime. Rust has a strong focus on safety and makes the conscious decision to not allow null pointers to exist in the first place.

However, sometimes we have a value that might not be defined. For example we might have a "middle_name" field on a User, but some users might not have a middle name. Rust handles this with the Option enum, defined by the standard library. Option is used frequently in Rust - so frequently that both Option and it's two variants are in the prelude, so you don't have to use it to bring it into scope.

Option is defined as:

enum Option<T> {
None,
Some(T),
}

The <T> means this is a generic enum - it can hold a value of any type. We'll talk more about generics in chapter 10, but this is very similar to generics and template classes in other languages. When we use an Option, we have to specify a concrete type for T which defines a new type. For example, an Option<i32> can be None, or it can be Some(i32).

// No need for `std::Options::Some`, or even `Option::Some`,
// because these are in the prelude.
let some_number = Some(5);
let some_str = Some("Hello");

// Need to explicitly annotate type here, since Rust
// can't automatically infer what type to use for
// `T` from `None`.
let absent_number: Option<i32> = None;

In this example some_number will be of type Option<i32>, and some_str will similarly be Option<&str>. The None case here is a bit like null in a traditional language. None serves as a marker that a value isn't present. We're not going to be able to use use an Option<i32> in place of a regular i32 though:

let x = 7;
let y = Some(8);

// This will fail, since x and y are mismatched types
let z = x + y;

The difference between Option in Rust and null in other languages is that we can't just use an Option, we have to explicitly handle the case where the value might not be there. What we need is a way to convert an Option<T> into a T. If you have a look at Option in the Rust Reference you'll see that it has many methods defined on it that provide different ways to extract the underlying value from an Option, each with different ways of handling the case where the value is None.

let x = Some(8);

// If x is Some then use the value, otherwise panic.
let must_exist = x.expect("x should never be undefined here");

// Same as expect, but uses a generic message.
let must_exist_2 = x.unwrap();

// If x is Some use the value, otherwise
// if x is None use 9.
let with_default = x.unwrap_or(9);

// If x is Some use the value + 1, otherwise use 0.
// We'll talk about match more in the next section!
let from_match = match x {
Some(v) => v + 1,
None => 0
};

6.2 The match Control Flow Construct

match is a bit like a switch/case statement on steroids. Formally, a match consists of an expression, and then one or more "arms" with patterns that try to match the expression. The pattern for each arm is evaluated in order, and the code associated with the first arm that matches will be executed. The pattern side is quite a bit more flexible than a switch/case statement (see chapter 18). One thing to note about a match expression is that the patterns must cover every possible value - they must be exhaustive. If there are any possibly-unhandled-values, the compiler will error.

Here's an example of a match expression, taken directly from the original Rust Book:

enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}

fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}

Patterns That Bind to Values

A match expression can bind to parts of values that match the pattern. This can be used to extract one or more values out of an enum. We saw a quick example of this when handling Options in the previous section:

let x = Some(7);

let from_match = match x {
Some(v) => v + 1,
None => 0
};

Here v gets bound to the contents of Some. Let's see this in action with our coin example from before. Let's change the Quarter variant of the Coin enum so it tells us which Canadian province this quarter is from:

#[derive(Debug)] // so we can inspect the state in a minute
enum Province {
Alberta,
BritishColumbia,
// --snip--
}

enum Coin {
Penny,
Nickel,
Dime,
Quarter(Province),
Loonie,
Toonie,
}

fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(province) => {
println!("Quarter from {:?}!", province);
25
}
Loonie => 100,
Toonie => 200,
}
}

Each arm of the match can have a simple expression, or a block of code. As with functions, if the block ends with an expression this will be used as the value for that arm. If we were to call this with a Coin::Quarter(Province::Ontario), then in the Quarter arm of the match, province would be bound to Province::Ontario.

Let's take a moment here to reflect on ownership implications. In this case, the Province enum implements the Copy trait, so we don't have to worry about ownership as the Province enum is going to be allocated on the stack. If we change the Coin enum so it is Quarter(String) however, then binding province inside the match would move ownership of the String out of the coin and we wouldn't be able to use it again outside the match! We could fix this by borrowing the value instead, either by changing the match expression to match &coin or to make value_in_cents take a reference to the Coin as a parameter instead of the Coin itself.

Catch-all Patterns and the _ Placeholder

A match expression must be exhaustive - it has to cover all possible cases. Sometimes we have a number of cases we want to treat the same way, or enumerating all cases would be impractical. If we wrote a match and passed in an i32 as the expression, we certainly wouldn't want to write out all 4 billion possible values the i32 could be! The catch-all pattern lets us create a default arm (similar to default in a switch/case statement in many languages).

The example used by the original Rust Book is that you're building a board game where a player rolls a dice. On a roll of a 3, the place gets a hat. On a 7, the player loses their hat. On any other dice roll, they move that many spaces:

let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
other => move_player(other),
}

Here we have explicit arms for the 3 and 7 case, and then we have a catch-all pattern that binds the value of dice_roll to other. If we didn't actually want to use the value in the catch-all case, we wouldn't want to bind the value to a variable, since we'd get a warning from the compiler about an unused variable. In this case, we can replace other with _:

match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
_ => (),
}

Here we're making this arm evaluate to the empty unit tuple, explicitly telling Rust that we don't want to do anything in this case. (Note that unlike other, _ also doesn't bind the variable, which has ownership implications! See chapter 18.

6.3 - Concise Control Flow with if let

On other languages you can convert a switch/case statement into a series of if/else statements. You can do the same in Rust. You could write:

    let config_max = Some(3u8);
match config_max {
Some(max) => println!("The maximum is configured to be {}", max),
_ => (),
}

But this is a bit verbose, considering the default arm does nothing. We can rewrite this as an if statement with if let:

let config_max = Some(3u8);
if let Some(max) = config_max {
println!("The maximum is configured to be {}", max);
}

if let takes a pattern and an expression, separated by an equals sign, and works exactly like the arm of a switch. The downside to if let over a match statement is that the compiler does not force you to exhaustively handle every possible scenario.

Continue to chapter 7.

- + \ No newline at end of file diff --git a/ch07-packages-crates-modules/index.html b/ch07-packages-crates-modules/index.html index b7bcedb..dfb5bcb 100644 --- a/ch07-packages-crates-modules/index.html +++ b/ch07-packages-crates-modules/index.html @@ -4,13 +4,13 @@ 7 - Managing Growing Projects with Packages, Crates, and Modules | The rs Book - +

7 - Managing Growing Projects with Packages, Crates, and Modules

So far all of our examples have lived in a single file, but almost any non-trivial program would be too large to fit in a single file. Rust provides a number of tools to help us organize a project:

  • Modules group together related code, and let you control what is a private implementation detail and what should be public interface.
  • Crates are a tree of modules that produce a library or an executable. Back in chapter 2 we installed the rand crate to help us generate random numbers.
  • Paths are used to reference a function, struct, enum, etc... from some other module or crate.
  • Packages are what you actually check into git (or your source control of choice) - a package contains a library crate, one or more binary crates, or both.
  • Workspaces let you group together a set of related packages, similar to a monorepo. We'll wait until chapter 14 to talk about workspaces

7.1 Packages and Crates

In many compiled languages - C or Java for example - the "unit of compilation" is a single file. In a C project, every .c source file compiles to a single object file, and then all the object files get linked together into a single executable (or into a dynamically linked library). If you change a .c file in a big project, you only have to recompile that single .c file and then relink all the object files.

In Rust, the unit of compilation is the crate. Crates come in two forms - library crates and binary crates, but most of the crates you're going to deal with are libraries, so the terms "crate", "library", and "library crate" are all used interchangeably when talking about Rust libraries.

A package is purely a cargo concept (in other words, rustc doesn't know anything about packages). A package is what you get when you run cargo new - a Cargo.toml file, and a src folder (possibly with subfolders) containing one or more source files.

The crate root is the file that rustc starts working from when it compiles a given crate. If a package contains a src/main.rs file with a main function, then the package contains a binary crate with the same name as the package. If a package contains a src/lib.rs, then it contains a library crate (again with the same name as the package). If it has both, then the package contains both a library crate and a binary crate. Having both is a very common pattern. For example, if you were writing a library to convert JPG images to PNG format, you might include both a library crate that other developers can use and a binary crate implementing a command line tool that uses the library.

If you want to include more than one binary crate in a package, you can add files in src/bin. Each file placed there will be compiled as a separate binary crate.

7.2 Defining Modules to Control Scope and Privacy

A module is quite similar to a package in Go, and is somewhat similar to a package in Java. If you're a JavaScript developer, then a module is close to an ESM module, except you can split a module up over multiple files.

rustc always starts at the crate root (usually src/main.rs or src/lib.rs) - it compiles this file, and any time it finds a mod statement, it adds the associated module to the crate and compiles it too. Suppose in main.rs we have the statement mod garden - Rust will look for the code for this module in three places:

  • Inline (e.g. mod garden { /* code goes here */ })
  • In src/garden.rs.
  • In src/garden/mod.rs (older style)

Similarly modules can defined submodules. src/garden.rs can have a mod vegetables that might be defined in src/garden/vegetables.rs (note that garden's submodules go in a folder named "garden", not in the same folder).

Note that we've marked the src/garden/mod.rs version as "older style". This is still supported (and as we'll see in chapter 11 it's very handy for writing integration tests) but the src/garden.rs is the one you should use by default. If you try to use the mix the [name].rs and [name]/mod.rs styles in the same module, you'll get a compiler error.

Every identifier at the top level of a Rust module has a path associated with it. If we created a struct called Asparagus in the vegetables module, then the absolute path for that struct would be crate::garden::vegetables::Asparagus. (This is a little bit like a file system path, but with ::s instead of slashes.)

Each identifier declared in a source file is also private by default, meaning it can only be accessed by functions inside that module (or it's submodules - submodules always have visibility into the private details of their ancestors). To make any identifier public you use the pub keyword, like pub fn do_the_thing() {...} or pub mod garden.

The use keyword is used to bring paths into scope. If you use crate::garden:vegetables::Asparagus in a file, then you can write Asparagus to refer to this struct instead of using the full path.

In the restaurant industry, the dining room and other places of the restaurant where customers go is called the "front of house" and the kitchen and offices and parts where customers are rarely seen are called the "back of house". Let's create library for managing a restaurant. We'll run cargo new restaurant --lib to create a library crate, and in src/lib.rs we'll put:

src/lib.rs
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}

fn seat_at_table() {}
}

mod serving {
fn take_order() {}

fn serve_order() {}

fn take_payment() {}
}
}

We'll just defined the modules inline here, because this is convenient for the purposes of an example, but usually we'd split this modules up into multiple files.

7.3 Paths for Referring to an Item in the Module Tree

To refer to an item in the module tree, we use a path. Paths come in two forms:

  • An absolute path starts from the crate root. For external library creates we're using, this starts with the name of the crate (e.g. rand) and for code within the current crate it starts with crate.
  • A relative path starts from the current module. It starts with an identifier in the current module or with self or super.

Using our restaurant example, let's say we want to call the add_to_waitlist function. From the top level of src/lib.rs we could do this in two ways:

src/lib.rs
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}

pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();

// Relative path, since this function is defined
// the same modules as `front_of_house`.
front_of_house::hosting::add_to_waitlist();
}

Relative paths have the clear advantage that they are shorter. Absolute paths have the advantage that, if you move a function from one module to another, all the absolute paths in that function won't have to change (although obviously all the paths pointing to the moved function will).

We have to mark hosting and add_to_waitlist as pub in order for eat_at_restaurant to compile. This is because eat_at_restaurant is defined at the root of the crate, and hosting and add_to_waitlist are in child modules. A parent cannot access the private contents of a another module, unless that other module is an ancestor - add_to_waitlist could access private members of front_of_house or the root of the crate.

Starting Relative Paths with super

The super keyword is used in a module path in exactly the same way as .. is used in a file path - it goes up one level in the module tree:

src/lib.rs
fn deliver_order() {}

mod back_of_house {
fn fix_incorrect_order() {
cook_order();
// Call into `deliver_order` in the parent module.
super::deliver_order();
}

fn cook_order() {}
}

Making Structs and Enums Public

pub is used to make an identifier visible outside of the module, but there are a few special considerations for structs and enums. When you make a struct public, by default all of it's fields are private and can only be accessed inside the module. You need to mark individual fields as pub if you want them to be visible to code outside the module:

src/lib.rs
mod back_of_house {
pub struct Breakfast {
pub toast: String,
seasonal_fruit: String,
}

impl Breakfast {
// Constructor for a summer Breakfast.
pub fn summer(toast: &str) -> Breakfast {
Breakfast {
toast: String::from(toast),
seasonal_fruit: String::from("peaches"),
}
}
}
}

pub fn eat_at_restaurant() {
// Order a breakfast in the summer with Rye toast
let mut meal = back_of_house::Breakfast::summer("Rye");

// Change our mind about what bread we'd like.
// We can write directly to `toast` because it is `pub`:
meal.toast = String::from("Wheat");
println!("I'd like {} toast please", meal.toast);

// The next line won't compile if we uncomment it; we're not allowed
// to see or modify the seasonal fruit that comes with the meal,
// since we're outside of the `back_of_house` module.
// meal.seasonal_fruit = String::from("blueberries");
}

The pub toast field can be read and written outside of the back_of_house module, but the private seasonal_fruit cannot. Note that the existence of this private field implies that other modules won't be able to create a new instance of Breakfast, since they won't be able to set this private field. Here we've created a public associated function called summer to act as a sort of constructor.

Enums behave exactly the opposite to structs. When we make an enum pub, all of it's variants and all fields defined on all variants are automatically pub as well. There's no way to have an enum with some fields that are private.

7.4 - Bringing Paths into Scope with the use Keyword

We've already seen many examples of using the use keyword to bring something into scope. We can use it with modules within our crate to bring members from a child module into scope, too:

src/lib.rs
use crate::front_of_house::hosting;

mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}

pub fn eat_at_restaurant() {
// Don't need to write `front_of_house::hosting::add_to_waitlist()`
// here because we brought `hosting` into scope with the `use`
// above.use crate::front_of_house::hosting;

hosting::add_to_waitlist();
}

You can think about use a bit like a symbolic link in a file system, or a bit like JavaScript's import { add_to_waitlist } from './hosting.js'. It adds a symbol to the scope of the use statement.

One thing to note about modules is that, whether they're split into another file or used inline, the mod keyword always creates a new scope that doesn't inherit anything from the parent scope. When we create a scope using braces, in most cases we assume all symbols from outside those braces will be available in the child scope. For example:

fn say_hello() {
let name = "Jason",

{
println!("Hello {}!", name);
}
}

Here name is visible inside the scope created by the inner braces. The scope created by mod however doesn't bring in anything from the parent scope:

src/lib.rs
mod front_of_house {
mod serving {
fn serve_order() {}
}

pub mod hosting {
fn seat_at_table() {
// This won't compile! `serving` is undefined here,
// even though it was defined one scope up.
serving::server_order();
}
}
}

This also means a use statement at the top level of a file will only bring a symbol into scope for the top-level module, and not for any inline modules. If you want to use a symbol inside an inline mod, you'll need to put the use inside that module:

src/lib.rs
use crate::front_of_house::hosting;


mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}


mod customer {
// We need this `use` here, even though we're already
// `use`ing hosting at the top level, because
// `mod customer` creates a new scope that doesn't
// inherit any symbols.
use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
}

This seems a bit strange at first, but it makes sense when you realize that modules are generally intended to be split across multiple files. If you move the contents of an inline mod out into a file, you won't have to deal with the fact that there are a bunch of symbols you don't have access to anymore.

Creating Idiomatic use Paths

These two listings do the same thing:

// Version 1
use crate::front_of_house::hosting;
fn fn1() {
hosting::add_to_waitlist();
}

// Version 2
use crate::front_of_house::hosting::add_to_waitlist;
fn fn2() {
add_to_waitlist();
}

But the first one is considered idiomatic and the second is not. Generally we use a module, and don't use individual functions within a module. This makes for more typing, because we need to type the name of the module, but it also makes it clear that the function comes from some other module and isn't defined locally.

On the other hand, when bringing structs and enums into scope, we generally use the individual struct or enum instead of the parent module. For example, we use std::collections::HashMap;, and then just type HashMap.

If you want to use two data types from different modules that have the same name, you can either refer to them by their namespace:

use std::fmt;
use std::io;

fn fn1() -> fmt::Result {...}
fn fn2() -> io::Result {...}

Or you can use the as keyword to rename a symbol:

use std::fmt::Result as FmtResult;
use std::io::Result as IoResult;

fn fn1() -> FmtResult {...}
fn fn2() -> IoResult {...}

Re-exporting Names with pub use

You can "re-export" a symbol from some other module in your module:

mod colors {
pub struct Color {
red: u8,
green: u8,
blue: u8,
}
}

mod ansi {
pub fn color_string(message: &str, color: crate::colors::Color) -> String {
// --snip--
}

// Re-export `Color` from colors.
pub use crate::colors::Color;
}

fn log_error(message: &str) {
let red = ansi::Color{red: 255, green: 0, blue: 0};
println!("{}", ansi::color_string(message, red));
}

Here callers of ansi can use ansi::Color, even though Color us actually defined in the colors module. This is very handy when there's a type you're using from some other crate that's central to your module. It's also handy when the internal organization of your library might be different than the public API you want to share. We'll talk about this idea more in chapter 14.

Using External Packages

Many useful crates are published on crates.io, and we can use these by adding them to the "dependencies" section of Cargo.toml. We did this back in chapter 2 when we built our guessing game. There we added the rand crate:

Cargo.toml
[dependencies]
rand = "0.8.5"

And then we used the Rng trait from rand:

use rand::Rng;

fn main() {
let secret_number = rand::thread_rng().gen_range(1..=100);
}

Using Nested Paths to Clean Up Large use Lists

These two sets of use statements are equivalent:

// This:
use std::cmp::Ordering;
use std::io;


// Can be shortened to this:
use std::{cmp::Ordering, io};

There's no difference between these, the second is just shorter. If we want to use a module and some members of that module, we can use the self keyword:

// This:
use std::io;
use std::io::Write;


// Can be shortened to this:
use std::io::{self, Write};

The Glob Operator

This brings all public symbols in the io::prelude module into scope:

use io::prelude::*;

This is generally something you'd only do in two cases. The first is this example, where a crate has defined a custom prelude that brings a lot commonly used symbols into scope. The second involves unit tests; we generally write tests for a given module in a child module called "tests", so we very frequently use super::*; to bring everything in the module we're testing down into the tests module. We'll talk more about testing in chapter 11.

Separating Modules into Different Files

We've used the inline style for modules in this chapter because we've been working with short examples, but in real life any non-trivial program is going to be split across multiple files:

In src/lib.rs:

src/lib.rs
mod front_of_house;

pub use crate::front_of_house;

pub fn eat_at_restaurant() {
front_of_house::add_to_waitlist();
}

And then in src/front_of_house.rs:

src/front_of_house.rs
pub fn add_to_waitlist() {}

You only need to load a file with mod once in your entire module tree, not in every place it is used. mod is not like include or import from other programming languages. There just has to be one mod somewhere to let rustc know it should include the file in the crate.

Note that it's perfectly acceptable to have a small module be defined inline. You can always move it into its own file later if it grows. Since the path of a symbol doesn't change based on whether a module is inline or in a separate file, moving inline code into files doesn't require any refactoring work. Tests are generally defined in an inline module.

Continue to chapter 8.

- + \ No newline at end of file diff --git a/ch08-common-collections/index.html b/ch08-common-collections/index.html index 075a7ea..5cd2491 100644 --- a/ch08-common-collections/index.html +++ b/ch08-common-collections/index.html @@ -4,13 +4,13 @@ 8 - Common Collections | The rs Book - +

8 - Common Collections

Rust's standard library includes a number of collections which store data on the heap:

  • Vec<T> (short for Vector) is essentially a variable length array.
  • String is a variable length collection of characters - like a special vector just for characters.
  • HashMap allows you to associate values with keys.

These are the most common collections, but if you check the collections documentation you'll see there are many others.

8.1 - Storing Lists of Values with Vectors

A Vec<T>, also known as a vector is a bit like a "variable-length array". It's a generic type (see chapter 10), so it can hold any type. Internally, vector stores it's data in a contiguous block of memory which it resizes as required. Since the size of a vector isn't know at compile time, it stores values on the heap. Vectors are used often in Rust programs, so Vec is part of the prelude.

If you want to know the details about how a vector is implemented, check out The Rustonomicon. Also be sure to check out the API documentation for methods defined on vector that we don't discuss here.

Creating a New Vector

Like many structs in Rust, we can create a vector with the associated new function:

let v: Vec<i32> = Vec::new();

Since we didn't create this vector with any initial values, and we're not calling any functions on it, it's impossible for rustc to infer the generic type of this vector, so we need the Vec<i32> type annotation.

If you want to create a vector from an array of initial values, Rust provides the vec! macro for this:

let v = vec![1, 2, 3];

Updating a Vector

let mut v = Vec::new();

// Add some members to the vector.
v.push(5);
v.push(6);
v.push(7);

We don't need to annotate the type of v here. Since we're passing i32s to push, Rust can infer the type of the vector. We do need to mark v as mutable though, otherwise we wouldn't be allowed to call push. In addition to push, there's a pop method that removes and returns the last element.

Reading Elements of Vectors

You can read elements from a vector using the same syntax you'd use to index elements of an array, or you can use the get method:

let v = vec![1, 2, 3, 4, 5];

let third = &v[2]; // This is an &i32.

let fourth = v.get(3); // This is an Option<&i32>

get returns an Option; if we try to get an index outside the bounds of the vector, then get will return None. Since the [] syntax doesn't return an Option then, as you might expect, it will cause a panic if you try to retrieve an index which is out-of-bounds.

Here's a quick example that looks at first glance like it ought to work, but will fail to compile:

let mut v = vec![1, 2, 3, 4, 5];

let first = &v[0];

v.push(6); // This will fail to compile!

println!("The first element is: {first}");

The simple explanation of why this doesn't work is that, as you may recall from chapter 4, if you have a mutable reference to a value, you can have no other references to that value. Here v.push(6) needs a mutable reference to the vector, which it can't get because of the ref stored in first. But first is just a ref to the first item in the vector? Why does that prevent us from modifying some other part of the vector?

The problem here has to do with the way that vector is implemented. Vector is, at heart, an array. When we push an element onto the vector, if there isn't enough room in the underlying array, vector will allocate a new chunk of memory to hold a larger array and copy elements from the old array to the new one. This means that the reference to first is really a pointer to a part of that buffer. If push were to free this memory, then first would point to unallocated memory.

Iterating over the Values in a Vector

We can use a for in loop to iterate over elements in a vector:

let v = vec![100, 32, 57];
for i in &v {
println!("{i}");
}

If our vector is mutable, we can mutate it as we iterate:

let mut v = vec![100, 32, 57];
for mut i in &mut v {
*i += 50;
}

Note the explicit '*` dereference operator here. We'll talk more about the dereference operator in chapter 15.

Using an Enum to Store Multiple Types

A vector can only store elements that are all the same type. Recall though that the different variants of an enum are all the same underlying type, so we can use this to our advantage. Here's an example of a vector of spreadsheet cells where each cell holds different data:

enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}

let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
];

This trick only works if you know all the types you want to store in a vector ahead of time. If you don't, you can use a trait object, which we'll discuss in chapter 17.

Dropping a Vector Drops Its Elements

Once a vector goes out-of-scope, like any other struct it gets dropped and the memory associated with it is freed.

8.2 - Storing UTF-8 Encoded Text with Strings

We're going to talk about strings here in the chapter on collections, because a string is basically a vector of UTF8 bytes. Strings can seem a bit more finicky in Rust than in other languages, but in actual fact most other languages let you do very unsafe things with Strings and the real difference is that Rust tries to protect you from this.

What Is a String?

In the core language, Rust only defines the string slice &str. This represents a string of characters that is stored "somewhere else", like in the program binary. The String type in Rust isn't actually part of the core language, but part of the standard library.

A String represents a growable, mutable UTF-8 encoded string stored on the heap.

Creating a New String

We can create a String with new, just like a vector:

let mut s = String::new();

Any type that implements the Display trait has a to_string method which can be used to create a string:

let s = "Hello, world!".to_string();

And as we've seen we can also use String::from to copy a string literal into a String:

let s = String::from("Hello, World!");

to_string and String::from do exactly the same thing here. Which you choose is a matter of style and readability.

Updating a String

Strings, like vectors, can grow in size and be modified. You can concatenate Strings with the + operator or the format! macro.

Some examples:

let mut foobar = String::from("foo");

// push_str() appends a string slice.
foobar.push_str("bar");

// push() appends a single character.
foobar.push('!');

We can concatenate with the + operator, similar to JavaScript, Java, and Go:

// + can be used to concatenate strings:
let s1 = String::from("Hello, ");
let s2 = String::from("World!");
let s3 = s1 + &s2;

The + operator here will take ownership of s1, so s1 won't be valid after the + expression. The eagle eyed among you will notice we didn't write s1 + s2, but s1 + &s2. The + operator is actually implemented using the add method on String. If you look up add, you'll it is implemented using generics, but basically in this particular case the function signature boils down to:

fn add(self, s: &str) -> String

And so the second parameter needs to be a reference. Those readers who are especially alert might also notice that we're passing an &String to the add operator here and not an &str for that second parameter, but this is okay because Rust can coerce the type of &s2 to &str. We'll talk more about this in chapter 15.

If we want to concatenate multiple strings, we can use the format! macro, which is a bit like println! except it evaluates to a String instead of printing the result to the screen:

let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");

let s = format!("{s1}-{s2}-{s3}");

format! doesn't take ownership of any of these values.

Indexing into Strings

Unlike with a vector, you can't index into a string with [].

Many languages treat indexing into a string as getting the nth byte from a string, but mainly this is a throwback to the days when we used encodings like 8-bit ASCII, where a single byte was a single character on the screen. Rust doesn't allow you to index into a String like this. You can use the s.bytes() method to get an iterator over the underlying bytes, or s.chars() to get at the underlying unicode scalar values.

Bytes and Scalar Values and Grapheme Clusters! Oh My!

We can look at any UTF-8 string as a series of bytes, but we can also look at it as a series of chars (which represent unicode scalar values), or as a series of grapheme clusters (i.e. how many "letters" it takes up on the screen).

The word "Hello" contains five bytes, five letters, and five grapheme clusters. If you replaced the first byte with a 74, you'd change it to "Jello".

The Ukrainian word for "rust" is "іржа", which contains 8 bytes: [209, 150, 209, 128, 208, 182, 208, 176]. Each of pair of bytes encodes a single unicode scalar value, so there are four chars in this, and there are also four grapheme clusters. If you replaced the first byte with a 74 you'd transform it into an invalid unicode string.

This is a female astronaut emoji: "🧑‍🚀". She's actually two other emoji joined together with a zero-width-joiner. Our astronaut emoji contains a total of eleven bytes: [240, 159, 167, 145, 226, 128, 141, 240, 159, 154, 128]. These bytes are the encoding of only three chars : ['🧑', '\u{200D}', '🚀']. And, this string contains only a single grapheme cluster - it displays as a single "letter" in the text of this book.

Slicing Strings

While you can't index a single byte or char out of a string, you can get a slice of bytes out of a string:

let hello = "Здравствуйте";

let s = &hello[0..4];

Here s ends up being a &str that contains the first four bytes of the string, which will be the string "Зд". If you tried to get &hello[0..1], this would return the first byte, but since this would result in an invalid UTF-8 string, this will cause a panic. You need to be extremely careful when slicing strings that you are slicing at valid char boundaries.

Methods for Iterating Over Strings

If you want to get at the underlying collection of characters or the collection of bytes, the best way is to use methods defined on String to get these explicitly:

// Get at the underlying chars in a string
for c in "Зд".chars() {
println!("{c}");
}

// Get at the underlying bytes
for b in "Зд".bytes() {
println!("{b}");
}

Getting Grapheme clusters from a String is a surprisingly non-trivial problem (it's called "Unicode text segmentation" if you're interested), so Rust doesn't provide a function to do it in the standard library. If you need this functionality, you'll have to look to a third party crate. If you're not careful about this, though, it's easy to insert a "-" into the middle of "🧑‍🚀" and accidentally turn it into "🧑-🚀" on-screen.

8.3 - Storing Keys with Associated Values in Hash Maps

HashMap<K, V> stores a mapping of keys of type K to values of type V. This is just like a Map in Go or JavaScript, or a HashMap in Java. The underlying implementation is a hash table with a hashing function that converts each key into a number.

Basic Hash Map Operations

Since HashMap isn't in the prelude, we have to use it:

src/main.rs
use std::collections::HashMap;

fn main() {
let mut scores = HashMap::new();

// Insert some values into the map
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);

// Access values with `get`
let team_name = String::from("Blue");
let score = scores.get(&team_name).copied().unwrap_or(0);

// Iterate over the keys with a for loop:
for (key, value) in &scores {
println!("{key}: {value}");
}
}

Note that .get() here will return an Option<&v>. If there's no value in the map we'll get back a None. We call copied to convert the Option<&i32> into an Option<i32>, and then call unwrap_or to provide a default for the None case. One important thing to note here especially if you're coming from JavaScript is that, when iterating over members of a Hash Map, you are not guaranteed to get them back in the same order you inserted them in. The ordering is arbitrary. (If you need predictable iteration order, a BTreeMap will iterate in sorted order over the keys.)

Hash Maps and Ownership

HashMaps take ownership of both keys and values passed to them:

use std::collections::HashMap;

let field_name = String::from("Favorite color");
let field_value = String::from("Blue");

let mut map = HashMap::new();
map.insert(field_name, field_value);
// field_name and field_value are invalid at this point
// as they have been moved.

For types that implement the Copy trait, the values will be copied into the hash map, so we won't have to worry about ownership. If we don't want the hash map to take ownership, we can store keys or values as references, but in this case the values the references point to must be valid for as long as the hash map exists. For this we need to talk about lifetimes, which we'll do in chapter 10.

Updating a Hash Map

When inserting into a Hash Map, the value we want to insert might already exist. If we just do a straight insert, this will overwrite the value that's already there:

use std::collections::HashMap;

let mut scores = HashMap::new();

scores.insert(String::from("Blue"), 10);

// Overwrite an existing value in the HashMap
scores.insert(String::from("Blue"), 25);

We can use the entry method on a hash map to get information about an existing entry in the map. The entry method returns an Entry enum, which has methods defined on it that allow us to manipulate the map. For example, Entry has an or_insert method which will return the existing entry or insert a new entry if there is nothing stored at that key:

use std::collections::HashMap;

fn main() {
let mut scores = HashMap::new();

scores.insert(String::from("Blue"), 10);

// Set a value only if it doesn't exist
let new_score = scores.entry(String::from("Blue")).or_insert(50);
}

One interesting thing about or_insert is that it returns a mutable reference to the existing or inserted value, which means we can use the return value from or_insert to update the value in the hash map. This example counts how many times each word appears in a string. It works by dereferencing the count in the map with the * operator to let us increment the value for each word:

use std::collections::HashMap;

fn main() {
let text = "hello world wonderful world";

let mut map = HashMap::new();

for word in text.split_whitespace() {
let count = map.entry(word).or_insert(0);
*count += 1;
}

println!("{:?}", map);
}

If you're a JavaScript programmer this is maybe going to look like magic. What's going on here is that count is a reference - essentially a pointer - to memory inside the hash map. We can use that pointer to modify the data in the hash map.

Hashing Functions

Each entry in a hash map is stored using a "hash" of the key, computed by a hash function. In Rust the default hash function is unspecified and may change in future versions, but at the time of this writing the default is SipHash. You can replace the default hash function by creating a custom hasher. You need to implement the BuildHasher trait, or find a crate on crates.io that implements it.

Continue to chapter 9.

- + \ No newline at end of file diff --git a/ch09-error-handling/index.html b/ch09-error-handling/index.html index 9cbe37d..14ae760 100644 --- a/ch09-error-handling/index.html +++ b/ch09-error-handling/index.html @@ -4,13 +4,13 @@ 9 - Error Handling | The rs Book - +

9 - Error Handling

Rust has a tiered error-handling scheme:

  • If something might reasonably be absent, Option is used.
  • If something goes wrong and can reasonably be handled, Result is used.
  • If something goes wrong and cannot reasonably be handled, the thread panics.
  • If something catastrophic happens, the program aborts.

-- The Rustonomicon, Chapter 7: Unwinding

9.1 - Unrecoverable Errors with panic!

We've already discussed a few times when your program will panic: when you try to index an array or vector out-of-bounds, or when you call expect on an Option::None, for example.

You can also force your program to panic with the panic! macro:

fn main() {
panic!("crash and burn");
}

When a panic occurs in the main thread, it halts the program. If the RUST_BACKTRACE=1 environment variable is set, then the program will also print a stack trace showing where the panic happened, although this only works if the binary contains debug symbols. (If a panic occurs in another thread, it will only halt that thread. See chapter 16.)

tip

There's also a todo! macro, an unimplemented! macro, and an unreachable! macro which each work just like the panic! macro. These macros differ from panic! only in semantics. If you have a function you haven't implemented yet, you might add in todo!("Need to implement this!") as a reminder to yourself. If you have a method you need to define to satisfy a trait but you know you will never call it, you could call unimplemented! instead of filling in an implementation.

Unwinding the Stack or Aborting in Response to a Panic

There are two options for what happens when a panic occurs. By default, the program starts unwinding, which means it starts walking back up the stack, freeing memory and cleaning up data. The alternative is aborting in which the program just immediately halts and lets the OS clean up everything (if you've ever written a C program, you've probably at some point seen the dreaded message "segmentation fault (core dumped)" - aborting is a bit like this).

You can switch to using the abort behavior by adding panic = 'abort' to your Cargo.toml file:

[profile.dev]
panic = 'abort'

[profile.release]
panic = 'abort'

Aborting has the advantage that we don't need all the code for unwinding, so our compiled binary will be smaller. In many cases aborting will be faster than unwinding too. If you want to know more about the differences between aborting an unwinding, see the Rustonomicon. Panicking has the advantage that it only terminates a single thread instead of the whole application (although this is the same thing in a single threaded application, and a panic in a thread can cause all sorts of other exciting problems).

9.2 - Recoverable Errors with Result

Many errors can be recovered from. You try to read a file and it doesn't exist, a socket closes unexpectedly, these are not the sorts of things where you typically want to panic. In languages like JavaScript, Java, C++, or Python, these sort os errors are represented as exceptions. In languages like C or Go, errors are explicitly returned from a function.

Rust takes this second approach with the Result enum. Result is similar to the Option enum, but instead of being Some or None, its variants are Ok and Err:

enum Result<T, E> {
Ok(T),
Err(E),
}

Much like Option, Result and its two variants are in the prelude, so there's no need to use them. Result is generic over both the type of result it returns, and the type of error.

Let's see a quick example:

use std::fs::File;

fn main() {
let greeting_file_result = File::open("hello.txt");

let greeting_file = match greeting_file_result {
Ok(file_obj) => file_obj,
Err(error) => panic!("Problem opening the file: {:?}", error),
};
}

If opening the file is successful, we'll fall into the first arm of the match, and file_obj will be a File. If the file doesn't exist or some other error happens, then error will be a std::io::Error.

Matching on Different Errors

In the previous example, we just panic if an error happens opening the file. But there are different reasons why this might error; the file might not exist, we might not have permission to read it, the network might be down. Let's suppose we want to handle the case where the file doesn't exist by creating the file, and in any other case we'll panic:

use std::fs::File;
use std::io::ErrorKind;

fn main() {
let greeting_file_result = File::open("hello.txt");

let greeting_file = match greeting_file_result {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("Problem creating the file: {:?}", e),
},
other_error => {
panic!("Problem opening the file: {:?}", other_error);
}
},
};
}

In the Err arm, we're calling error.kind() to find out what kind of error this is. This returns a std::io::ErrorKind, which is an enum of all the various reasons an io operation might fail. We match on the kind, and if it is ErrorKind::NotFound we'll try to create the file (which might also fail, so we'll have to deal with that error too!)

If you're a JavaScript programmer who remembers the days before async and await, you might be looking at some of these examples and having flashbacks to the "pyramid of doom" - the name JavaScript programmers use for having many deeply nested callbacks. Don't worry just yet - we're going to explore some ways to make that code a little less verbose in the rest of this chapter.

Alternatives to Using match with Result<T, E>

That last example had a lot of match statements! Fortunately Result has many methods defined on it which can be used to write more concise versions of the code above. Here's a quick example using the unwrap_or_else method:

use std::fs::File;
use std::io::ErrorKind;

fn main() {
let greeting_file = File::open("hello.txt").unwrap_or_else(|error| {
if error.kind() == ErrorKind::NotFound {
File::create("hello.txt").unwrap_or_else(|error| {
panic!("Problem creating the file: {:?}", error);
})
} else {
panic!("Problem opening the file: {:?}", error);
}
});
}

This introduces a new concept called closures which we'll talk more about in chapter 13, but if languages you've used have arrow functions or lambdas, you can probably figure out what's going on in this example. (Hint: the |error| { ... } creates a function that takes an error as a parameter, similar to an (err) => {...} in JavaScript.) If you're having trouble with this example, don't worry - bookmark this and come back to it after you've read chapter 13.

Shortcuts for Panic on Error: unwrap and expect

When we were learning about the Option enum, we learned about the unwrap and expect(message) methods which panic if the Option is None. Result has unwrap and expect methods that work similarly, causing a panic if the Result is an Err variant:

use std::fs::File;

fn main() {
let greeting_file = File::open("hello.txt").unwrap();

let greeting_file2 = File::open("goodbye.txt")
.expect("goodbye.txt should have been installed");
}

expect is generally preferred over unwrap, as it gives more information about what went wrong and gives you a chance to explicitly document the assumptions that were made when you decided to panic here.

Propagating Errors

Often if an error occurs in a function, we don't want to handle the error ourselves but propagate the error to the function's caller. If you're coming to Rust from Go, you've no doubt written if err != nil { return err } many times in your career. In most other languages, when we throw an exception, we're used to it traveling up the stack to the closest catch.

Here's a very verbose example of a function that reads a username from a file. If the file can't be read, we don't want read_username_from_file to panic, but we also don't know how to handle the error here. We want to return the error back to the caller so the caller can decide what to do about the fact that we can't find the username:

use std::fs::File;
use std::io::{self, Read};

// This function is so long! We can make it shorter!
fn read_username_from_file() -> Result<String, io::Error> {
let username_file_result = File::open("hello.txt");

let mut username_file = match username_file_result {
Ok(file) => file,
Err(e) => return Err(e),
};

let mut username = String::new();

match username_file.read_to_string(&mut username) {
Ok(_) => Ok(username),
Err(e) => Err(e),
}
}

One important thing to note here is that read_username_from_file returns a Result<String, io:Error>. We chose io:Error for the E part of Result<T, E>, because this is the error type that both of the functions we call can return. We can use a shortcut called the ? operator to reduce a lot of this boilerplate:

use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
let mut username_file = File::open("hello.txt")?;
let mut username = String::new();
username_file.read_to_string(&mut username)?;
Ok(username)
}

The ? operator can be placed after any Result, and basically is the same as the match expression from the original example. The ? says: "If result is an Ok variant, resolve this expression to the value of the Ok. If result is an Err then return result". This makes errors abort early and return to the caller, very similar to how exceptions work.

tip

Here we're adding a ? to a Result<*, io:Error> and we're returning a Result<*, io:Error> - since the error types are the same, we can use ?, but note that the error types don't have to be the same! The ? operator will pass errors through the from function from the From trait on our return type to convert the error from one error type to another.

For example, if we wanted to defined a custom error type named OurError, we could define impl From<io::Error> for OurError to tell Rust how to convert io:Errors to OurErrors, without needing to add any more code to our example.

See chapter 10 for more information about the From and Into traits.

We can shorten this method further by eliminating some variable names and using chaining:

use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
let mut username = String::new();
File::open("hello.txt")?.read_to_string(&mut username)?;
Ok(username)
}

But of course reading a file to a string is a fairly common operation, and the standard library provides a function to do it, so we can shorten this even further:

use std::fs;
use std::io;

fn read_username_from_file() -> Result<String, io::Error> {
fs::read_to_string("hello.txt")
}

Where The ? Operator Can Be Used

? can be used with both Results and Options. In the Option case it works a little bit like JavaScript's optional chaining operator.

fn last_char_of_first_line(text: &str) -> Option<char> {
text.lines().next()?.chars().last()
}

Here text might be the empty string, or perhaps it's a string like "\nfoo" where the first line has no last char, so this has to return an Option. Note that here too, just like a Result, the ? will turn into a return statement if the Option is an Option::None and return from the whole function. This is quite different from the JavaScript version, where it just ignores the rest of the expression.

Since using the ? operator can turn into a early return statement, the return type of your function must match what ? is going to return. If you use ? on a Result then your function has to return a Result with a compatible error type, and if you use it on an Option your function must return an Option. This, for example, is not going to compile:

use std::fs::File;

fn main() {
// Can't use `?` here!
let greeting_file = File::open("hello.txt")?;
}

This happens because the function signature for main implicitly declares that it returns (), not a Result. Up until now all our main functions have had this signature, but Rust allows us to return a Result from main:

use std::error::Error;
use std::fs::File;

fn main() -> Result<(), Box<dyn Error>> {
let greeting_file = File::open("hello.txt")?;

Ok(())
}

Whoa! What's this Box<dyn Error>? This is a trait object, which we'll discuss in chapter 17. For now, know that this means "any kind of error". When main returns, if it returns an Ok variant, the program will terminate and return 0 exit code to the shell, signalling the program terminated correctly. If it returns an Err variant, the error will be printed to stderr and the program will return a 1 to the shell to signal an error.

tip

The main function can return other types too! It can return anything that implements the std::process::Termination trait.

9.3 - To panic! or Not to panic!

If you're an experienced programmer, your language of choice probably has a way to handle errors and something much like panic. You know this stuff, you can probably safely skip ahead to the next chapter.

If you're still here: A panic halts the entire program, so should be used sparingly. There are some times where it's good to use expect, unwrap, and panic!:

  • In unit tests, where you know there isn't supposed to be an error, and you want to fail the test immediately if there is.
  • When you know something should never happen. It's perfectly OK to write let home: IpAddr = "127.0.0.1".parse().unwrap(); because you know this string will parse to a valid IP address.
  • When you have detected your program is in an invalid state. If you're trying to work out the percentage likelihood that something will happen and you arrive at 200%, then you know there's a bug in your function, and a panic might be reasonable. Even better than panicking when you find you have invalid state, though, is to make it so invalid state cannot be represented in your program. We'll talk a bit about this in chapter 17.
  • If your function has a contract and the values passed in violate that contract, it might make sense to panic. If your function's documentation clearly states that it requires a value between 0 and 1, and someone passes in a 7, then if you believe this clearly represents an error somewhere in the caller's code, a panic is appropriate. If an illegal value could create some kind of security risk or correctness risk, then a panic is definitely warranted. If your function is already returning a Result anyways, it might make sense to just add an error though.
  • If you're writing a book about Rust and you want your examples to be short and concise, and a lot of error handling code would obscure the points you are trying to make, then a panic is fine.

There are some places where it's a terrible idea to panic. When you panic inside a function, callers of that function have no easy way to recover from the panic. These are places where it's better to return an error:

  • When you encounter an error that is unlikely, but could happen in normal operation of the program.
  • When you're authoring a library. Callers to your library might like the opportunity to abort an operation or try again, instead of crashing the entire application.
info

We said that a program can't recover from a panic. This is not strictly true, as there is a catch_unwind function in Rust. But this is not something intended to be used for day-to-day error handling.

Continue to chapter 10.

- + \ No newline at end of file diff --git a/ch10/ch10-01-generic-data-types/index.html b/ch10/ch10-01-generic-data-types/index.html index aecea3f..4c230b1 100644 --- a/ch10/ch10-01-generic-data-types/index.html +++ b/ch10/ch10-01-generic-data-types/index.html @@ -4,13 +4,13 @@ 10.1 - Generic Data Types | The rs Book - +

10.1 - Generic Data Types

In Function Definitions

We can use generics to define a function that can accept different data types. This is similar to generics in TypeScript, Java, and Go, and just like template functions in C++.

Here's an example of a function to find the largest number in a list:

fn largest_i32(list: &[i32]) -> &i32 {
let mut largest = &list[0];

for item in list {
if item > largest {
largest = item;
}
}

largest
}


fn main() {
let number_list = vec![34, 50, 25, 100, 65];

let result = largest_i32(&number_list);
println!("The largest number is {}", result);
}

The problem with this function is that it can only accept a list of i32. If we wanted to write a version of this for char or for u64, the function signature would change, but the code in body would be identical. We can use generics here to write the function to accept any type by changing the function signature to:

// This doesn't QUITE work...
fn largest<T>(list: &[T]) -> &T {

The <T> after the function name tells the compiler this is a generic function, so anywhere inside the function body where there's a T, we'll replace it with some concrete type when the function is actually called. (Or actually, when it's compiled. We'll compile one version of this function for each type it is used with.)

If you actually try to compile the above though, rustc will complain. The problem is that T here could be an i32 or a u64... but it could also be a struct or an enum. Inside the function we do item > largest - how would we decide if one struct was larger than another? We need to restrict what kinds of types can be used in place of T with a trait bound. In this case we only want to allow T to be a type that implements the str::cmp::PartialOrd trait. Types that implement this trait can be compared to each other:

fn largest<T: PartialOrd>(list: &[T]) -> &T {
info

Why a single letter T for the generic type? It doesn't have to be; you can use fn largest<Number>... instead, and it will work. But in almost every language that supports something like generics, the convention is to use a single character.

In Struct Definitions

Generics aren't just for functions, we can also use them in structs. Here we have a Point struct which has an x and a y. Both x and y are type T, so they must both be the same type:

struct Point<T> {
x: T,
y: T,
}

fn main() {
let integer = Point { x: 5, y: 10 };
let unsigned: Point<u32> = Point { x: 9, y: 20 };
let float = Point { x: 1.0, y: 4.0 };

// This won't work, because we're trying to use two different types
let wont_work = Point { x: 5, y: 4.0 };
}

If we want to support mixed types we can, but we'll have to redefine the struct to allow it:

struct MultiPoint<T, U> {
x: T,
y: U,
}

In Method Definitions

If we create a struct with generic properties, it makes sense that we'll have to define methods that are generic too:

pub struct Point<T> {
x: T,
y: T,
}

impl<T> Point<T> {
pub fn x(&self) -> &T {
&self.x
}
}

Note the impl<T> - we need the <T> here to let the compiler know that T is not a concrete type. Why? Because we can also declare methods only on specific concrete versions of a generic struct. This will add a distance_from_origin to Point<f32>, but not to any other Point, such as Point<u32>:

impl Point<f32> {
fn distance_from_origin(&self) -> f32 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}

We can also add generics to a method that are unrelated to the generics from the struct. Here we have a Point with two generic parameters called X1 and Y1, and a generic method with two more X2 and Y2:

struct Point<X1, Y1> {
x: X1,
y: Y1,
}

impl<X1, Y1> Point<X1, Y1> {
// Note that mixup takes `X2` and `Y2` generic types,
// in addition to `X1` and `Y1` from the struct!
fn mixup<X2, Y2>(self, other: Point<X2, Y2>) -> Point<X1, Y2> {
Point {
x: self.x,
y: other.y,
}
}
}

fn main() {
let p1 = Point { x: 5, y: 10.4 };
let p2 = Point { x: "Hello", y: 'c' };

let p3 = p1.mixup(p2);

// Prints "p3.x = 5, p3.y = c".
println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
}

In Enum Definitions

We've already seen a few enums that use generics such as Option<T> and Result<T, E>:

enum Option<T> {
Some(T),
None,
}

enum Result<T, E> {
Ok(T),
Err(E),
}

Performance of Code Using Generics

Above we said that a you can use generics to define a function that can accept different data types, but it's perhaps more accurate to say that you can use them to easily create a whole bunch of functions, one for each data type. Much like C++ template functions, Rust generics are implemented using monomorphization, which is a fancy way of saying it generates a copy of your function for each generic type it was used with at compile time.

In other words, if we go back to the fn largest<T>(list: &[T]) -> &T we started this section with, if you were to call:

    let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&number_list);

let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest(&char_list);

then internally Rust would actually compile two different functions, a largest<i32> and a largest<char>. This means generic have no runtime performance impact (but they do make your executable slightly larger).

Continue to 10.02 - Traits.

- + \ No newline at end of file diff --git a/ch10/ch10-02-traits/index.html b/ch10/ch10-02-traits/index.html index fe255e9..02fae67 100644 --- a/ch10/ch10-02-traits/index.html +++ b/ch10/ch10-02-traits/index.html @@ -4,13 +4,13 @@ 10.2 - Traits: Defining Shared Behavior | The rs Book - +
-

10.2 - Traits: Defining Shared Behavior

A trait in Rust is very similar to what most other languages call an interface. A trait defines some set of behavior, and every struct that implements the trait needs to implement that behavior.

Defining a Trait

Let's suppose we have two types, Tweet and NewsArticle. We might want to be able to get a summary of tweet, and we might want to be able to get a summary of a news article, so it would make sense for both of these to implement a summarize() function. We can define a trait called Summary that defines the method signature that these types will need to implement:

pub trait Summary {
fn summarize(&self) -> String;
}

Note that the trait only defines the method signatures - the contract, if you will - that the types need to implement. Each type is free to implement this function differently.

Implementing a Trait on a Type

In languages like TypeScript and Go, if we have an interface, and we have a type that defines all the same methods that the interface declares, then the type implements that interface. There's no need to explicitly mark that the type implements the interface. This is called "duck typing", because, as the saying goes, "if it walks like a duck, and it quacks like a duck, then it must be a duck."

Not so in Rust. Here we must explicitly declare that a type implements a trait. The syntax is impl [TRAIT] for [STRUCT] {}, and inside the curly braces we place all the methods we need to implement:

pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}

impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}

pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}

impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}

This is very similar to defining a method on the struct directly, but the method is actually defined on the trait. If we want to call the summarize function, we need to make sure the trait is in scope. In this example we have to use both Summary and Tweet, even though Summary never appears in the code:

use aggregator::{Summary, Tweet};

fn main() {
let tweet = Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
};

println!("1 new tweet: {}", tweet.summarize());
}

Other crates can use the Summary trait and implement it on their own types, just like you can implement traits from the standard library on your own types. One thing to note is that if you want to implement a trait on a type, then either the trait or the type (or both) must be local to your crate. You can't use a trait from one external crate, a type from another, and then implement the external trait on the external type in your crate.

This restriction is in place because of something called the orphan rule. Let's suppose there's a color crate out there. You implement a library crate that uses color, but you notice one of the types in color doesn't implement the Display trait and you want to println! a color, so you implement the Display trait on that type. Now suppose I'm writing a separate library crate, and I do the same thing. Now suppose someone adds your crate and my crate to their application. At this point, the Rust compiler has two competing implementations for Display on this type, so which one does it use? Since Rust has no way to know which is the "correct" one, Rust just stops this from ever happening by forcing the crate to own at least one of the type or trait.

note

The orphan rule is actually slightly more complicated than mentioned above. Once generics start getting involved, it's possible to use a foreign trait and foreign type, given that one of the generic types is local. See the above link for full details.

Default Implementations

Remember how we said a trait just had signatures and no implementations? Well, we lied a little. Sometimes it's handy to be able to define default behavior for a method:

pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}

// We can implement this trait with an empty
// impl block, taking the default function
// definitions.
impl Summary for NewsArticle {}

Default implementations are allowed to call other methods on the same trait. This allows a trait to provide a lot of functionality while only requiring implementers to implement part of the trait:

pub trait Summary {
fn summarize_author(&self) -> String;

fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}

Some implementations might only implement summarize_author(), while some might implement both methods.

Traits as Parameters

When we define a generic function, we can limit what kinds of concrete types are allowed to be used in place of the generic type using a trait bound:

pub fn notify<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}

Here we're declaring a generic function, but we're setting bounds on the type of T. Whatever you pass in for T has to satisfy the Summary trait. This is a common thing to do, so there's a shortcut to specify this using the impl keyword:

// This is syntactic sugar for the example above.
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}

We can specify more than one trait bound:

// Using a trait bound:
pub fn notify<T: Summary + Display>(item: &T) {...}

// Using the `impl` syntax:
pub fn notify(item: &(impl Summary + Display)) {...}

Here whatever we pass in for T must satisfy both our own Summary trait and the Display trait from the standard library (so we can use {} to display the item with println! or format!).

This can get a bit hard to read if you have a lot of traits bounds. There ends up being a lot of clutter between the name of the function and the parameters. Borrowing a page from SQL, we can also write trait bounds using a where clause. These two examples are equivalent:

fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {...}

fn some_function<T, U>(t: &T, u: &U) -> i32
where
T: Display + Clone,
U: Clone + Debug,
{...}

Returning Types that Implement Traits

We can hide the concrete type returned by a function using an opaque type. This lets us hide the concrete type from the caller (and allows you to change the concrete type later without affecting callers):

fn returns_summarizable() -> impl Summary {
Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
}
}

Note that even though this impl syntax looks similar to the shortcut we used to specify a trait bound above, this is not at all the same. This function is not generic. There is still a single concrete type being returned by this function (in this case Tweet), but callers are limited to only using the interface provided by the trait (in this case Summary).

The concrete type here is inferred by the compiler, but it's important to realize there is still one. If you were to add an if statement to this function, you would not be able to return a Tweet in one branch and a NewsArticle in the other. (We'll see how to overcome this with trait objects and dynamic dispatch in chapter 17.)

This syntax is useful if we want to return something that has a concrete type that can't be written down, like a closure:

fn thing_returning_closure() -> impl Fn(i32) -> bool {
println!("here's a closure for you!");
|x: i32| x % 3 == 0
}

We haven't talked about iterators yet, but sometimes when using an iterator, the type inferred by the compiler can be quite long, and writing the full type out by hand would be a lot of work without much benefit. Being able to supply an opaque type here is much more concise.

Using Trait Bounds to Conditionally Implement Methods

As we saw earlier, we can specify an implementation for a method on specific types of a generic type. We can similarly implement a method on specific trait bounds:

use std::fmt::Display;

struct Pair<T> {
x: T,
y: T,
}

impl<T> Pair<T> {
fn new(x: T, y: T) -> Self {
Self { x, y }
}
}

impl<T: Display + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.x >= self.y {
println!("The largest member is x = {}", self.x);
} else {
println!("The largest member is y = {}", self.y);
}
}
}

Here the new() associated function is implemented on all generic types, but cmp_display() is only defined on a Pair<T> if the inner type used for T implements both the Display and the PartialOrd traits.

We can also conditionally implement a trait for any type that implements some other trait! These are called blanket implementations. This example comes from the standard library:

impl<T: Display> ToString for T {
// --snip--
}

The implements the ToString trait on any type that implements the Display trait. Because of this, we can call to_string() on any type that implements Display.

From and Into

From and Into are two related traits in rust. These are used to convert a type from one type to another. If you implement From, you get Into for free. We already mentioned using the From trait to convert Errors from one type to another. Let's see another example:

struct Millimeters(u32);
struct Meters(u32);

impl From<Meters> for Millimeters {
fn from(value: Meters) -> Self {
return Millimeters(value.0 * 1000);
}
}

impl From<Millimeters> for Meters {
fn from(value: Millimeters) -> Self {
return Meters(value.0 / 1000);
}
}

fn main() {
let one_meter = Meters(1);
let millis = Millimeters::from(one_meter);
println!("1 meter is {} millimeters", millis.0);

let one_meter = Meters(1);
let into_millis: Millimeters = one_meter.into();
println!("1 meter is {} millimeters", into_millis.0);
}

Here Millimeters::from(one_meter) and one_meter.into() both convert meters into millimeters. When we call into the type the meter is converted to is inferred from the annotation on into_millis.

Continue to 10.3 - Lifetimes.

- +

10.2 - Traits: Defining Shared Behavior

A trait in Rust is very similar to what most other languages call an interface. A trait defines some set of behavior, and every struct that implements the trait needs to implement that behavior.

Defining a Trait

Let's suppose we have two types, Tweet and NewsArticle. We might want to be able to get a summary of tweet, and we might want to be able to get a summary of a news article, so it would make sense for both of these to implement a summarize() function. We can define a trait called Summary that defines the method signature that these types will need to implement:

pub trait Summary {
fn summarize(&self) -> String;
}

Note that the trait only defines the method signatures - the contract, if you will - that the types need to implement. Each type is free to implement this function differently.

info

When we name structs, we typically use a noun to name a struct. Functions are typically verbs. Traits in Rust are less consistently named.

You might think from the name "trait" that these should be named after adjectives. Or perhaps since traits fill the same role as interfaces in other languages, a noun would be appropriate. But the trait for a type that implements the read method is neither "Readable" nor "Reader", but Read. A type that can be copied has the Copy marker trait, not the "Copyable" trait.

From these examples - Read and Copy - clearly traits in Rust should be named after verbs! But there are plenty of examples in the standard library that seem to defy this such as Iterator, Hasher, or Sized, and the example in Rust by Example is Animal.

This ambiguous naming comes at least in part from the fact that traits are inspired in part by Haskell's typeclasses, which have similar naming weirdness. The best rule of thumb I've seen is that if a trait has a single well defined method (such as write or read) the the trait should be named after the method. Otherwise, the trait name should be a noun.

Implementing a Trait on a Type

In languages like TypeScript and Go, if we have an interface, and we have a type that defines all the same methods that the interface declares, then the type implements that interface. There's no need to explicitly mark that the type implements the interface. This is called "duck typing", because, as the saying goes, "if it walks like a duck, and it quacks like a duck, then it must be a duck."

Not so in Rust. Here we must explicitly declare that a type implements a trait. The syntax is impl [TRAIT] for [STRUCT] {}, and inside the curly braces we place all the methods we need to implement:

pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}

impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}

pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}

impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}

This is very similar to defining a method on the struct directly, but the method is actually defined on the trait. If we want to call the summarize function, we need to make sure the trait is in scope. In this example we have to use both Summary and Tweet, even though Summary never appears in the code:

use aggregator::{Summary, Tweet};

fn main() {
let tweet = Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
};

println!("1 new tweet: {}", tweet.summarize());
}

Other crates can use the Summary trait and implement it on their own types, just like you can implement traits from the standard library on your own types. One thing to note is that if you want to implement a trait on a type, then either the trait or the type (or both) must be local to your crate. You can't use a trait from one external crate, a type from another, and then implement the external trait on the external type in your crate.

This restriction is in place because of something called the orphan rule. Let's suppose there's a color crate out there. You implement a library crate that uses color, but you notice one of the types in color doesn't implement the Display trait and you want to println! a color, so you implement the Display trait on that type. Now suppose I'm writing a separate library crate, and I do the same thing. Now suppose someone adds your crate and my crate to their application. At this point, the Rust compiler has two competing implementations for Display on this type, so which one does it use? Since Rust has no way to know which is the "correct" one, Rust just stops this from ever happening by forcing the crate to own at least one of the type or trait.

note

The orphan rule is actually slightly more complicated than mentioned above. Once generics start getting involved, it's possible to use a foreign trait and foreign type, given that one of the generic types is local. See the above link for full details.

Default Implementations

Remember how we said a trait just had signatures and no implementations? Well, we lied a little. Sometimes it's handy to be able to define default behavior for a method:

pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}

// We can implement this trait with an empty
// impl block, taking the default function
// definitions.
impl Summary for NewsArticle {}

Default implementations are allowed to call other methods on the same trait. This allows a trait to provide a lot of functionality while only requiring implementers to implement part of the trait:

pub trait Summary {
fn summarize_author(&self) -> String;

fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}

Some implementations might only implement summarize_author(), while some might implement both methods.

Traits as Parameters

When we define a generic function, we can limit what kinds of concrete types are allowed to be used in place of the generic type using a trait bound:

pub fn notify<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}

Here we're declaring a generic function, but we're setting bounds on the type of T. Whatever you pass in for T has to satisfy the Summary trait. This is a common thing to do, so there's a shortcut to specify this using the impl keyword:

// This is syntactic sugar for the example above.
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}

We can specify more than one trait bound:

// Using a trait bound:
pub fn notify<T: Summary + Display>(item: &T) {...}

// Using the `impl` syntax:
pub fn notify(item: &(impl Summary + Display)) {...}

Here whatever we pass in for T must satisfy both our own Summary trait and the Display trait from the standard library (so we can use {} to display the item with println! or format!).

This can get a bit hard to read if you have a lot of traits bounds. There ends up being a lot of clutter between the name of the function and the parameters. Borrowing a page from SQL, we can also write trait bounds using a where clause. These two examples are equivalent:

fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {...}

fn some_function<T, U>(t: &T, u: &U) -> i32
where
T: Display + Clone,
U: Clone + Debug,
{...}

Returning Types that Implement Traits

We can hide the concrete type returned by a function using an opaque type. This lets us hide the concrete type from the caller (and allows you to change the concrete type later without affecting callers):

fn returns_summarizable() -> impl Summary {
Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
}
}

Note that even though this impl syntax looks similar to the shortcut we used to specify a trait bound above, this is not at all the same. This function is not generic. There is still a single concrete type being returned by this function (in this case Tweet), but callers are limited to only using the interface provided by the trait (in this case Summary).

The concrete type here is inferred by the compiler, but it's important to realize there is still one. If you were to add an if statement to this function, you would not be able to return a Tweet in one branch and a NewsArticle in the other. (We'll see how to overcome this with trait objects and dynamic dispatch in chapter 17.)

This syntax is useful if we want to return something that has a concrete type that can't be written down, like a closure:

fn thing_returning_closure() -> impl Fn(i32) -> bool {
println!("here's a closure for you!");
|x: i32| x % 3 == 0
}

We haven't talked about iterators yet, but sometimes when using an iterator, the type inferred by the compiler can be quite long, and writing the full type out by hand would be a lot of work without much benefit. Being able to supply an opaque type here is much more concise.

Using Trait Bounds to Conditionally Implement Methods

As we saw earlier, we can specify an implementation for a method on specific types of a generic type. We can similarly implement a method on specific trait bounds:

use std::fmt::Display;

struct Pair<T> {
x: T,
y: T,
}

impl<T> Pair<T> {
fn new(x: T, y: T) -> Self {
Self { x, y }
}
}

impl<T: Display + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.x >= self.y {
println!("The largest member is x = {}", self.x);
} else {
println!("The largest member is y = {}", self.y);
}
}
}

Here the new() associated function is implemented on all generic types, but cmp_display() is only defined on a Pair<T> if the inner type used for T implements both the Display and the PartialOrd traits.

We can also conditionally implement a trait for any type that implements some other trait! These are called blanket implementations. This example comes from the standard library:

impl<T: Display> ToString for T {
// --snip--
}

The implements the ToString trait on any type that implements the Display trait. Because of this, we can call to_string() on any type that implements Display.

From and Into

From and Into are two related traits in rust. These are used to convert a type from one type to another. If you implement From, you get Into for free. We already mentioned using the From trait to convert Errors from one type to another. Let's see another example:

struct Millimeters(u32);
struct Meters(u32);

impl From<Meters> for Millimeters {
fn from(value: Meters) -> Self {
return Millimeters(value.0 * 1000);
}
}

impl From<Millimeters> for Meters {
fn from(value: Millimeters) -> Self {
return Meters(value.0 / 1000);
}
}

fn main() {
let one_meter = Meters(1);
let millis = Millimeters::from(one_meter);
println!("1 meter is {} millimeters", millis.0);

let one_meter = Meters(1);
let into_millis: Millimeters = one_meter.into();
println!("1 meter is {} millimeters", into_millis.0);
}

Here Millimeters::from(one_meter) and one_meter.into() both convert meters into millimeters. When we call into the type the meter is converted to is inferred from the annotation on into_millis.

Continue to 10.3 - Lifetimes.

+ \ No newline at end of file diff --git a/ch10/ch10-03-lifetimes/index.html b/ch10/ch10-03-lifetimes/index.html index f305908..96a5e02 100644 --- a/ch10/ch10-03-lifetimes/index.html +++ b/ch10/ch10-03-lifetimes/index.html @@ -3,14 +3,14 @@ -10.3 - Validating References with Lifetimes | The rs Book - +10.3 - Validating References with Lifetimes | The rs Book +
-

10.3 - Validating References with Lifetimes

info

TODO: This section needs some rework. If you want to get deep into how lifetimes work from the compiler's perspective, this is a good read.

Every value in Rust has a lifetime - a point in the code where the value is created, and a point in the code where the value is destroyed. Every reference in Rust has two lifetimes - the lifetime of the reference itself (from where it's created until where it is last used) and a lifetime of the value it points to. The lifetime of the reference obviously needs to be shorter than the lifetime of the value, otherwise the reference will point to freed memory.

Just as rustc infers the type of many of our parameters, in most cases Rust can infer the lifetime of a reference (usually from when it is created until it's last use in a function). Just as we can explicitly annotate a variable's type, we can also explicitly annotate the lifetime of a reference in cases where the compiler can't infer what we want.

Preventing Dangling References with Lifetimes

The main point of ownership is to prevent dangling references - to prevent us from accessing memory after it has been freed. Here's an example:

fn main() {
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("r: {}", r); // |
} // ---------+

This won't compile. The variable r is in scope for the entire main() function, but it's a reference to x which will be dropped when we reach the end of the inner scope. After we reach the end of that inner scope, r is now a reference to freed memory, so Rust's borrow checker won't let us use it.

More formally, we can say that r and x have different lifetimes, which we've marked in the comments of this example, using the labels 'a and 'b (strange names, but this is actually a bit of foreshadowing). The borrow checker sees that r has a lifetime of 'a, but references memory that has the lifetime 'b, and since 'b is shorter than 'a, the borrow checker won't allow this.

This version fixes the bug:

fn main() {
let x = 5; // ----------+-- 'b
// |
let r = &x; // --+-- 'a |
// | |
println!("r: {}", r); // | |
// --+ |
} // ----------+

Here x has a larger lifetime than r, so r can be a valid reference to x.

Generic Lifetimes in Functions

Now for an example that doesn't compile, for what might not at first be obvious reasons. We're going to pass two string slices to a longest() function, and it will return back whichever is longer:

fn main() {
let string1 = String::from("abcd");
let string2 = "xyz";

let result = longest(string1.as_str(), string2);
println!("The longest string is {}", result);
}

// This doesn't work!
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}

If we try to compile this, we get an error from the borrow checker. When the Rust compiler checks a call to a function, it doesn't look at the contents of the function, only at the signature. The root of the problem here is that in this function, the rust compiler doesn't know ahead of time whether we're going to return x or y, and since these may have different lifetimes, the compiler can't know how long the returned reference will be valid for. Consider this example of calling this function:

fn main() {
let string1 = String::from("abcd");
let result;

{
let string2 = String::from("xyz");
result = longest(&string1, &string2);
}

// This shouldn't compile! But how does rust know that?
println!("The longest string is {}", result);
}

Here if longest() returned the reference to string1, it would still be valid by the time we get to the println!, but if it returned the reference to string2 it would not. How is the borrow checker supposed to decide if the call to longest() is valid? The answer is that it can't. At least, not without a little help from us.

Lifetime Annotation Syntax

We fix this problem by telling the compiler about the relationship between these references. We do this with lifetime annotations. Lifetime references are of the form 'a:

&i32        // a reference
&'a i32 // a reference with an explicit lifetime
&'a mut i32 // a mutable reference with an explicit lifetime

A lifetime annotation on a single variable isn't very meaningful. Lifetime annotations really describe a constraint on the relationship of references between multiple variables.

Lifetime Annotations in Function Signatures

We can declare a lifetime annotation for a function in much the same way we add generic types. The lifetime annotation must start with a '. Typically they are single characters, much like generic types. And just like generic types, these will be filled in with a real lifetime for each call to the function.

We can fix the longest() function in our previous example with:

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}

Think about this a bit like a generic function (the syntax is similar for a good reason). We're saying here there exists some lifetime which we're going to call 'a, and the variables x and y both life at least as long as this hypothetical 'a. They don't have to both be the same lifetime, they just both have to be valid at the start and end of 'a. Then in the case of this function we're making the claim that the value we return is going to be valid for this same lifetime. At compile time, the compiler will see how long the passed in x lives, how long the passed in y lives, and then it will verify that the result of this function isn't used anywhere outside of that lifetime.

Putting this a bit more succinctly, we're telling the compiler that the return value of longest() will live at least as long as the shorter lifetime of x and y. When the rust compiler analyzes a call to longest() it can now mark it as an error if the two parameters passed in don't adhere to this constraint.

info

Lifetime annotations don't actually change the lifetime of the references passed in, it only gives the borrow checker enough information to work out whether a call is valid.

Returning to this example:

fn main() {
let string1 = String::from("abcd");
let result;

{
let string2 = String::from("xyz");
result = longest(&string1, &string2);
}

// This doesn't compile!
println!("The longest string is {}", result);
}

Here the compiler now knows that the return value of longest() is only as long as the shorter of &string1 and &string2, so it knows that the use of result in the println! macro is invalid.

Thinking in Terms of Lifetimes

The way we annotate lifetimes depends on what the function is doing. If we changed longest() to only ever return the first parameter, we could annotate the lifetimes as:

fn longest<'a>(x: &'a str, y: &str) -> &'a str {
x
}

This tells rustc that the lifetime of the return value is the same as the lifetime of the first parameter.

The lifetime of the return value must have the same annotation as at least one of the parameters (or be 'static, which we'll discuss in a moment). If you created a reference to something you create inside the function and return it, whatever you created will be dropped at the end of the function, so the reference will be invalid.

Lifetime Annotations in Struct Definitions

So far all the structs we've created in this book have owned all their types. If we want to store a reference in a struct, we can, but we need to explicitly annotate it's lifetime. Just like a function, we do this with the generic syntax:

struct ImportantExcerpt<'a> {
part: &'a str,
}

fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().expect("Could not find a '.'");

let i = ImportantExcerpt {
part: first_sentence,
};
}

Again, it's helpful to think about this like we would any other generic declaration. When we write ImportantExcerpt<'a> we are saying "there exists some lifetime which we'll call 'a" - we don't know what that lifetime is yet, and we won't know until someone creates an actual instance of this struct. When we write part: &'a str, we are saying "when someone reads this ref, it has the lifetime 'a" (and if someone later writes a new value to this ref, it must have a lifetime of at least 'a). At compile time, the compiler will fill in the generic lifetimes with real lifetimes from your program, and then verify that the constraints hold.

Here this struct has only a single reference, and so it might seem odd that we have to give an explicit lifetime for it. You might think the compiler could automatically figure out the lifetime here (and perhaps one day in this trivial example it will - Rust is evolving pretty rapidly).

info

The original "The Rust Programming Language" here said that "this annotation means an instance of ImportantExcerpt can't outlive the reference it holds in its part field," but I found that not a helpful way to think about this - of course a struct can't outlive any references stored inside it. I found this answer on Stack Overflow to be a lot more illuminating.

Here's an example where a struct requires two different lifetime annotations (borrowed from this Stack Overflow discussion which has some other good examples too):

struct Point<'a, 'b> {
x: &'a i32,
y: &'b i32,
}

fn main() {
let x = 1;
let v;
{
let y = 2;
let f = Point { x: &x, y: &y };
v = f.x;
}
println!("{}", *v);
}

The interesting thing here is that we're copying a reference out of a struct and then using it after the struct has been dropped. This is okay because in this case the lifetime of the reference is longer than that of the struct. There's no way the compiler could know this without lifetime annotations. We we create the Point<'a, 'b> here, the compiler fills in 'a with the lifetime of x = 1, so when we do v = f.x the compiler knows v also has that same lifetime. Also in this example, if you tried to give both x and y the same lifetime annotation, this would fail to compile.

tip

Similar to trait bounds, we can add a lifetime bound to a lifetime annotation in a function or a struct.

struct Point<'a, 'b: 'a> {
x: &'a f32,
y: &'b f32,
}

You can read 'b: 'a as "'b outlives 'a", and this implies that 'b must be at least as long as 'a. There are very few cases where you would need to do such a thing, though.

Lifetime Elision

Way back in chapter 4, we wrote this function:

fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();

for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}

&s[..]
}

How come this compiles without lifetime annotations? Why don't we have to tell the compiler that the return value has the same lifetime as s? Actually, in the pre-1.0 days of Rust, lifetime annotations would have been mandatory here. But there are certain cases where Rust can now work out the lifetime on it's own. We call this lifetime elision, and say that the compiler elides these lifetime annotations for us.

What the compiler does is to assign a different lifetime to every reference in the parameter list ('a for the first one, 'b for the second, and so on...). If there is exactly one input lifetime parameter, that lifetime is automatically assigned to all output parameters. If there is more than one input lifetime parameter but one of them is for &self, then the lifetime of self is assigned to all output parameters. Otherwise, the compiler will error.

In the case above, there's only one lifetime that first_word could really be returning; if first_word created a new String and tried to return a reference to it, the new String would be dropped when we leave the function and the reference would be invalid. The only sensible reference for it to return comes from s, so Rust infers this for us. (It could be a static lifetime, but if it were we'd have to explicitly annotate it as such.)

Lifetime Annotations in Method Definitions

We can add lifetime annotations to methods using the exact same generic syntax we use for generic structs:

impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}

fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("Attention please: {}", announcement);
self.part
}
}

Here 'a refers to the lifetime of the struct itself, but thanks to lifetime elision, in announce_and_return_part(), the return value is automatically given the same lifetime as self, so we don't actually have to use it.

The Static Lifetime

There's a special lifetime called the 'static lifetime:

let s: &'static str = "I have a static lifetime.";

This is a slice of a string that's part of the program's binary, so it will always be available. you may see the 'static lifetime mentioned in error messages when Rust suggests a fix, but unless you actually want a reference that lasts the life of your program, likely the real problem is that you're trying to create a dangling reference or there's lifetime mismatch.

Generic Type Parameters, Trait Bounds, and Lifetimes Together

use std::fmt::Display;

fn longest_with_an_announcement<'a, T>(
x: &'a str,
y: &'a str,
announcement: T,
) -> &'a str
where
T: Display,
{
println!("Announcement! {}", announcement);
if x.len() > y.len() {
x
} else {
y
}
}

This takes two string slices and returns whichever is longer. It also prints an announcement, which is passed in as a parameter and can be any type that implements the Display trait. (If someone showed you this code before you started reading this book, I wonder what would you have thought it meant?)

Further Reading

There are some advanced cases where lifetime annotations are required that we haven't discussed here (for example trait bounds sometimes require lifetime annotations, but they are usually inferred). The Rust Reference is a good place to read about this sort of thing when you're a little more comfortable with the language.

Lifetimes and ownership are such a central and important part of Rust that I'll also direct you to this excellent two part blog post on the subject.

Continue to chapter 11.

- +

10.3 - Validating References with Lifetimes

info

This chapter explains lifetimes in a somewhat different, and slightly more technical way than the original "The Rust Programming Language" did. If you find the explanation here confusing, you might try reading the original, or check the end of this section for some additional reading. Lifetimes can be one of the more confusing parts of Rust if you're a newcomer. They're a result of Rust's unique ownership system, so there aren't any direct analogs in other languages. As a result, many attempts have been made to explain them, so if you have a hard time don't give up! Somewhere out there, someone will explain this in a way that clicks for you.

Let's Pretend We're Compilers

Let's pretend we're compilers (beep boop!), and we want to do some type checking. Here's some code that we want to check:

fn biggest(x: &i32, y: &i32) -> &i32 {
if *x > *y {
x
} else {
y
}
}

fn main() {
let i1 = 7;
let result;

{
let i2 = 8;
result = biggest(&i1, &i2);
}

// This shouldn't compile! But how does rust know that?
println!("The bigger value is {}", result);
}

If we're going to type check the call to biggest in main, we're going to go look at the signature for the biggest function, and compare it to how it is being used. We're only going to look at the signature, because it's the contract for this function and it should contain everything we need to know (and we're busy compilers and we can't possibly be expected to delve into the actual implementation of biggest). We're going to see that biggest takes two &i32, and returns an &i32. We're passing in two references to i32s, and we're assigning the result to result which we can infer to be an &i32! So this is fine! Job done, on to the next function.

Well... except it isn't. If you follow through the execution of this code, we're going to end up assigning result = &i2, and then we'll try dereference result after i2 has been dropped, creating a use-after-freed error. How are we supposed to know this doesn't compile? We, fellow compilers, are missing some critical information here.

Up until know we've been leaving something out when we talk about references. We've written references as &i32, but that is actually not the entire type of a reference. From a type safety perspective, a reference contains three things; the type of the target (here i32), whether or not this is a mutable exclusive reference (& vs &mut), and finally some information about how long that underlying value it is referring to lives. That last value is called the reference's lifetime. All three of these things are part of the reference's type, and only when all three are taken into consideration can we make a decision about whether the correct arguments are being passed into or out of a function.

Some Terminology

Before we go any further, let's define some terminology. We already know the scope (or lexical scope) of a variable is the {} around where the variable is declared; it's the part of the code where that variable binding is valid. The actual value that a variable is bound to also has a scope (or liveness scope) of a slightly different nature - it's the portion of the code where that value is valid. The scope of a value starts where the value is created and ends where the value is dropped.

A reference in Rust has a lifetime, which is part of the reference's type. You can naively think of the lifetime as being as long as the liveness scope of the value the reference points to, and that will get you pretty far, but it would be better to think of the lifetime as being at most as long as the scope of the value.

info

Some other Rust documentation and articles calls the liveness scope the "lifetime of the value". Why do we use the term "scope" instead? A reference has a lifetime (shorter than or equal to the liveness scope of the value it refers to), but a reference is itself a kind of value with it's own liveness scope, and a reference is bound to a variable with a lexical scope (the liveness scope and lexical scope of a reference are the same, since references are Copy). If we used lifetime to talk about liveness scopes, then references would have two different lifetimes, and this chapter would get confusing very quickly.

Preventing Dangling References with Lifetimes

Let's take a step back and look at a simplified example. Here's some rust code that doesn't compile:

fn main() {
let r;

{
let x = 5; // -+-- 'b
r = &x; // |
} // -+ 💥 `x` is freed

println!("r: {}", r);
}

We're trying to create a reference r and then use that reference after the value it points to has been dropped, so this shouldn't compile. But again - how does the compiler know this shouldn't compile? The reason is because x has a liveness scope that corresponds to the 'b we've marked in the comments. That means r here is not just an &i32, r has a lifetime which is equal to the 'b we've annotated above. We can think about r as being of the type &'b i32.

Since we're trying to access this r outside of the 'b (which is part of r's type), this is going to fail to compile.

Lifetime Inheritance

We're also going to introduce a concept here called lifetime inheritance. Lifetimes are a lot like objects in OO languages, in that one lifetime inherits from another. A larger lifetime inherits from a smaller lifetime.

No, we didn't get that backwards, although it is slightly unintuitive. In an Object Oriented world, we might say that a Car inherits from Vehicle, which means that anywhere you want a vehicle, you're allowed to use a car, but if you need a car, not just any vehicle will do. Similarly in Rust, the longer lifetime inherits from the shorter one; if you need a shorter lifetime, you can always convert a longer lifetime into a shorter one, but you can't safely convert a shorter one into a longer one. From a type-safety perspective, we would say if you have a function that takes a reference with a shorter lifetime, you can always type-coerce a longer one into a shorter one, but the reverse is not true.

Lifetime Annotation Syntax

Since the lifetime is part of the type of a reference, there's a way to name that lifetime in the code called a lifetime annotation. Lifetime annotations are always written as part of a generic function or a generic struct, and much like generic types, lifetime annotations usually consist of a single letter:

&i32        // a reference
&'a i32 // a reference with an explicit lifetime
&'a mut i32 // a mutable reference with an explicit lifetime

Generic Lifetimes in Functions

Let's go back to our biggest example from before, and fix it:

fn biggest<'a>(x: &'a i32, y: &'a i32) -> &'a i32 {
if *x > *y {
x
} else {
y
}
}

fn main() {
let i1 = 7;
let result;

{
let i2 = 8;
result = biggest(&i1, &i2);
}

// This shouldn't compile! But how does rust know that?
println!("The bigger value is {}", result);
}

We've written a generic function here, that's generic over some lifetime 'a. When the compiler tries to call biggest, it needs two references with that lifetime, and it's going to return a reference with that same lifetime.

When the compiler looks at our call to biggest, it treats this just like it would any other generic function, and tries to infer a value for 'a. The compiler sees we are assigning the returned &'a i32 to result, so the lifetime 'a must be at least as long as the liveness scope of result. The reference to i1 meets this criteria, but the reference to i2 doesn't - we can't type-coerce a shorter reference into a longer one - so the compiler gives us the error `i2` does not live long enough. If you want to think about this from a more informal standpoint, what we're doing here is telling the compiler "there exists some lifetime 'a which we're going to return, and the lifetime scopes of the two values referred to in the arguments needs to be at least that long."

It's important to note here that the liveness scopes of the underlying values that get passed in to the function in the caller don't have to be exactly the same. As we mentioned above, a reference is in itself a value. When you pass a reference into a function, what's actually happening down in the compiler is that we're passing a copy of the reference itself. When you create a new reference, you can think of it as having a lifetime equal to the liveness scope of whatever it points to. When you pass a reference, you can think of the copied reference as being converted into a new type which possibly has a shorter lifetime. When you call biggest above, the two references you pass in might have different lifetimes, but inside biggest we'll have two copied references which both have the same lifetime, and the return value will have the same lifetime too. This may seem like kind of an overly pedantic way to think about this, but if you have a structure with multiple references in it, thinking about it this way can really help you work through some convoluted error messages from the borrow checker.

info

One important thing to note here is that lifetime annotations don't actually change the lifetime of the references passed in, they only gives the borrow checker enough information to work out whether a call is valid.

The way we annotate lifetimes depends on what the function is doing. If we changed biggest() to only ever return the first parameter, we would annotate the lifetimes as:

fn biggest<'a>(x: &'a i32, y: &'b i32) -> &'a i32 {
x
}

This tells the compiler that the lifetime of the return value is the same as the lifetime of the first parameter, and the second parameter doesn't relate to the return value at all. If you walk through our example above with this new definition of biggest, you'll see that this would compile - the lifetimes of the returned reference and the reference to i1 are the same, and the liveness scope of i2 doesn't matter.

In most cases you will want the annotation in the return value to match the annotation of at least one parameter. See the 'static lifetime below!

Lifetime Annotations in Struct Definitions

So far all the structs we've created in this book have owned all their types. If we want to store a reference in a struct, we can, but we need to explicitly annotate it's lifetime. Just like a function, we do this with the generic syntax:

struct ImportantExcerpt<'a> {
part: &'a str,
}

fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().expect("Could not find a '.'");

let i = ImportantExcerpt {
part: first_sentence,
};
}

Again, it's helpful to think about this like we would any other generic declaration. When we write ImportantExcerpt<'a> we are saying "there exists some lifetime which we'll call 'a" - we don't know what that lifetime is yet, and we won't know until someone creates an actual instance of this struct. When we write part: &'a str, we are saying "this ref has that same lifetime 'a" (and, since you can update the contents of a struct, if someone later writes a new value to this ref, it must have a lifetime of at least 'a). At compile time, the compiler will fill in the generic lifetimes with real lifetimes from your program, and then verify that the constraints hold.

Here's an example where a struct requires two different lifetime annotations (borrowed from this Stack Overflow discussion which has some other good examples too):

struct Point<'a, 'b> {
x: &'a i32,
y: &'b i32,
}

fn main() {
let x1 = 1;
let v;
{
let y1 = 2;
let f = Point { x: &x1, y: &y1 };
v = f.x;
}
println!("{}", *v);
}

One interesting thing here is that we're copying a reference out of a struct and then using it after the struct has been dropped. If we look at this through the lens of type safety, when we create a Point, the lifetime of f.x which we're calling 'a is going to be the same as the liveness scope of x1. When we assign v = f.x, the compiler will infer the type of v as &'a i32. When we dereference v, we're still inside 'a (since this is the liveness scope of x), so this is an acceptable borrow.

If we rewrote the above struct as:

struct Point<'a> {
x: &'a i32,
y: &'a i32,
}

then this example would fail to compile. At compile time, the compiler infers the type of v to be a &'a i32 where 'a's lifetime as long as the main function, and then tries to type-coerce the reference to x1 and y1 to be the same. Since the liveness scope of y1 is shorter than the lifetime that v requires, it can't be coerced, so again we get the compiler error `y1` does not live long enough.

tip

Similar to trait bounds, we can add a lifetime bound to a lifetime annotation in a function or a struct.

struct Point<'a, 'b: 'a> {
x: &'a f32,
y: &'b f32,
}

You can read 'b: 'a as "'b outlives 'a", and this implies that 'b must be at least as long as 'a. There are very few cases where you would need to do such a thing, though.

Lifetime Elision

Way back in chapter 4, we wrote this function:

fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();

for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}

&s[..]
}

How come this compiles without lifetime annotations? Why don't we have to tell the compiler that the return value has the same lifetime as s? Actually, in the pre-1.0 days of Rust, lifetime annotations would have been mandatory here. But there are certain cases where Rust can work out the lifetime on it's own. We call this lifetime elision, because we are allowed to elide the annotations here. (Elide is a fancy programmer word for "omit" or "hide" or "leave out").

When the compiler comes across a function with one or more references that are missing annotations, the compiler assigns a different lifetime to every missing annotation. If there is exactly one input lifetime parameter, that lifetime is automatically assigned to all output parameters. If there is more than one input lifetime parameter but one of them is for &self, then the lifetime of self is assigned to all output parameters. Otherwise, the compiler will error.

In the case above, there's only one lifetime that first_word could really be returning; if first_word created a new String and tried to return a reference to it, the new String would be dropped when we leave the function and the reference would be invalid. The only sensible reference for it to return comes from s, so Rust infers this for us.

Lifetime elision can sometimes get things wrong. We could write a function that returns a static string for example, which would have the special lifetime 'static. In this case, we'd have to explicitly annotate the lifetimes in this function. But more often than not, lifetime elision guesses correctly, and in the cases where it doesn't we'll safely get a compiler error.

Lifetime Annotations in Method Definitions

We can add lifetime annotations to methods using the exact same generic syntax we use for functions:

impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}

fn announce_and_return_part(&self, announcement: &str) -> &'a str {
println!("Attention please: {}", announcement);
self.part
}
}

Since part has a lifetime of 'a, we can explicitly tell the compiler that the returned value here will have that same lifetime. If we left out the 'a and just returned &str, then lifetime elision would make the return value have the same lifetime as &self - this would probably be OK too in most cases, but there could be cases where the lifetime of 'a is longer than the liveness scope of self.

The Static Lifetime

As we've hinted at above, there's a special lifetime called the 'static lifetime:

let s: &'static str = "I have a static lifetime.";

This is a slice of a string that's part of the program's binary, so it will always be available. Since a 'static lifetime is the length of the whole program, and a larger lifetime can always be coerced to a smaller one, you can always safely pass a static reference wherever a reference is required.

You may see the 'static lifetime mentioned in error messages when Rust suggests a fix, but unless you actually want a reference that lasts the life of your program, likely the real problem is that you're trying to create a dangling reference or there's lifetime mismatch.

You will also sometimes see 'static used as a trait bound:

fn print_it( input: impl Debug + 'static ) {
println!( "'static value passed in is: {:?}", input );
}

This means something similar but subtly different to the 'static lifetime annotation. What this means is that the receiver can hang on to the reference for as long as they like - the reference will not become invalid until they drop it. You can always pass an owned value to satisfy a 'static trait bound. If you move a value into print_it, then the value lasts at least as long as print_it will, so as far as print_it is concerned, the value may as well last forever. You'll see a lot of examples of functions that take 'static values when we start looking at spawning new threads and dealing with async functions.

Generic Type Parameters, Trait Bounds, and Lifetimes Together

use std::fmt::Display;

fn longest_with_an_announcement<'a, T>(
x: &'a str,
y: &'a str,
announcement: T,
) -> &'a str
where
T: Display,
{
println!("Announcement! {}", announcement);
if x.len() > y.len() {
x
} else {
y
}
}

This takes two string slices and returns whichever is longer. It also prints an announcement, which is passed in as a parameter and can be any type that implements the Display trait. (If someone showed you this code before you started reading this book, I wonder what you would have thought of all these random 'as scattered throughout the code?)

Further Reading

  • There are some advanced cases where lifetime annotations are required that we haven't discussed here (for example trait bounds sometimes require lifetime annotations, but they are usually inferred). The Rust Reference is a good place to read about this sort of thing when you're a little more comfortable with the language.
  • The Rustonomicon has a section on Subtyping and Variance which goes into the technical details of how references work within the type system in much greater detail than this chapter did.
  • This excellent two part blog post gives another take on explaining lifetimes.
  • This stack overflow answer has an excellent explanation of how lifetime annotations work in structs.
  • One thing we didn't talk about in this chapter is reborrows.

Continue to chapter 11.

+ \ No newline at end of file diff --git a/ch11-automated-tests/index.html b/ch11-automated-tests/index.html index fed5d26..5bde6e0 100644 --- a/ch11-automated-tests/index.html +++ b/ch11-automated-tests/index.html @@ -4,13 +4,13 @@ 11 - Writing Automated Tests | The rs Book - +

11 - Writing Automated Tests

11.1 - How to Write Tests

Let's create a new library:

$ cargo new adder --lib
$ cd adder

You may notice that the default library project created by cargo has this block in src/lib.rs:

src/lib.rs
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}
}

This little snippet is generated by cargo for you so you don't need to remember all this boilerplate for generating a test (and perhaps as a gentle nudge to get you to write tests in the first place). This comes with some new syntax we haven't seen before though. First, there's the #[cfg(test)]. This is called an attribute. These are sort of like annotations in languages like Java or in JavaScript.

In this case, this is a configuration attribute which tells the compiler that mod tests should only be included in the compiled output if the test configuration is active. This prevents our test code from being shipped as part of our release binary. The #[test] attribute marks the it_works function as a test case.

The assert_eq! macro asserts that the two parameters passed to it are equal. If they are not, assert_eq! will panic, causing our test to fail.

We can run all tests in this project with cargo test. Cargo will run all our tests for us with the built-in test runner and report on any failures. You may notice if you look carefully at the output that there's a section about Doc-tests. Rust can actually compile examples in our documentation and run them as tests - we'll learn more about this in chapter 14.

We can add a second test to this block that always fails:

src/lib.rs
#[cfg(test)]
mod tests {
#[test]
fn exploration() {
assert_eq!(2 + 2, 4);
}

#[test]
fn another() {
panic!("Sad trombone");
}
}

Any panic! in a test will be marked as a failure. That's what assert_eq! does if the values aren't equal - it panics.

Checking Results with the assert! Macro

The assert! macro is provided by the standard library. It is much like assert in Python, Node.js, C, and friends. You pass a condition to assert, if it is true nothing happens, if it is false then it panics.

Way back in chapter 5 when we were learning about how to write methods, we came up with this example:

#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}

impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}

Here's an example of tests that use assert to verify that a larger rectangle can_hold a smaller one, and that a smaller one cannot hold a larger one:

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn larger_can_hold_smaller() {
let larger = Rectangle {
width: 8,
height: 7,
};
let smaller = Rectangle {
width: 5,
height: 1,
};

assert!(larger.can_hold(&smaller));
}

#[test]
fn smaller_cannot_hold_larger() {
let larger = Rectangle {
width: 8,
height: 7,
};
let smaller = Rectangle {
width: 5,
height: 1,
};

assert!(!smaller.can_hold(&larger));
}
}

Note that we added use super::* to the top of mod tests. The tests module is a new module that doesn't inherit anything from the parent scope. This use brings all symbols from the parent scope into our scope, so we can reference them without needing to do crate::Rectangle.

We can add a custom error message to an assert! macro:

    #[test]
fn larger_can_hold_smaller() {
let larger = Rectangle {
width: 8,
height: 7,
};
let smaller = Rectangle {
width: 5,
height: 1,
};

assert!(
larger.can_hold(&smaller),
"Rectangle {:?} should fit inside {:?}",
smaller,
larger
);
}

This works just like the println! marco. There's a format string, and then one or more parameters.

Testing Equality with the assert_eq! and assert_ne! Macros

assert_eq! takes two parameters and asserts that they are equal; if they are equal it will do nothing, if they are not it will panic. assert_ne! asserts that two values are "not equal". Some languages encourage you to make the left-hand parameter be the expected and the right be the actual, some it's the reverse. Rust doesn't give any special meaning to either value - it just calls them left and right.

If either of these fail, the resulting error message will print the left and right values for you. In order to print them the values you pass in must implement the Debug trait. These macros are implemented with the == and != operators, which means that the values you pass in also need to implement the PartialEq trait. Both of these can be derived (see appendix C):

src/lib.rs
#[derive(PartialEq, Debug)]
struct Rectangle {
width: u32,
height: u32,
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn larger_can_hold_smaller() {
let larger = Rectangle { width: 8, height: 7 };
let smaller = Rectangle { width: 2, height: 2 };

// These rectangles are not "equal".
assert_ne!(larger, smaller);
}
}

Just like assert!, we can provide an optional custom message at the end.

Checking for Panics with should_panic

If we have some code that we know should panic in certain conditions, we can verify that it does so with the should_panic attribute:

src/lib.rs
pub struct Guess {
value: i32,
}

impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 {
panic!(
"Guess value must be greater than or equal to 1, got {}.",
value
);
} else if value > 100 {
panic!(
"Guess value must be less than or equal to 100, got {}.",
value
);
}

Guess { value }
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
#[should_panic]
fn greater_than_100() {
Guess::new(200);
}
}

This test isn't very robust, because if the test panics for some reason other than what we are expecting, the test will still pass. We can fix that by passing a expected value to should_panic. The test will only pass if the panic message contains the given text.

#[cfg(test)]
mod tests {
use super::*;

#[test]
#[should_panic(expected = "less than or equal to 100")]
fn greater_than_100() {
Guess::new(200);
}
}

Using Result<T, E> in Tests

We've been writing tests that panic when they fail, but we can also write tests that return an error when they fail. This is very handy for testing functions that return a Result already, and it also allows the use of the ? operator in the test.

#[cfg(test)]
mod tests {
#[test]
fn it_works() -> Result<(), String> {
if 2 + 2 == 4 {
Ok(())
} else {
Err(String::from("two plus two does not equal four"))
}
}
}

If we want to do some "negative testing" and verify that a Result is an Err variant, we can use assert!(value.is_err()).

11.2 - Controlling How Tests Are Run

cargo test runs tests in parallel. It captures all output from tests and prevents it from being displayed, since any logging inside functions you call would be distracting when you're looking at the list of tests that passed and failed. All of this can be changed though.

When you run cargo test, you can pass arguments to cargo test itself or to the generated test running. If you try running these two commands:

$ cargo test --help
$ cargo test -- --help

You'll see very different output. Anything before the -- goes to cargo test, and anything after to the test runner.

Running Tests in Parallel or Consecutively

By default, tests run in parallel in multiple threads. If two different tests write to the same file or modify the same database, though, you can run into problems where both tests are changing things at the same time. Ideally, well written tests shouldn't do this sort of thing, but you can limit tests to running in a single thread with cargo test -- --test-threads=1. Your tests will take longer, but won't interfere with each other if they share state.

Showing Function Output

When running a test, by default all output is captured. Output is only shown for tests that fail. You can run cargo test -- --show-output to see output from passing tests, as well.

Running a Subset of Tests by Name

In a large project, running the full test suite can take a while. If you're trying to track down a problem in a specific area, sometimes you just want to run a single test or a small group of tests. If you pass a string to cargo test, it will run only tests that include that string in the name of their test function. For example, given these tests:

src/lib.rs
pub fn add_two(a: i32) -> i32 {
a + 2
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn add_two_and_two() {
assert_eq!(4, add_two(2));
}

#[test]
fn add_three_and_two() {
assert_eq!(5, add_two(3));
}

#[test]
fn one_hundred() {
assert_eq!(102, add_two(100));
}
}

We can run:

# Run all tests
$ cargo test

# Run the "one_hundred" test only
$ cargo test one_hundred

# Run any test with "add" in the name
$ cargo test add

Ignoring Some Tests Unless Specifically Requested

Sometimes we have a test that is expensive to run, or which is failing in some particularly obtuse way but we don't have time to fix the problem right now. We can skip tests with the ignore attribute:

#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}

#[test]
#[ignore]
fn expensive_test() {
// code that takes an hour to run
}

We can run only ignored tests with cargo test -- --ignored, and we can run all tests (ignored and not-ignored alike) with cargo test -- --include-ignored.

11.3 - Test Organization

In Rust we like to think about unit tests as focused tests that test a single module at a time, and integration tests as tests that test the public facing API of your library exactly as external code would, potentially exercising multiple modules and even libraries you depend on.

Unit Tests

The convention for unit tests is to add a tests module with a #[cfg(test)] attribute in each source file, which tests functions and methods found in that file (just as we saw above). Putting the test code immediately alongside the code that it is testing has many advantages.

Some people in the testing community are very passionate believers that you should only test the public parts of any module. Some will advocate for the opposite, for testing private functions and methods directly if they are difficult to exercise through the public interface. What constitutes "good practice" is well beyond the scope of this book, but note that a child module can see private members of its parent module, so the tests module is free to test private functionality.

Integration Tests

To write integration tests, we create a tests directory at the top level of our package, next to src. Cargo treats this as a special folder, and will load each file in tests and run it as an integration test. Integration tests are completely external to your library, and can only access it's public API exactly like any other consumer. For the "adder" crate we've been using as an example in this chapter, we might have a directory structure like:

adder
├── Cargo.lock
├── Cargo.toml
├── src
│ └── lib.rs
└── tests
└── integration_test.rs
tests/integration_test.rs
use adder;

#[test]
fn it_adds_two() {
assert_eq!(4, adder::add_two(2));
}

Much like the bin folder, each file in the tests folder is a separate crate, so we need to use our library by name in each one. There's also no need for a #[cfg(test)] attribute here, since these are only ever compiled when running tests.

Integration tests are run alongside unit tests, so to run these we just need to run cargo test (although note that if unit tests are failing, integration tests will not be run). We can still limit which integration tests run by passing a function name to cargo test. We can also run tests in a single file (for example, in "integration_test.rs") with cargo test --test integration_test.

Submodules in Integration Tests

Let's suppose we're working on a large project with several integration test files. It might be helpful to have some common helper functions to set up tests that we want to share across multiple files, or perhaps some mock data we want to share. You might try putting such code into a common.rs file and then using mod common. The problem here is that cargo test will think common.rs is a test file and will try to run it.

To avoid this, we can use the older module naming style we mentioned in chapter 7 and put our common code in tests/common/mod.rs. cargo test will not recurse into subdirectories, so it won't run these files as tests.

Integration Tests for Binary Crates

If your project only has a binary crate and no library crate, you can't use integration tests to run anything in your project, because you can't use anything out of a binary crate. This is another good reason why it's a good idea to put as much of your logic as you can into a library crate, and then create a thin application wrapper around it in your binary crate.

Continue to chapter 12.

- + \ No newline at end of file diff --git a/ch12-io-project-cli/index.html b/ch12-io-project-cli/index.html index 85edde2..9a85f3f 100644 --- a/ch12-io-project-cli/index.html +++ b/ch12-io-project-cli/index.html @@ -4,13 +4,13 @@ 12 - An I/O Project: Building a Command Line Program | The rs Book - +

12 - An I/O Project: Building a Command Line Program

We know enough Rust now that we can actually write a useful program. We're going to make a copy of the Linux grep command. If you're a Windows user, or you're not much of a command-line person, the grep command basically works like this:

$ grep [pattern] [filename]

We run grep and give it a pattern and a filename. grep will read the file, and print out any lines that match the pattern. We'll walk through building this project step-by-step, but if you're the sort of person who likes to read the last page of a book first, you can find the example for this project in the GitHub repo for this book.

12.1 - Accepting Command Line Arguments

We'll start this project, as we start all projects in this book, with cargo:

cargo new minigrep
cd minigrep

And we'll kick things off with this quick skeleton of our app in src/main.rs:

src/main.rs
use std::env;

fn main() {
let args: Vec<String> = env::args().collect();

// &args[0] is the name of the binary
// e.g. target/debug/minigrep
let query = &args[1];
let file_path = &args[2];

println!("Searching for {}", query);
println!("In file {}", file_path);
}

We call env::args() to get an iterator of command line arguments. We use std::env instead of use std::env::args, because the convention in rust is to include the name of the the module when calling functions.

args returns an iterator, which we're going to gloss over a bit for now (see chapter 13). Right now what you need to know is that env::args().collect() is going to return a collection of all the command line arguments. We have to annotate args with the Vec<String> type, because collect here is actually capable of returning different types of collections, and we specify which one we want by annotating the receiving variable! As is standard in most languages, the zeroth arg is the name of the executable so we skip it.

tip

We could also tell collect what type to return with the turbofish operator: ::<>:

let args = env::args().collect::<Vec<String>>();

We can also use this syntax to get Rust to infer the generic type of the vector for us:

let args: Vec<_> = env::args().collect();

We can run our program by running:

$ cargo run -- query file.txt
...
Searching for query
In file file.txt

Just like with cargo test, everything before the -- is options for cargo itself, and everything afterwards gets passed through to our executable.

Since we don't check the length of args here, if you try to run this with less than two command line arguments, it will panic. We'll add some error handling in a minute.

info

env::args() will also panic if any of the arguments contain invalid unicode. If you find yourself writing a program that needs to accept invalid unicode on the command line, check out std::env::args_os().

12.2 - Reading a File

In order to read in a file, first we need a file. Create a file called poem.txt in the root of your project (next to Cargo.toml) and paste in this text from Emily Dickinson:

poem.txt
I'm nobody! Who are you?
Are you nobody, too?
Then there's a pair of us - don't tell!
They'd banish us, you know.

How dreary to be somebody!
How public, like a frog
To tell your name the livelong day
To an admiring bog!

Then we can add some code to read the file:

src/main.rs
use std::env;
use std::fs;

fn main() {
// --snip--
println!("In file {}", file_path);

let contents = fs::read_to_string(file_path)
.expect("Should have been able to read the file");

println!("With text:\n{contents}");
}

If we run this with cargo run -- test poem.txt, it should print out the contents of the poem. Let's split this up into multiple functions and handle some of these error cases correctly.

12.3 - Refactoring to Improve Modularity and Error Handling

Separation of Concerns for Binary Projects

We've said this before, but the best way to organize an application is to have a binary crate and a library crate, and make the binary crate call into the library crate. We want as much code as possible in the library crate. This does two things; first it makes it so a third party who wants to use our code could do so without having to call the binary, and second it's much easier to test code in a library crate. We try to keep the binary crate as small as possible so it's obvious that it is correct just from reading it.

Our binary crate will:

  • Call the command line parsing logic.
  • Set up any configuration (read config files, environment variables).
  • Call a run function in lib.rs and handle any error that run returns.

Extracting the Argument Parser

Let's move all the code for parsing arguments into src/lib.rs. First, we'll create a Config struct for holding our configuration. We'll also provide a constructor which builds the config from command line arguments:

src/lib.rs
pub struct Config {
pub query: String,
pub file_path: String,
}

impl Config {
pub fn build(args: &[String]) -> Result<Config, &'static str> {
if args.len() < 3 {
return Err("not enough arguments");
}

let query = args[1].clone();
let file_path = args[2].clone();

Ok(Config { query, file_path })
}
}

Here we've defined Config in such a way that it owns the query and file_path strings. We can't directly take ownership of the strings in args, because the slice passed in owns them and we're only borrowing the slice. To fix this we're calling clone to make copies of the strings.

Cloning the strings is slightly inefficient. In our case we know that args will stick around for the entire program, so we could probably use references to the strings in args, but there'd be some work to manage the lifetimes of the references. Since the length of the query and file_path are likely to be quite short, the tradeoff in efficiency is likely going to be small, so this is fine. We'll talk about a better way to handle this situation a bit more in chapter 13 (hint: we could pass in the iterator itself instead of the result of collect).

You may have also noticed that all the constructors we've seen so far have been called new, but we called ours build. This is because build isn't quite a normal constructor. First, by convention the new constructor should not be allowed to fail. Second, if you were using this library in some other program where you were providing the arguments directly, you wouldn't want to call Config::new and pass in an array of strings, where the first string is ignored. That's a weird interface.

Speaking of failures, our build function returns a Result<T, E> so we can pass errors back to the caller. Since our errors are always constant strings, they'll have static lifetime, so our E is &'static str.

Let's also add a run function to src/lib.rs:

src/lib.rs
use std::{error::Error, fs};

// --snip--

pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
let contents = fs::read_to_string(config.file_path)?;

todo!("Implement me!");

Ok(())
}

We're returning a Result<(), Box<dyn Error>>. As we saw before Box<dyn Error> lets us return any kind of error (and as before, we'll put off covering trait objects until chapter 17). Returning a Result lets us use the ? operator when calling fs:read_to_string to propagate any error it generates up the call stack. Note at the end we're calling Ok(()) to return the unit type wrapped inside a Result. We can't just not return anything, because then we'd implicitly be returning (), which doesn't match the declared Result type.

Back in src/main.rs we'll have to update our code to handle creating the Config data structure and calling our run function:

src/main.rs
use minigrep::Config;
use std::{env, process};

fn main() {
let args: Vec<String> = env::args().collect();

let config = Config::build(&args).unwrap_or_else(|err| {
eprintln!("Problem parsing arguments: {err}");
process::exit(1);
});

if let Err(e) = minigrep::run(config) {
eprintln!("Application error: {e}");
process::exit(1);
}
}

First notice that we need to use minigrep::Config to bring Config from the library crate into the binary crate. We don't do this for run, because the convention is to use structs directly and use the crate or module name for functions. (If you're wondering why we don't have to use minigrep, it's because minigrep is a fully qualified top level path like std.)

When we call into Config::build, we're calling unwrap_or_else to handle the error case. This function unwraps the Ok variant, "or else" calls into the provided closure (see chapter 13), which we're using to print an error message and exit with an error code. When calling run we could also have used unwrap_or_else, but run doesn't return a value we want to unwrap, so instead we use the if let... syntax.

Finally, note the subtle change from our usual println! macro. We're using eprintln! here instead, which causes our errors to be printed to stderr instead of stdout. If you try running:

$ cargo run > output.txt
Problem parsing arguments: not enough arguments

you should see the error in the terminal, instead of it being sent to output.txt.

12.4 Developing the Library's Functionality

We're going to create a function called search that takes a query and the contents of a file, and returns all the matching lines. We're also going to write a test case for this function:

src/lib.rs
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let mut results = Vec::new();

for line in contents.lines() {
if line.contains(query) {
results.push(line);
}
}

results
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn one_result() {
let query = "duct";
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.";

assert_eq!(vec!["safe, fast, productive."], search(query, contents));
}
}

The one_result test calls our search function with a query and some text from a fake file, and makes sure the result contains the only line that contains duct. The only thing we haven't seen before is the multi-line string starting with "\.

In the implementation of our search function, we need to split the contents into lines, iterate over the lines, and add each line that matches into our result. We store the results in a vector and return it (passing ownership back up to the parent). Notice the lifetime annotations on search. We're telling Rust that the references in the returned vector are only valid for as long as the contents string passed in is valid.

Using the search Function in the run Function

The final piece here is to call into our search function from run:

src/lib.rs
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
let contents = fs::read_to_string(config.file_path)?;

for line in search(&config.query, &contents) {
println!("{line}");
}

Ok(())
}

We can now try cargo run -- frog poem.txt and it should print out the single line that contains "frog", or cargo run -- body poem.txt should print three lines.

12.5 - Working with Environment Variables

We'll add a feature to minigrep to allow it to do case-insensitive matches if the IGNORE_CASE environment variable is set. Why an environment variable instead of a command line switch like -i? Because this section is about environment variables.

We'll start this out by defining a new function called search_case_insensitive in src/lib.rs:

src/lib.rs
// --snip--

pub fn search_case_insensitive<'a>(
query: &str,
contents: &'a str,
) -> Vec<&'a str> {
let query = query.to_lowercase();
let mut results = Vec::new();

for line in contents.lines() {
if line.to_lowercase().contains(&query) {
results.push(line);
}
}

results
}

This looks a lot like search with a few to_lowercase added in to handle case sensitivity. Note that to_lowercase returns a copy of the string rather than editing the original, so after the first line of this function query is a String instead of a &str. It's worth noting here that to_lowercase will handle some basic Unicode - certainly anything in English - but it's not perfect, so if you were implementing this for real you'd probably pull in a crate to handle this.

No function is complete without a matching test:

src/lib.rs
#[cfg(test)]
mod tests {
use super::*;

#[test]
fn case_sensitive() {
// --snip--
}

#[test]
fn case_insensitive() {
let query = "rUsT";
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Trust me.";

assert_eq!(
vec!["Rust:", "Trust me."],
search_case_insensitive(query, contents)
);
}
}

We also need to update our Config struct to add an ignore_case boolean and update the run function to call our new search_case_insensitive function. We don't show these changes here - they're pretty easy to do yourself. If you run into trouble the complete code for this is in the GitHub repo. The interesting part is where we actually use the environment variable in Config::build:

src/lib.rs
use std::{error::Error, fs, env};

// --snip--

impl Config {
pub fn build(args: &[String]) -> Result<Config, &'static str> {
if args.len() < 3 {
return Err("not enough arguments");
}

let query = args[1].clone();
let file_path = args[2].clone();

// Read in the `IGNORE_CASE` environment variable.
let ignore_case = env::var("IGNORE_CASE").is_ok();

Ok(Config {
query,
file_path,
ignore_case,
})
}
}

env::var returns a Result<String, VarError>. If the variable is set (regardless of what it is set to), env::var will return an Ok variant, and is_ok will return true. To run this on Windows, you can use:

PS> $Env:IGNORE_CASE=1
PS> cargo run -- to poem.txt
PS> Remove-Item Env:IGNORE_CASE

On Mac or Linux, you can use:

$ IGNORE_CASE=1 cargo run -- to poem.txt

Continue to chapter 13.

- + \ No newline at end of file diff --git a/ch13-functional-language-features/index.html b/ch13-functional-language-features/index.html index 774dd1f..b27e324 100644 --- a/ch13-functional-language-features/index.html +++ b/ch13-functional-language-features/index.html @@ -4,13 +4,13 @@ 13 - Functional Language Features: Iterators and Closures | The rs Book - +

13 - Functional Language Features: Iterators and Closures

In this chapter we will cover closures, which are a like functions you can assign to variables or pass around as parameters. We'll also learn about iterators which are used for iterating over a collection of items.

13.1 - Closures: Anonymous Functions that Capture Their Environment

A closure is essentially a function that can access variables in the enclosing scope. You can store a closure in a variable or pass it as a parameter.If you're a JavaScript programmer, you're no doubt very familiar with closures.

Capturing the Environment with Closures

Here's the scenario that we're going to use for this section, taken directly from the original "The Rust Programming Language": Every so often, our t-shirt company gives away an exclusive, limited-edition shirt to someone on our mailing list as a promotion. People on the mailing list can optionally add their favorite color to their profile. If the person chosen for a free shirt has their favorite color set, they get that color shirt. If the person hasn't specified a favorite color, they get whatever color the company currently has the most of.

We'll implement this using an enum ShirtColor for the color of the shirt, and we'll use a Vec<ShirtColor> to represent stock - each item in the vector represents a t-shirt. We'll define a giveaway method on Inventory to figure out which shirt to give a customer:

src/main.rs
#[derive(Debug, PartialEq, Copy, Clone)]
enum ShirtColor {
Red,
Blue,
}

struct Inventory {
// A vector of shirt colors, one for each shirt we have in stock.
shirts: Vec<ShirtColor>,
}

impl Inventory {
/// Figure out what color of shirt to give away.
fn giveaway(&self, user_preference: Option<ShirtColor>) -> ShirtColor {
user_preference.unwrap_or_else(|| self.most_stocked())
}

// Figure out what shirt color we have the most of in inventory.
fn most_stocked(&self) -> ShirtColor {
let mut num_red = 0;
let mut num_blue = 0;

for color in &self.shirts {
match color {
ShirtColor::Red => num_red += 1,
ShirtColor::Blue => num_blue += 1,
}
}
if num_red > num_blue {
ShirtColor::Red
} else {
ShirtColor::Blue
}
}
}

fn main() {
let store = Inventory {
shirts: vec![ShirtColor::Blue, ShirtColor::Red, ShirtColor::Blue],
};

let user_pref1 = Some(ShirtColor::Red);
let giveaway1 = store.giveaway(user_pref1);
println!(
"The user with preference {:?} gets {:?}",
user_pref1, giveaway1
);

let user_pref2 = None;
let giveaway2 = store.giveaway(user_pref2);
println!(
"The user with preference {:?} gets {:?}",
user_pref2, giveaway2
);
}

Everything here should be familiar. The part we want to focus on is the giveaway method, specifically this line:

user_preference.unwrap_or_else(|| self.most_stocked())

We're calling unwrap_or_else on an Option<ShirtColor>. If the Option is the Some variant, this will unwrap the value and return it, just like unwrap. But if it's None, this will call into the closure we pass as the first parameter: || self.most_stocked(). This closure is a tiny function that takes no parameters (if there were some, they'd appear between the ||) and returns the result of self.most_stocked(). Notice that the closure is using the self variable, which isn't being passed explicitly as a parameter to the closure. This parameter is captured from the outer scope.

Closure Type Inference and Annotation

With functions, we always have to annotate the type of the function. With closures, generally we don't have to annotate the types, as Rust can usually infer the correct types from the function we're passing the closure to. We can annotate them the same way we do functions though:

let expensive_closure = |num: u32| -> u32 {
println!("calculating slowly...");
thread::sleep(Duration::from_secs(2));
num
};

Even if a closure is not annotated, it does have concrete types. This example would fail to compile:

let example_closure = |x| x;

let s = example_closure(String::from("hello"));
let n = example_closure(5);

If we were to call this with only a String, then rust would infer the type of x in the closure to be a String. Since we call it once with a String and once with an i32, Rust won't know which it should be and will generate a compiler error.

Capturing References or Moving Ownership

In JavaScript or Go, when a closure captures a value, this just counts as one more reference to the value for the garbage collector. Since Rust has no garbage collector, ownership rules apply to closures just like anywhere else. A closure can capture an immutable reference to a value, a mutable reference, or can take ownership of the value. Generally which of these happens is inferred by the compiler depending on what the closure does with the value.

src/main.rs
fn immutable_example() {
let list = vec![1, 2, 3];
println!("Before defining closure: {:?}", list);

// Here `list` is captured as an immutable reference.
let only_borrows = || println!("From closure: {:?}", list);

println!("Before calling closure: {:?}", list);
only_borrows();
println!("After calling closure: {:?}", list);
}

fn mutable_example() {
let mut list = vec![1, 2, 3];
println!("Before defining closure: {:?}", list);

// Here `list` is captured as a mutable reference,
// since we `push` a new item onto the list.
let mut borrows_mutably = || list.push(7);

borrows_mutably();
println!("After calling closure: {:?}", list);
}

In mutable_example, notice that we've declared the borrows_mutably closure as mutable itself! If you think about a closure as an implicit data structure, containing data captured from the environment, then in order to mutate any values held in that structure we have to declare the owning variable as mut. Second, notice that in mutable_example we can't print the contents of list in between when we create borrows_mutably and when we call it, since borrows_mutably has a mutable reference to list and if we have a mutable reference, we can't have any other references at the same time.

A closure will automatically take ownership of a value if it needs to. We can force a closure to take ownership of all captured values with the move keyword:

src/main.rs
use std::thread;

fn main() {
let list = vec![1, 2, 3];
println!("Before defining closure: {:?}", list);

thread::spawn(move || println!("From thread: {:?}", list))
.join()
.unwrap();
}

Here we're transferring ownership of list to a new thread. We haven't covered threads yet, but we will in chapter 16. Transferring ownership is required here, because our main function might finish before the thread, or the thread might finish first. If the thread borrowed a mutable reference, and main finished first, the value would be dropped and the underlying memory would be freed, leaving the thread with a dangling reference.

Moving Captured Values Out of Closures and the Fn Traits

Depending on what a closure does with the values it captures, the compiler will automatically add some or all of these traits to the closure:

  • FnOnce applies to all closures. It represents a closure that can be called once. All closures can be called at least once, so all closures implement this trait. If a closure moves captured values out of it's body, then it will only implement this trait. Such a closure can not safely be called twice, since it won't be able to move the captured values a second time.
  • FnMut applies to any closure that doesn't move captured values out of its body. Despite the name, the closure may or may not mutate captured values. These closures can safely be called multiple times.
  • Fn applies to any closure that implements FnMut but that also doesn't mutate any captured values. Such a closure can safely be called multiple times concurrently.

Let's take a look at the implementation of Option<T>::unwrap_or_else:

impl<T> Option<T> {
pub fn unwrap_or_else<F>(self, f: F) -> T
where
F: FnOnce() -> T
{
match self {
Some(x) => x,
None => f(),
}
}
}

Here T is the type of the Option<T> itself, and F is the type of the parameter we pass to unwrap_or_else. F has a trait bound for FnOnce() -> T, which means F must be a closure that can be called at least once, takes no parameters, and must return a T. Since all closures implement FnOnce, this lets unwrap_or_else accept any closure.

A regular function can implement these traits as well! If what we are doing doesn't require capturing any values, we can use the name of a function in place of a closure when passing a closure to a function.

Let's have a look at another standard library function, sort_by_key defined on slices:

src/main.rs
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}

fn main() {
let mut list = [
Rectangle { width: 10, height: 1 },
Rectangle { width: 3, height: 5 },
Rectangle { width: 7, height: 12 },
];

list.sort_by_key(|r| r.width);
println!("{:#?}", list);
}

This sorts the list in place, sorting by the width of each rectangle. sort_by_key takes a FnMut instead of a FnOnce. The closure we passed to sort_by_key doesn't mutate any values, but it does need to be called more than once (at least once for each Rectangle), so it can't be FnOnce.

If we tried to do something like:

let mut sort_operations = vec![];
let value = String::from("by key called");

// This doesn't compile!
list.sort_by_key(|r| {
sort_operations.push(value);
r.width
});

this wouldn't work. The problem here is that the closure takes ownership of value from the enclosing scope when it is created, then gives away ownership to sort_operations when it calls push. This means this closure only implements FnOnce. It can't be called a second time, since it won't be able to transfer ownership of value a second time. If we changed this closure to increment a counter in the enclosing scope instead of pushing a value onto a vector, this would fix the issue, as the closure could borrow the counter as a mutable reference, and would be FnMut.

13.2 - Processing a Series of Items with Iterators

In Rust, iterators are lazy, meaning if you create an iterator and then don't call any functions on it, the iterator won't do any work:

let v1 = vec![1, 2, 3];

// Create an iterator
let v1_iter = v1.iter();

// Do something for each item the iterator returns.
// The iterator doesn't do anything until we use it.
for val in v1_iter {
println!("Got: {}", val);
}

The Iterator Trait and the next Method

All iterators implement a trait from the standard library called, unsurprisingly, Iterator:

pub trait Iterator {
type Item;

fn next(&mut self) -> Option<Self::Item>;

// --snip--
}

This trait uses some syntax we haven't seen before: type Item and Self::Item. This is called an associated type and it's a very much like a generic type. We'll talk more about this in chapter 19.

The part we --snip--ed out only contains methods with default implementations, so if you want to implement Iterator, you only need to implement next. Every time next is called, it returns Some item until it runs out of items, and then it returns None. For a vector, the items are returned in the same order they were present in the vector:

#[test]
fn iterator_demonstration() {
let v1 = vec![1, 2, 3];

let mut v1_iter = v1.iter();

assert_eq!(v1_iter.next(), Some(&1));
assert_eq!(v1_iter.next(), Some(&2));
assert_eq!(v1_iter.next(), Some(&3));
assert_eq!(v1_iter.next(), None);
}

Calling next on an iterator changes it's internal state, which is why the self parameter on next is marked &mut. This means we need to declare v1_iter as mut here as well. In the example above where we used a for loop, you might notice we didn't make v1_iter mutable. This is because the for loop took ownership of the iterator and made it mutable - sneaky Rust.

Another thing to note is that the iterator returned by iter returns immutable references to the underlying collection. There's an iter_mut that returns mutable references, if we want to modify some or all of the members of a collection. There's also an into_iter which takes ownership of the receiver (into because it converts the underlying collection into an iterator, and you won't be able to access the underlying collection anymore) and returns owned values. For example, if you called v1.into_iter above, you'd get back an iterator of owned values, and wouldn't be able to use v1 anymore.

Methods that Consume the Iterator

If you have a look at the documentation for Iterator you'll see that it provides quite a few methods with default implementations. Many of these call into next, which is why you don't have to implement them all. Calling into next though means that these will consume some or all of the items in the iterator. We call these consuming adaptors.

The sum method for example will consume all items in the iterator and sum them together. In order to ensure we can't use an iterator after calling sum, sum takes ownership of the iterator.

Methods that Produce Other Iterators

Iterator adaptors are methods on an iterator that don't consume the contents of the iterator, but they do take ownership of the iterator, converting it into some new kind of iterator. A common example of this is the map method (which should be familiar if you're coming from JavaScript):

let v1: Vec<i32> = vec![1, 2, 3];

let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();

Here v2 will be vec![2, 3, 4]. map produces a new iterator of modified items from the underlying vector.

We call collect here to transform the new iterator returned by map into a vector. Note that map, by itself, consumes no values! Until we call collect the closure won't be called. This "lazy" behavior is a bit different from map in JavaScript.

Using Closures that Capture Their Environment

The filter method (another familiar method for the JavaScript folks) is another iterator adaptor, which consumes the old iterator and returns a new one. It's parameter is a closure that returns a boolean, which is used to "filter out" some elements from the underlying iterator. The closure is called for each item, and if it returns true the new iterator will include the item, if false the item will be discarded:

#[derive(PartialEq, Debug)]
struct Shoe {
size: u32,
style: String,
}

fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
shoes.into_iter().filter(|s| s.size == shoe_size).collect()
}

Notice here that the closure captures shoe_size from the environment and uses it to decide whether a shoe should be included in the returned vector or not.

13.3 - Improving our I/O Project

With our new friend the iterator we can revisit our project from chapter 12 and make some of the code clearer and more concise.

Removing a clone Using an Iterator

In chapter 12 we wrote this Config struct:

impl Config {
pub fn build(args: &[String]) -> Result<Config, &'static str> {
if args.len() < 3 {
return Err("not enough arguments");
}

let query = args[1].clone(); // <= Ugly clone! It hurts us!
let file_path = args[2].clone();

let ignore_case = env::var("IGNORE_CASE").is_ok();

Ok(Config {
query,
file_path,
ignore_case,
})
}
}

and we promised that when we got to chapter 13, we'd talk more about that call to clone. Our problem here was that the args vector passed in here owns the strings we want to use, and we're only borrowing args so we can't take ownership of them. But, if we go look at where build is called:

let args: Vec<String> = env::args().collect();

let config = Config::build(&args).unwrap_or_else(|err| {
println!("Problem parsing arguments: {err}");
process::exit(1);
});

we're actually getting an iterator back from env::args() and converting the iterator into a vector. Instead of doing this, we could pass the iterator directly to build, then build can consume the iterator and take ownership of the strings. In the caller we change the above to:

let config = Config::build(env::args()).unwrap_or_else(|err| {
eprintln!("Problem parsing arguments: {err}");
process::exit(1);
});

And then in build we can do:

impl Config {
pub fn build(
mut args: impl Iterator<Item = String>,
) -> Result<Config, &'static str> {
args.next();

let query = match args.next() {
Some(arg) => arg,
None => return Err("Didn't get a query string"),
};

let file_path = match args.next() {
Some(arg) => arg,
None => return Err("Didn't get a file path"),
};

let ignore_case = env::var("IGNORE_CASE").is_ok();

Ok(Config {
query,
file_path,
ignore_case,
})
}
}

We've updated the function signature so args is now a generic type with a trait bound allowing any iterator which returns Strings. Since we're taking ownership of args and we'll be mutating it, we'll mark it as mut. Then we read out parameters one by one. We start with a call to args.next() to skip over the first parameter - the name of our executable - then we copy each subsequent value into a variable, taking ownership without having to clone anything.

Making Code Clearer with Iterator Adaptors

Recall our original implementation of search:

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let mut results = Vec::new();

for line in contents.lines() {
if line.contains(query) {
results.push(line);
}
}

results
}

We can now rewrite this in a more functional style, and eliminate our mutable results vector:

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
contents
.lines()
.filter(|line| line.contains(query))
.collect()
}

Choosing Between Loops or Iterators

In general, the "functional" style with iterators is preferred by most Rust programmers. The code is more concise, and in most cases will be easier to read. In our search example, a theoretical enhancement would be to do the filtering in parallel across multiple threads. This will be easier to do now that we no longer has to manage a mutable results vector.

13.4 Comparing Performance: Loops vs. Iterators

How well do iterators perform? The original "The Rust Programming Language" had this to say:

We ran a benchmark by loading the entire contents of The Adventures of Sherlock Holmes by Sir Arthur Conan Doyle into a String and looking for the word "the" in the contents. Here are the results of the benchmark on the version of search using the for loop and the version using iterators:

test bench_search_for ... bench: 19,620,300 ns/iter (+/- 915,700)
test bench_search_iter ... bench: 19,234,900 ns/iter (+/- 657,200)

We can see that for our implementation, the iterator implementation was ever so slightly faster. Understand the point here is not to say "iterators are faster" or "for loops are faster", the point is that in most situations, they're going to be pretty close. Rust calls iterators a zero cost abstraction meaning that they don't add any extra overhead over "hand coding" a solution. If you try to use an iterator over a short fixed size array, in many cases Rust will "unroll the loop" and if you examine the underlying assembly, you'll find no loop at all, no bounds checks, and all your values stored in registers, exactly as if you'd hand coded it.

If you really need to eek out every last bit of performance, you'll want to write some benchmark tests that exercise your code with a variety of different inputs.

Continue to chapter 14.

- + \ No newline at end of file diff --git a/ch14-more-about-cargo/index.html b/ch14-more-about-cargo/index.html index 3f8a6cc..402c465 100644 --- a/ch14-more-about-cargo/index.html +++ b/ch14-more-about-cargo/index.html @@ -4,13 +4,13 @@ 14 - More about Cargo and Crates | The rs Book - +
-

14 - More about Cargo and Crates

14.1 - Customizing Builds with Release Profiles

Cargo has four built-in release profiles called dev, release, test, and bench. cargo build will build in the dev profile, and cargo build --release in the release profile, and the other two are used when running tests. Cargo has various settings for for these profiles, which you can override in Cargo.toml:

[profile.dev]
opt-level = 0
overflow-checks = true

[profile.release]
opt-level = 3
overflow-checks = false

In this example opt-level controls how much Rust tries to optimize your code and can be set from 0 to 3 (these are also the defaults - 0 for dev because you want the build to be fast, 3 for release because you want your program to be fast). overflow-checks is used to determine whether or not Rust will add in runtime checks to see if integer arithmetic overflows any values.

For a full list of options see the cargo documentation.

14.2 - Publishing a Crate to Crates.io

If you write a library crate, you can publish it to crates.io so other people can use it.

Setting Up a Crates.io Account

To publish something on crates.io, first you'll need to create an account. Then visit https://crates.io/me, grab your API key, and run:

$ cargo login your-key-here

Your API key will be stored in ~/.cargo/credentials. This is secret so if anyone gets ahold of your API key, be sure to revoke it and generate a new one, otherwise people can publish crates in your name, and before you know it all your crates will be full of crypto miners or worse.

Making Useful Documentation Comments

One thing we haven't done much of in our examples so far is to document our public structs and functions, but if you look at any package on crates.io you'll see everything has automatically generated helpful documentation. This documentation comes from documentation comments which start with three slashes instead of two, and support markdown. Documentation comments should be placed immediately before the thing they are documenting:

/// Adds one to the number given.
///
/// # Examples
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
x + 1
}

If you run cargo doc --open in your project, you can see what the generated documentation for your project will look like. This will also include documentation for any crates that you depend on.

Commonly Used Sections

We used the # Examples markdown heading above to make a section for our examples. Some other commonly used headings:

  • # Panics describes the scenarios in which the given function might panic.
  • # Errors describes the kinds of conditions under which this function might return an error, and what kinds of errors are returned.
  • # Safety should be present for any function that is unsafe (see chapter 19).

You don't need all of these on every function. Any examples you provide will automatically be run as tests when you cargo test, so you'll know your examples actually work.

Commenting Contained Items

There's a second kind of documentation comment //! that, instead of documenting what comes right after it, documents the "parent" of the comment. You can use these at the root of src/lib.rs to document the crate as a whole, or inside a module to document the module:

//! # My Crate
//!
//! `my_crate` is a collection of utilities to make performing certain
//! calculations more convenient.

/// Adds one to the number given.
// --snip--

Exporting a Convenient Public API with pub use

We talked about this back in chapter 7, but sometimes we might organize the internals of our library in such a way that makes sense to use when we're developing it, but is at odds with how someone would actually want to use our crate. If you have some struct or module that is frequently used by users of your crate, but is buried deep in the module hierarchy, this is going to be a pain point for your users.

Here's an example:

//! # Art
//!
//! A library for modeling artistic concepts.

pub mod kinds {
/// The primary colors according to the RYB color model.
pub enum PrimaryColor {
Red,
Yellow,
Blue,
}

/// The secondary colors according to the RYB color model.
pub enum SecondaryColor {
Orange,
Green,
Purple,
}
}

pub mod utils {
use crate::kinds::*;

/// Combines two primary colors in equal amounts to create
/// a secondary color.
pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {
// --snip--
}
}

Users of this crate would have to write code like:

use art::kinds::PrimaryColor;
use art::utils::mix;

fn main() {
let red = PrimaryColor::Red;
let yellow = PrimaryColor::Yellow;
mix(red, yellow);
}

but users of this crate probably don't care about kinds or utils - to them this should be top level functionality for this crate. We can "re-export" these at the top level with pub use:

//! # Art
//!
//! A library for modeling artistic concepts.

pub use self::kinds::PrimaryColor;
pub use self::kinds::SecondaryColor;
pub use self::utils::mix;

pub mod kinds {
// --snip--
}

pub mod utils {
// --snip--
}

Adding Metadata to a New Crate

In order to publish on crates.io, our crate needs a name, a description, and a valid license identifier:

Cargo.toml
[package]
name = "my_awesome_colors"
version = "0.1.0"
edition = "2021"
description = "A fancy awesome crate for generating colored text in the terminal."
license = "MIT"

Publishing Your Crate

To publish your package run:

$ cargo publish

If someone has already used your name on crates.io then this will fail - names are handed out first-come-first-served. If you make some changes to your crate, bump the version number (using semantic versioning rules) and then run cargo publish again.

Deprecating Versions from Crates.io with cargo yank

Sometimes an older version of your crate will have some terrible security bug, or has a fatal bug that completely breaks it. For this or other reasons, you'll want to stop people from installing this version and warn existing users they need to upgrade. You can't remove an old version, but you can mark it as deprecated:

$ cargo yank --vers 1.0.1

If you accidentally yank the wrong version, you can undo this:

$ cargo yank --vers 1.0.1 --undo

14.3 - Cargo Workspaces

Sometimes a library crate gets so large that you want to split it up into multiple smaller crates. Cargo workspaces lets you do this while keeping all the crates together in the same git repo. It's a bit like the Rust version of a monorepo. You can build all packages in a workspace by running cargo build from the root folder of the workspace, and run tests in all workspaces with cargo test.

Creating a Workspace

A workspace consists of multiple packages with their own individual Cargo.yaml files, with a single Cargo.lock file at the root of the workspace. We'll create a simple example here with a single binary crate and two library crates. If you want to see the code for this, check this book's GitHub repo. First we'll create a new directory for our workspace and initialize it as a git repo:

$ mkdir add
$ cd add
$ git init .
$ echo "/target" > .gitignore

The add directory is the root of our workspace, so all other files we create will be relative to this folder. We're going to add three packages to this workspace: "adder", our binary package, and "add_one" and "add_two", our libraries. Let's start by creating these packages as subdirectories:

$ cargo new adder
$ cargo new add_one --lib
$ cargo new add_two --lib

You may have noticed that we ran git init in the add directory - we did this because generally we want to commit the entire workspace as a single repo, and if we hadn't run git init, then cargo new ... would have "helpfully" initialized all three new packages as git repos for us.

Now in the add folder - the root folder of our workspace - we are going to create a Cargo.toml for the entire workspace. This Cargo.toml won't have any metadata or dependencies, it will simply list all the packages that make up the workspace:

[workspace]

members = [
"adder",
"add_one",
"add_two",
]
tip

If you do these in the opposite order - create the top-level Cargo.toml first and then create the child packages - it will still work, but as you create each package you'll get warnings from cargo about not being able to find the packages you haven't created yet.

We can build this workspace, to make sure we did everything right:

$ cargo build
Compiling add_two v0.1.0 (add/add_two)
Compiling adder v0.1.0 (add/adder)
Compiling add_one v0.1.0 (add/add_one)
Finished dev [unoptimized + debuginfo] target(s) in 0.11s

At this point you should have a directory structure inside add that looks like:

├── .git
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── add_one
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── add_two
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target

Note that the top level folder has a Cargo.lock file, but none of the child projects do.

Referencing Other Packages in the Workspace

We'll put this in add_one/src/lib.rs:

pub fn add_one(x: i32) -> i32 {
x + 1
}

We want to use this library in our binary crate in the adder folder. To do this, first we have to add the add_one package as a dependency of adder:

adder/Cargo.toml
[dependencies]
add_one = { path = "../add_one" }

And then we can use add_one in the adder package, just as we would any other dependency:

adder/src/main.rs
use add_one;

fn main() {
let num = 10;
println!("Hello, world! {num} plus one is {}!", add_one::add_one(num));
}

From the add directory we can now run:

$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/adder`
Hello, world! 10 plus one is 11!

If we had multiple packages with binary crates in the workspace, we'd have to specify which package to run with cargo run -p adder, or we could cd adder and then cargo run from the adder folder.

Depending on an External Package in a Workspace

We can depend on an external create in a workspace by adding it to the [dependencies] section of the appropriate package's Cargo.toml. For example, we can add the rand crate to add_one in add_one/Cargo.toml:

add_one/Cargo.toml
[dependencies]
rand = "0.8.5"

If we add use rand; inside add_one/src/lib.rs, then cargo build, we'll see the rand package being downloaded. We'll also get a warning because we're useing rand, but we never reference it in the library. Oops!

If we want to use rand in other packages in our workspace, we have to add it again to the appropriate Cargo.yaml. Since there's only one Cargo.lock file for the whole workspace, if adder and add_one both depend on rand, we know that they will both depend on the same version of rand thanks to the common lockfile (at least, assuming the have compatible semver versions in the different Cargo.toml files).

14.4 - Installing Binaries with cargo install

You can publish more than just library crates to crates.io - you can also publish binary crates! Users can install your crates with cargo install. (This is very similar to npm install -g if you're a node.js developer.) For example, ripgrep is a very fast alternative to the grep command:

$ cargo install ripgrep

Binaries you install this way get put in ~/.cargo/bin (assuming you're running a default installation of cargo from rustup). You'll probably want to put this folder in your shell's $PATH. The name of the installed binary is not necessarily the same as the name of the crate. If you try installing ripgrep above, for example, it will install ~/.cargo/rg.

14.5 - Extending Cargo with Custom Commands

Much like git, you can create your own custom cargo commands. If there's an executable on your path called cargo-something, then you can run cargo something to run that executable. These custom commands will also show up in cargo --list.

Continue to chapter 15.

- +

14 - More about Cargo and Crates

14.1 - Customizing Builds with Release Profiles

Cargo has four built-in release profiles called dev, release, test, and bench. cargo build will build in the dev profile, and cargo build --release in the release profile, and the other two are used when running tests. Cargo has various settings for for these profiles, which you can override in Cargo.toml:

[profile.dev]
opt-level = 0
overflow-checks = true

[profile.release]
opt-level = 3
overflow-checks = false

In this example opt-level controls how much Rust tries to optimize your code and can be set from 0 to 3 (these are also the defaults - 0 for dev because you want the build to be fast, 3 for release because you want your program to be fast). overflow-checks is used to determine whether or not Rust will add in runtime checks to see if integer arithmetic overflows any values.

For a full list of options see the cargo documentation.

14.2 - Publishing a Crate to Crates.io

If you write a library crate, you can publish it to crates.io so other people can use it.

Setting Up a Crates.io Account

To publish something on crates.io, first you'll need to create an account. Then visit https://crates.io/me, grab your API key, and run:

$ cargo login your-key-here

Your API key will be stored in ~/.cargo/credentials. This is secret so if anyone gets ahold of your API key, be sure to revoke it and generate a new one, otherwise people can publish crates in your name, and before you know it all your crates will be full of crypto miners or worse.

Making Useful Documentation Comments

One thing we haven't done much of in our examples so far is to document our public structs and functions, but if you look at any package on crates.io you'll see everything has automatically generated helpful documentation. This documentation comes from documentation comments which start with three slashes instead of two, and support markdown. Documentation comments should be placed immediately before the thing they are documenting:

/// Adds one to the number given.
///
/// # Examples
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
x + 1
}

If you run cargo doc --open in your project, you can see what the generated documentation for your project will look like. This will also include documentation for any crates that you depend on.

Commonly Used Sections

We used the # Examples markdown heading above to make a section for our examples. Some other commonly used headings:

  • # Panics describes the scenarios in which the given function might panic.
  • # Errors describes the kinds of conditions under which this function might return an error, and what kinds of errors are returned.
  • # Safety should be present for any function that is unsafe (see chapter 19).

You don't need all of these on every function. Any examples you provide will automatically be run as tests when you cargo test, so you'll know your examples actually work.

Commenting Contained Items

There's a second kind of documentation comment //! that, instead of documenting what comes right after it, documents the "parent" of the comment. You can use these at the root of src/lib.rs to document the crate as a whole, or inside a module to document the module:

//! # My Crate
//!
//! `my_crate` is a collection of utilities to make performing certain
//! calculations more convenient.

/// Adds one to the number given.
// --snip--

Exporting a Convenient Public API with pub use

We talked about this back in chapter 7, but sometimes we might organize the internals of our library in such a way that makes sense to use when we're developing it, but is at odds with how someone would actually want to use our crate. If you have some struct or module that is frequently used by users of your crate, but is buried deep in the module hierarchy, this is going to be a pain point for your users.

Here's an example:

//! # Art
//!
//! A library for modeling artistic concepts.

pub mod kinds {
/// The primary colors according to the RYB color model.
pub enum PrimaryColor {
Red,
Yellow,
Blue,
}

/// The secondary colors according to the RYB color model.
pub enum SecondaryColor {
Orange,
Green,
Purple,
}
}

pub mod utils {
use crate::kinds::*;

/// Combines two primary colors in equal amounts to create
/// a secondary color.
pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {
// --snip--
}
}

Users of this crate would have to write code like:

use art::kinds::PrimaryColor;
use art::utils::mix;

fn main() {
let red = PrimaryColor::Red;
let yellow = PrimaryColor::Yellow;
mix(red, yellow);
}

but users of this crate probably don't care about kinds or utils - to them this should be top level functionality for this crate. We can "re-export" these at the top level with pub use:

//! # Art
//!
//! A library for modeling artistic concepts.

pub use self::kinds::PrimaryColor;
pub use self::kinds::SecondaryColor;
pub use self::utils::mix;

pub mod kinds {
// --snip--
}

pub mod utils {
// --snip--
}

Adding Metadata to a New Crate

In order to publish on crates.io, our crate needs a name, a description, and a valid license identifier:

Cargo.toml
[package]
name = "my_awesome_colors"
version = "0.1.0"
edition = "2021"
description = "A fancy awesome crate for generating colored text in the terminal."
license = "MIT"

Publishing Your Crate

To publish your package run:

$ cargo publish

If someone has already used your name on crates.io then this will fail - names are handed out first-come-first-served. If you make some changes to your crate, bump the version number (using semantic versioning rules) and then run cargo publish again.

Deprecating Versions from Crates.io with cargo yank

Sometimes an older version of your crate will have some terrible security bug, or has a fatal bug that completely breaks it. For this or other reasons, you'll want to stop people from installing this version and warn existing users they need to upgrade. You can't remove an old version, but you can mark it as deprecated:

$ cargo yank --vers 1.0.1

If you accidentally yank the wrong version, you can undo this:

$ cargo yank --vers 1.0.1 --undo

14.3 - Cargo Workspaces

Sometimes a library crate gets so large that you want to split it up into multiple smaller crates. Cargo workspaces lets you do this while keeping all the crates together in the same git repo. It's a bit like the Rust version of a monorepo. You can build all packages in a workspace by running cargo build from the root folder of the workspace, and run tests in all workspaces with cargo test.

Creating a Workspace

A workspace consists of multiple packages with their own individual Cargo.yaml files, with a single Cargo.lock file at the root of the workspace. We'll create a simple example here with a single binary crate and two library crates. If you want to see the code for this, check this book's GitHub repo. First we'll create a new directory for our workspace and initialize it as a git repo:

$ mkdir add
$ cd add
$ git init .
$ echo "/target" > .gitignore

The add directory is the root of our workspace, so all other files we create will be relative to this folder. We're going to add three packages to this workspace: "adder", our binary package, and "add_one" and "add_two", our libraries. Let's start by creating these packages as subdirectories:

$ cargo new adder
$ cargo new add_one --lib
$ cargo new add_two --lib

You may have noticed that we ran git init in the add directory - we did this because generally we want to commit the entire workspace as a single repo, and if we hadn't run git init, then cargo new ... would have "helpfully" initialized all three new packages as git repos for us.

Now in the add folder - the root folder of our workspace - we are going to create a Cargo.toml for the entire workspace. This Cargo.toml won't have any metadata or dependencies, it will simply list all the packages that make up the workspace:

[workspace]

members = [
"adder",
"add_one",
"add_two",
]
tip

If you do these in the opposite order - create the top-level Cargo.toml first and then create the child packages - it will still work, but as you create each package you'll get warnings from cargo about not being able to find the packages you haven't created yet.

We can build this workspace, to make sure we did everything right:

$ cargo build
Compiling add_two v0.1.0 (add/add_two)
Compiling adder v0.1.0 (add/adder)
Compiling add_one v0.1.0 (add/add_one)
Finished dev [unoptimized + debuginfo] target(s) in 0.11s

At this point you should have a directory structure inside add that looks like:

├── .git
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── add_one
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── add_two
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target

Note that the top level folder has a Cargo.lock file, but none of the child projects do.

Referencing Other Packages in the Workspace

We'll put this in add_one/src/lib.rs:

pub fn add_one(x: i32) -> i32 {
x + 1
}

We want to use this library in our binary crate in the adder folder. To do this, first we have to add the add_one package as a dependency of adder:

adder/Cargo.toml
[dependencies]
add_one = { path = "../add_one" }

And then we can use add_one in the adder package, just as we would any other dependency:

adder/src/main.rs
use add_one;

fn main() {
let num = 10;
println!("Hello, world! {num} plus one is {}!", add_one::add_one(num));
}

From the add directory we can now run:

$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/adder`
Hello, world! 10 plus one is 11!

If we had multiple packages with binary crates in the workspace, we'd have to specify which package to run with cargo run -p adder, or we could cd adder and then cargo run from the adder folder.

Depending on an External Package in a Workspace

We can depend on an external create in a workspace by adding it to the [dependencies] section of the appropriate package's Cargo.toml. For example, we can add the rand crate to add_one in add_one/Cargo.toml:

add_one/Cargo.toml
[dependencies]
rand = "0.8.5"

If we add use rand; inside add_one/src/lib.rs, then cargo build, we'll see the rand package being downloaded. We'll also get a warning because we're useing rand, but we never reference it in the library. Oops!

If we want to use rand in other packages in our workspace, we have to add it again to the appropriate Cargo.yaml. Since there's only one Cargo.lock file for the whole workspace, if adder and add_one both depend on rand, we know that they will both depend on the same version of rand thanks to the common lockfile (at least, assuming the have compatible semver versions in the different Cargo.toml files).

14.4 - Installing Binaries with cargo install

You can publish more than just library crates to crates.io - you can also publish binary crates! Users can install your crates with cargo install. (This is very similar to npm install -g if you're a node.js developer.) For example, ripgrep is a very fast alternative to the grep command:

$ cargo install ripgrep

Binaries you install this way get put in ~/.cargo/bin (assuming you're running a default installation of cargo from rustup). You'll probably want to put this folder in your shell's $PATH. The name of the installed binary is not necessarily the same as the name of the crate. If you try installing ripgrep above, for example, it will install ~/.cargo/rg.

14.5 - Extending Cargo with Custom Commands

Much like git, you can create your own custom cargo commands. If there's an executable on your path called cargo-something, then you can run cargo something to run that executable. These custom commands will also show up in cargo --list.

One handy command you can install this way is cargo-audit, which will check your dependencies against the rustsec security advisory database:

$ cargo install cargo-audit
$ cargo audit

Continue to chapter 15.

+ \ No newline at end of file diff --git a/ch15-smart-pointers/index.html b/ch15-smart-pointers/index.html index 59f32c3..1fb8e57 100644 --- a/ch15-smart-pointers/index.html +++ b/ch15-smart-pointers/index.html @@ -4,13 +4,13 @@ 15 - Smart Pointers | The rs Book - +

15 - Smart Pointers

In C++, whenever we want to store an object on the heap, we new that object to allocate some memory. At some later point in time, we have to delete that memory. This is much like malloc and free in standard C.

C++ has a few different "smart pointers" that will delete that memory for you at the appropriate time. The most commonly used is probably shared_ptr, which keeps a reference count on the heap. Every time your clone a shared_ptr it increments the reference count (which is shared between all the clones), and every time one is destroyed it decrements the count. Once the count reaches 0, shared_ptr knows there are no more references to the underlying memory so it is safe to be freed.

Rust has a variety of smart pointer objects as well, which allow us to store values on the heap, including Rc<T> which works much like C++'s shared_ptr and allows us to effectively share ownership of a value across multiple variables in code. This chapter will explore a few of the different smart pointer implementations in Rust and where you might want to use them.

Smart pointers in Rust generally implement the Drop trait (so they can run some custom code when they are dropped, like decrementing a reference count) and the Deref trait (which lets a smart pointer be used in place of a reference to the underlying value).

15.1 - Using Box<T> to Point to Data on the Heap

Box<T> is perhaps the "least smart" of the smart pointers. Box<T> lets us store a single piece of data on the heap instead of on the stack:

src/main.rs
fn main() {
let b = Box::new(5);
println!("b = {}", b);
}

Here "5" gets stored as four bytes on the heap instead of as four bytes on the stack. Notice that we can use b exactly like a &i32 when we pass it to println!.

Why would we want to do this? When we're passing data around on the stack, Rust has to know the size of that data at compile time. When we pass an i32 as a parameter, for example, Rust knows that it's going to need 4 bytes on the stack to hold that parameter. But sometimes we don't know the size of a value ahead of time, and this is where Box<T> is useful - examples would be recursive data structures (which can be "infinitely" large since they can contain more of themselves) and trait objects, where we want to claim that a parameter implements a specific trait but we don't care what concrete type the parameter is (we'll talk more about these in chapter 17).

In these cases, instead of passing the value directly on the stack, we pass the Box<T> on the stack and put the unknown-sized value on the heap. The size of Box<T> is known at compile time, so the compiler can do it's thing.

Another example where Box<T> would be useful is where you have some particularly large piece of data that you want to pass around. Values passed on the stack are pass-by-copy, and copying large amounts of data can be inefficient. Storing the data on the heap lets us pass around copies of the relatively small Box<T> instead.

Enabling Recursive Types with Boxes

This is a data structure called the cons list which we're going to borrow from lisp:

enum List {
Cons(i32, List),
Nil,
}

This is sort of a "linked list", where each item is either an i32 and a "next item on the list", or else is Nil (to signify the end of the list). We could use this like:

use List::{Cons, Nil}

fn main() {
let list = Cons(1, Cons(2, Cons(3, Nil)));
}

This is probably not a data structure you'd actually want to use in Rust, but it's a recursive data structure that's convenient for this example. If you try to compile the above, it will fail, because Rust can't work out the size of the list variable to store it on the stack.

For an enum, Rust will allocate enough memory to store the largest of the enum's variants. Here the largest is going to be Cons, which can hold an i32 and a List, so it's four bytes long plus the size of a List. But this is a recursive definition - sizeof(List) = 4 + sizeof(List). This makes rustc an unhappy compiler.

The solution is to move this to the heap:

src/main.rs
use List::{Cons, Nil}

enum List {
Cons(i32, Box<List>),
Nil,
}

fn main() {
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
}

Now sizeof(Cons) = 4 + sizeof(Box<List>), and the size of Box<List> doesn't depend on the size of <List> (since that part is stored on the heap), so the size of the list variable is known at compile time.

15.2 Treating Smart Pointers Like Regular References with the Deref Trait

In this section we're going to implement our own smart pointer called MyBox. Our smart pointer won't actually store anything on the heap, it will just store things on the stack. It will be even less smart than a regular Box<T>, but it will give us a chance to explore the Deref trait.

Following the Pointer to the Value

Before we talk about Deref, let's talk about what we mean by dereferencing.

src/main.rs
fn main() {
let x = 5;
let y = &x;

assert_eq!(5, x);
assert_eq!(5, *y);
}

Here x is of type i32, but y is of type &i32. y is essentially a pointer to x. We can assert that x is equal to 5, but in order to get to the value of y we have to dereference it to get to the value that y points to. Rust will automatically dereference a value for you in many places, so the * operator doesn't get much use in Rust, but there are places (like in this example) where it is required.

info

If you're coming from a language like C or Go, this is probably second nature to you. If you're coming from JavaScript, this might be a new concept. Because y here points to the memory that stores the x value, you can think about *y as basically an alias for x. If x were mutable and we declared y as &mut, we could use *y to change x because it points to the memory where x is stored:

src/main.rs
fn main() {
let mut x = 5;
let y = &mut x;
*y = 10;
assert_eq!(10, x);
}

Another way to think about this is that a reference in Rust is a little bit like a Ref from React that points to an object:

typescript.ts
interface Num {
value: number;
}

interface Ref<T> {
current: T;
}

function main() {
const x: Num = { value: 5 };
const y: Ref<Num> = { current: x };

assert.equal(x.value, 5);
assert.equal(y.current.value, 5);
}

In our Rust example, the *y is basically doing the same thing as y.current in our TypeScript example.

Using Box<T> Like a Reference

Because Box<T> implements Deref, we can use the * operator on it, and treat it just like a reference. This (combined with a Rust feature called deref coercion) means that any function that takes a &i32 can also take a &Box<i32>:

src/main.rs
fn show_value(v: &i32) {
println!("{v}");
}

fn main() {
let x = 5;
let y = Box::new(x);

show_value(&x); // Prints 5
show_value(&y); // Prints 5
}

Defining Our Own Smart Pointer

Let's create our own smart pointer so we can implement the Deref trait. To do this we'll create a simple "pointer" that stores a value in a generic named tuple:

struct MyBox<T>(T);

impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}

And then, in order to let us use the * operator on MyBox<T>, we implement the Deref trait. This trait has only one required method for us to implement called deref, which borrows self and returns the inner value:

use std::ops::Deref;

impl<T> Deref for MyBox<T> {
type Target = T;

fn deref(&self) -> &Self::Target {
&self.0
}
}

(Since many other languages don't have tuples, a quick reminder here that in the deref method self here is a tuple, so self.0 is the first (and only) element in the tuple, and &self.0 returns a reference to the first element in the tuple.)

And now we can do:

src/main.rs
fn main() {
let x = 5;
let y = MyBox::new(x);

assert_eq!(5, x);
assert_eq!(5, *y);
}

When we write *y here (or on any object that implements Deref), what's actually happening is Rust is going to replace this with *(y.deref()).

Implicit Deref Coercions with Functions and Methods

We've noted before that you can pass a &String to a function that expects a &str:

src/main.rs
fn hello(name: &str) {
println!("Hello, {name}!");
}

fn main() {
let x = String::from("Rust");
hello(&x);
}

The reason this works is because of a feature called deref coercion. If we call a function that takes a reference to type X, but instead we pass a reference to type Y, then if Y implements Deref Rust will call deref on the value we passed in (possible more than once!) to convert it into a reference to the correct type. For example, String implements the Deref trait and returns a &str, so Rust can automatically convert a &String to &str.

If we were to pass a &MyBox<String> to the hello function above, Rust would convert it to a &String via MyBox's deref method, and then into a &str via String's deref method.

If Rust didn't implement deref coercion, we'd have to write something like:

src/main.rs
fn main() {
let m = MyBox::new(String::from("Rust"));
hello(&(*m)[..]);
}

And no one wants to write that.

How Deref Coercion Interacts with Mutability

The Deref trait only works with immutable references, but there is also a DerefMut trait for mutable references. Rust will do deref coercion in three cases:

  • If you have a &T and you want an &U, then if T implements Deref to type &U, then rust will take care of this for you, just like we saw above.
  • If you have a &mut T and want an &mut U, this will happen in exactly the same way but here the conversion will happen via the DerefMut trait instead.
  • If you have a &mut T and you want a &U, then Rust will use the Deref trait on type T to convert the mutable ref to an immutable &U.

Obviously ownership rules prevent Rust from automatically converting a &T to a &mut U.

15.3 - Running Code on Cleanup with the Drop trait

The Drop trait allows us to specify some code that must be run whenever a struct is dropped (i.e. when it goes out of scope). Box<T> implements Drop so it can clean up the memory it is using on the heap. The Rc<T> type (which will talk about in the next section) implements Drop so it can decrement a reference count.

Drop can also be used to clean up other resources. If you have a struct that opens a network connection in its constructor, you can implement the Drop trait to ensure the network connection is closed when the struct is dropped, ensuring you won't leak any resources. This is a pattern borrowed from C++ called "Resource Acquisition Is Initialization" or RAII.

The Drop trait is included in the prelude, and has only one required method named drop. Let's see an example:

src/main.rs
struct CustomSmartPointer {
data: String,
}

impl Drop for CustomSmartPointer {
fn drop(&mut self) {
println!("Dropping CustomSmartPointer with data `{}`!", self.data);
}
}

fn main() {
let c = CustomSmartPointer {
data: String::from("my stuff"),
};
let d = CustomSmartPointer {
data: String::from("other stuff"),
};
println!("CustomSmartPointers created.");

// `drop` is called automatically on `c`
// and `d` here.
}

If you run this, you'll see the drop method gets called automatically for c and d when they get dropped at the end of main.

Dropping a Value Early with std::mem::drop

Sometimes we may want to drop a value earlier than it would normally get dropped at the end of the scope. For example, if we're using the RAII pattern to acquire some resource like a lock or a network connection, we may want to drop that value early to release that resource before we reach the end of the function.

We cannot simply call the drop method on a type, however, as the Rust compiler is going to call it for us, and we don't want to double free any memory or resources by calling drop twice. Instead we can call std::mem::drop, passing in the value we want to drop:

fn main() {
let c = CustomSmartPointer {
data: String::from("some data"),
};
println!("CustomSmartPointer created.");
drop(c);
println!("CustomSmartPointer dropped before the end of main.");
}

15.4 - Rc<T>, the Reference Counted Smart Pointer

Rc<T> is a reference counting smart pointer (this is why it's named Rc), conceptually very similar to C++'s shared_ptr. Note that Rc<T> isn't thread safe - we'll talk about a multithreaded alternative called Arc<T> in chapter 16. Rc<T> is used in the case where we have some data we want to use in multiple places, but we're not sure at compile time who is going to be finished with this data first.

If we model a graph as a collection of edges and nodes, then we might decide that an edge owns the nodes it connects to. But, any node could be connected to by multiple edges, and in Rust any piece of data can only have one owner. What we want want is for a node to be dropped once it's no longer attached to any edges, so we want some kind of shared ownership.

The idea behind Rc<T> is that it allocates some data on the heap and a counter on the heap, and sets that counter to 1. Whenever we make a clone of an Rc<T>, the clone points to the same memory and the same counter, and increments the counter by one. Whenever an Rc<T> is dropped, it decrements the counter by 1 and if the counter is 0 then it can safely free the memory on the heap. Each instance of Rc<T> is only owned by one variable, just like normal Rust ownership rules. Rc<T> is quite a small data structure - really just a pointer - so it is quite inexpensive to copy. The end result is something that looks and behaves a lot lie multiple ownership.

Using Rc<T> to Share Data

Let's see a concrete example. Lets go back to our cons list, but we'll do something slightly unusual, and join three lists together:

src/main.rs
enum List {
Cons(i32, Box<List>),
Nil,
}

use crate::List::{Cons, Nil};

fn main() {
let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
let b = Cons(3, Box::new(a));
let c = Cons(4, Box::new(a)); // This doesn't work!
}

We have list a, and then we make this the tail of both list b and list c. We're essentially trying to create this data structure:

Diagram of Cons list

The problem we're going to run into here is that the Box<T> type owns the value we put in it, so when we create b we move a into a Box<T>. When we try to create c, a has already been moved, so we can't move it again.

We could fix this particular example with some lifetime references, but that won't work in all situations, so instead we'll fix this with Rc<T>:

src/main.rs
enum List {
Cons(i32, Rc<List>),
Nil,
}

use crate::List::{Cons, Nil};
use std::rc::Rc;

fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
let b = Cons(3, Rc::clone(&a));
let c = Cons(4, Rc::clone(&a));

println!("count after creating c = {}", Rc::strong_count(&a));
}

Now instead a is an Rc, and instead of b and c taking ownership of a, they each make a clone of a instead. Each clone increments Rc<T>'s internal reference count by one.

info

We could have called a.clone() instead of Rc::clone(&a) here - these do the same thing. We use Rc::clone for reasons of convention. For most types, a.clone() would perform a deep copy of the value and all of it's data, so a call to a.clone() stands out to the experienced Rust programmer as a potential performance problem. Here we use Rc::clone(&a) instead to signal to the reader "This is OK, we're just cloning an Rc<T>.

We've also shown here that we can get the reference count out of an Rc<T>. Try experimenting with the above code and see what the count is at various points during execution. If you create a scope around c, you can see the reference count decrement when c is dropped. You may have noticed that we're calling Rc::strong_count to get the reference count. If you know what a weak reference is, you'll be unsurprised to learn there's also an Rc::weak_count, which we'll hear about more a little later in this chapter.

Since there are multiple references to the data held by Rc<T>, then by Rust ownership rules, this data is going to be read only - we can't get a mutable reference to it.

15.5 - RefCell<T> and the Interior Mutability Pattern

Suppose for a moment that you're a Rust developer working on a bug in the Rust standard library. You want to keep track of how many times to_lowercase is called on a particular string. No problem, you can add a private field to the String struct called to_lowercase_called and increment it every time someone calls to_lowercase:

pub fn to_lowercase(&self) -> String {
self.to_lowercase_called += 1; // This won't compile.
// --snip--

This would work in most languages, but in Rust to_lowercase borrows an immutable reference to self, so we can't mutate self. And obviously we can't change the signature of to_lowercase without potentially breaking many places where it is called. The problem here is that Rust's concept of "immutability" is getting in the way. You and I know that incrementing a counter like this won't materially affect how this string looks from its outside API, and incrementing this counter won't affect any existing uses of String, but Rust has no way of knowing that.

There's a design pattern in Rust called interior mutability which is designed to overcome this sort of problem. Essentially we have some data structure that as a whole is immutable, but we want to be able to mutate individual parts of it. The primitive that Rust uses to enable this is called an UnsafeCell<T> which basically lets us "opt out" of immutability guarantees for references. Using an UnsafeCell<T> directly involves writing unsafe code (see chapter 19), but most commonly you'd use two "safe" wrappers around UnsafeCell, which are Cell<T> and RefCell<T>.

As a concrete example, here's how you might use a Cell<T> to implement our counter in to_lowercase:

pub struct String {
to_lowercase_called: RefCell<usize>,
// --snip--
}

impl String {
pub fn to_lowercase(&self) -> String {
let old_count = self.to_lowercase_called.get();
self.to_lowercase_called.set(old_count + 1);
// --snip--
}
}

Cell<T> is a smart pointer, a bit like a Box<T> - it stores a single value, and you're allowed to get or set the value contained inside, even if all you have is an immutable reference to the Cell<T>. The problem with Cell<T> is that since you don't have Rust's compile time checks ensuring you only have a single mutable reference at a time, it's easy to write code with a Cell<T> that has race conditions.

RefCell<T> is similar in concept, but it lets us borrow a reference to the value inside by calling the borrow and borrow_mut methods. Those borrows are checked the same way normal borrows are - we can have a single mutable reference to the value inside, or multiple immutable references, but not both at the same time. As is usually the case where unsafe code is involved, RefCell<T> makes some trade offs that we need to be aware of. The difference between a RefCell<T> and normal mutability rules is that RefCell<T> enforces these checks at runtime instead of at compile time - if we try to take two mutable references to the value stored in a RefCell<T>, it will result in a panic instead of a compiler error.

RefCell<T> is implemented using unsafe code, but it bundles it up behind an easy-to-understand API we can use. We say RefCell<T> provides a safe API around unsafe code, which is a common idiom for unsafe code in Rust.

One final note about RefCell<T> is that, like Rc<T>, it is not thread safe.

A Use Case for Interior Mutability: Mock Objects

Let's look at a concrete example. We're writing code for an email server. Users have quotas, and when they get close to that quota, we want to send them a message. The message gets sent via the Messenger trait:

pub trait Messenger {
fn send(&self, msg: &str);
}

How it actually gets sent we don't care. An implementation might send an SMS, or maybe it will - in a fit of irony - send an email and fill up their inbox even more. Here's the code that actually checks the quota:

pub struct LimitTracker<'a, T: Messenger> {
messenger: &'a T,
value: usize,
max: usize,
}

impl<'a, T> LimitTracker<'a, T>
where
T: Messenger,
{
pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> {
LimitTracker {
messenger,
value: 0,
max,
}
}

pub fn set_value(&mut self, value: usize) {
self.value = value;

let percentage_of_max = self.value as f64 / self.max as f64;

if percentage_of_max >= 1.0 {
self.messenger.send("Error: You are over your quota!");
} else if percentage_of_max >= 0.9 {
self.messenger
.send("Urgent warning: You've used up over 90% of your quota!");
} else if percentage_of_max >= 0.75 {
self.messenger
.send("Warning: You've used up over 75% of your quota!");
}
}
}

We want to write a test case for set_value. To do this we'll create a MockMessenger that doesn't actually send a message, but just records all the messages it would have sent. We can create a private Vec<String> to store all these messages for testing purposes. But just like our to_lowercase example above we have a problem: in order to implement the Messenger trait, the send method on our MockMessenger must borrow self immutably, which means we can't mutate our vector. We'll use RefCell<T> to implement the interior mutability pattern here:

#[cfg(test)]
mod tests {
use super::*;
use std::cell::RefCell;

struct MockMessenger {
sent_messages: RefCell<Vec<String>>,
}

impl MockMessenger {
fn new() -> MockMessenger {
MockMessenger {
sent_messages: RefCell::new(vec![]),
}
}
}

impl Messenger for MockMessenger {
fn send(&self, message: &str) {
self.sent_messages.borrow_mut().push(String::from(message));
}
}

#[test]
fn it_sends_an_over_75_percent_warning_message() {
// --snip--

assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);
}
}

Here in MockMessenger::send we're calling into borrow_mut to give us a mutable reference to the vector stored inside the RefCell so we can push values onto that vector, even though we only have an immutable reference to the RefCell. In the test case, we call borrow to get an immutable reference so we can verify a message was sent.

Having Multiple Owners of Mutable Data by Combining Rc<T> and RefCell<T>

Rc<T> lets us have multiple owners, RefCell<T> lets us mutate internal state. We can combine these powers together to make something mutable with multiple owners. Looking back to our cons list example:

src/main.rs
#[derive(Debug)]
enum List {
Cons(Rc<RefCell<i32>>, Rc<List>),
Nil,
}

use crate::List::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;

fn main() {
let value = Rc::new(RefCell::new(5));

let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));

let b = Cons(Rc::new(RefCell::new(3)), Rc::clone(&a));
let c = Cons(Rc::new(RefCell::new(4)), Rc::clone(&a));

// We can modify the value at the end of the list,
// even though there are multiple references
// to it.
*value.borrow_mut() += 10;

println!("a after = {:?}", a);
println!("b after = {:?}", b);
println!("c after = {:?}", c);
}

15.6 - Reference Cycles Can Leak Memory

In C it's easy to create a memory leak; just malloc some memory and never free it. In a language like Rust it's not so simple, but it can definitely be done. One way to do it is with Rc<T>, RefCell<T>, and a circular reference. The problem is that Rc<T> uses a simple reference count to know when memory is safe to free, but if we have two Rc<T>s that point to each other, then even with no one else referencing them, they'll both have a reference count of 1.

In a garbage collected language like Java or JavaScript, this problem is solved using reachability. The two values are reachable from each other, but neither is reachable from the root set. We have no garbage collector in Rust, and Rc<T> is simply not smart enough to get out of this situation on its own, so we leak memory.

Creating a Reference Cycle

Let's look at a slightly modified version of our cons list example, where the pointer to the next item in the list is mutable via RefCell<T>. We then set up two lists elements which each have a next pointing to each other:

src/main.rs
#[derive(Debug)]
enum List {
Cons(i32, RefCell<Rc<List>>),
Nil,
}

use crate::List::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;

impl List {
fn tail(&self) -> Option<&RefCell<Rc<List>>> {
match self {
Cons(_, item) => Some(item),
Nil => None,
}
}
}

fn main() {
// Create `a` which represents the list `[5]`.
let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));
// Create `b` which represents the list `[10, 5]`
let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));

// Set a's `next` to be `b`. `a` is now the list `[5, 10, 5, 10, 5, 10...]`.
if let Some(link) = a.tail() {
*link.borrow_mut() = Rc::clone(&b);
}

// These will both be 2, because `a` and `b` are refs to these values,
// (which is the first count) and they also point to each other
// (which is the second).
println!("b rc count after changing a = {}", Rc::strong_count(&b));
println!("a rc count after changing a = {}", Rc::strong_count(&a));

// Uncomment the next line to see that we have a cycle;
// it will overflow the stack
// println!("a next item = {:?}", a.tail());
}

Have a quick read through that example and you'll see that both a and b end up pointing to each other. Both a and b end up with a strong_count of 2. When we hit the end of the main function, a will be dropped, reducing the ref count for a's Rc<List> to 1 (the one from b), and the same will happen to b. As a result, even though there are no more Rc objects left using this memory, the count is never reduced to zero and the memory will never be freed.

Preventing Reference Cycles: Turning an Rc<T> into a Weak<T>

One way to solve the problem we presented in the previous section is to make it so some of these pointers confer ownership semantics and some do not. It doesn't lend itself well to the example we just used, so we're going to use a new example here, using a tree data structure. We're going to have Nodes that have a mutable list of references to their children, and each child will have a reference to the parent. This structure is full of circular references: a parent node points to each child, and each child points back to the parent.

To prevent a possible memory leak, here we'll make the parent references strong and the child references weak. In other words, if a child has a reference to a parent, that reference won't count towards the reference count that Rc<T> uses:

src/main.rs
use std::cell::RefCell;
use std::rc::{Rc, Weak};

#[derive(Debug)]
struct Node {
value: i32,
parent: RefCell<Weak<Node>>,
children: RefCell<Vec<Rc<Node>>>,
}

fn main() {
// Create a leaf node
let leaf = Rc::new(Node {
value: 3,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![]),
});

println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());

// Create a parent for the leaf node
let branch = Rc::new(Node {
value: 5,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![Rc::clone(&leaf)]),
});

// Wire up `leaf`'s parent pointer
*leaf.parent.borrow_mut() = Rc::downgrade(&branch);

println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
}

We already know that calling rc::clone will increment the strong_count for that Rc and return back a new Rc that points to the same memory. Here Rc::downgrade works the same way, except instead of returning an Rc<Node> it returns a Weak<Node> and instead of incrementing strong_count it increments weak_count. When an Rc is dropped, if the strong_count is decremented to 0 the underlying memory will be freed, even if the weak_count is still positive.

This means that whenever we want to deference a Weak<Node>, we have to check that there's still something in there, and the underlying memory hasn't been freed. We do this by calling Weak::upgrade on the Weak<Node>, which will return an Option<Rc<Node>>. If the underlying memory hasn't been cleaned up yet, then Weak::upgrade returns a Some<Rc<Node>> (the new Rc<Node> increments the strong_count, as you might expect) and if not, it returns a None to let you know your weak reference isn't valid anymore.

Since the relationship from child-to-parent is weak, if we drop a parent, it's strong_count will drop to 0, and the entire tree will end up being freed. No more leaks!

Continue to chapter 16.

- + \ No newline at end of file diff --git a/ch16-fearless-concurrency/index.html b/ch16-fearless-concurrency/index.html index 88dffc6..8042cfd 100644 --- a/ch16-fearless-concurrency/index.html +++ b/ch16-fearless-concurrency/index.html @@ -4,13 +4,13 @@ 16 - Fearless Concurrency | The rs Book - +

16 - Fearless Concurrency

Concurrent programming has a lot of potential pit falls - race conditions, thread safe access to variables - in other languages these problems show up in production as tricky to reproduce problems. Access to memory is handled through Rust's type system and ownership rules, and it turns out these rules can do an excellent job of catching many concurrency problems at compile time too.

Throughout this chapter we'll use the term concurrent, but we really mean concurrent or parallel (concurrent meaning multiple things happening on a single CPU, and parallel meaning multiple things happening on many CPUs). A lot of what we discuss in this chapter applies to both multithreaded code and async code. We'll talk a little bit about async code in chapter 21.

Different languages tend to use different abstractions to deal with thread safety, each with their own strengths and weaknesses. For example, Java makes use of synchronized blocks which take ownership of a monitor (very similar to a mutex), and your most frequent headaches in Java are deadlocks. In Go, concurrency is most frequently handled using message passing over a channel, and while deadlocks are thus rare in Go, goroutine leaks (where a goroutine is started but never terminates) are frighteningly common. Rust doesn't dictate a solution to you: Rust has both channels and mutexes and you can use whichever is a better fit for your particular problem.

Threads for JavaScript Programmers

This book is intended to people who already know another language, and so we're skipping a lot of beginner concepts. However, JavaScript is one of the most popular languages in the world, and it doesn't deal much with threads, so we'll briefly cover some thread concepts here. If you know what "thread" and "mutex" mean, feel free to skip ahead to the next section. If not, this is far from a complete introduction to threads, but it will at least introduce the terminology you need to make it through this chapter.

If you've used JavaScript much, you know that JavaScript has an event loop. In node.js, if you call fs.readFile(filename, {encoding: "utf-8"}, cb), then node will ask the OS to open filename and read it's contents, and once that data is available node will call into your callback. The actual reading of the file may or may not happen in some other thread, but your code all executes in a single thread, inside the event loop. Because of this, a calculation-heavy JavaScript program has a hard time making use of multiple CPUs. We say that JavaScript code can do a lot of things concurrently, but not so much in parallel. (At least, without using web workers.)

You probably already know that spawning a thread is a bit like starting a second program running. You can run two threads in parallel on two different CPUs. The biggest difference between running two separate programs and running two threads is that the threads can read and write the same memory.

When two threads want to read and write to the same memory, we need some way to synchronize access to that memory. If one thread is writing part of a data structure while another is reading it, then the reader might get a partially updated data structure, or one with pointers to data that has been allocated but hasn't been initialized yet. These introduce bugs in our program called race conditions.

One of the most common ways to synchronize access is called a mutex (short for "mutual exclusion"). Whenever a thread wants to read or write to shared memory, it locks the mutex. Only one thread is allowed to own the lock on the mutex at a time, so if a second thread tries to lock the mutex, it will block until the mutex is available.

Sometimes a thread needs to access two different parts of memory, protected by two different mutexes. If a first thread tries to lock mutex a and then mutex b, and a second thread tries to lock mutex b and then mutex a, it's possible that the first thread will end up with a and get stuck waiting for b, while the second thread ends up with b and waits for a. This is called a deadlock and both threads will end up waiting forever.

In this chapter we're going to talk about how to spawn threads, how to use mutexes to synchronize access, and we'll talk about channels which are a way for threads to pass messages back and forth to each other (if you are familiar with web workers, this is a little bit like postMessage).

16.1 - Using Threads to Run Code Simultaneously

If you're coming from a language like Go or Erlang, it's important to realize that threads in Rust are real, actual, OS level threads. In Go, goroutines are "green threads", where multiple goroutines map to a single OS thread. In Rust, each thread is bare metal OS thread (although, it's worth noting that there are crates that implement other models of threading).

In chapter 21 we'll talk about async programming where many I/O bound tasks (like many incoming requests to a web server) can be handled in parallel with a small number of threads, but in this chapter we're talking about plain old vanilla threads.

Creating a New Thread with spawn

We start a new thread by calling thread::spawn, passing it a closure which will be run in the new thread:

src/main.rs
use std::thread;
use std::time::Duration;

fn main() {
let handle = thread::spawn(|| {
for i in 1..10 {
println!("hi number {} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
}
});

for i in 1..5 {
println!("hi number {} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}

handle.join().unwrap();
}

If you run this, then as you'd expect, you'll get a mix of messages from the main thread and the spawned thread intermixed with each other.

If you comment out the call to join at the end of the main function and run this, you probably will not see all 9 numbers from the spawned thread being printed. If the main thread quits, all child threads quit immediately. Rust doesn't wait for all threads to finish to quit the program, as some languages such as Java do. The join function on ThreadHandle will cause the calling thread to wait until the thread the handle references has terminated.

Using move Closures with Threads

Here's an example that doesn't compile because of an ownership problem:

src/main.rs
use std::thread;

fn main() {
let v = vec![1, 2, 3];

// This doesn't work!
let handle = thread::spawn(|| {
println!("Here's a vector: {:?}", v);
});

drop(v);

handle.join().unwrap();
}

Can you spot the problem? If you need a hint, notice that v gets declared in the main function, and we reference v inside the closure. As you might recall from chapter 13, Rust is going to infer that, since we're only reading from v inside the closure, we can borrow a reference to v.

The problem here is that immediately after spawning the thread, the main function drops v. This means the memory associated with the vector is going to be freed, possibly before the thread has even had a chance to run. The Rust compiler throws an error here, because the thread might not have a valid reference to v when it needs it. There's no way for the compiler to know.

More formally, from a type safety perspective, the closure passed to thread::spawn has the trait bounds F: FnOnce() -> T + Send + 'static. The closure has a static lifetime, which may or may not overlap the lifetime of the vector, so the closure can't borrow from the vector.

One way to fix this is with the move keyword, which forces the closure to take ownership of v:

    let handle = thread::spawn(move || {
println!("Here's a vector: {:?}", v);
});

Scoped Threads

Another way to solve the example above is with thread::scope:

src/main.rs
use std::thread;

fn main() {
let v = vec![1, 2, 3];
let mut i = 32;

let scope = thread::scope(|s| {
s.spawn(|| {
// Can borrow from the outer scope.
println!("Here's a vector: {:?}", v);
});

s.spawn(|| {
// Can even mutably borrow, so long as
// no other threads borrow same variable.
i = 7;
});
});
}

thread::scope passes a Scope object to the passed in closure, which can be used to spawn new threads. All threads spawned this way are joined before thread::scope returns, so their lifetime is known. These threads can therefore borrow from the enclosing function. If any individual thread panics, then thread::scope will panic as well.

16.2 - Using Message Passing to Transfer Data Between Threads

Rust has channels, which will be very familiar to any Go programmers reading this. A channel is a bit like a FIFO queue - a producer can transmit a message to the channel, and a consumer can receive a message from a channel. Let's see an example:

src/main.rs
use std::sync::mpsc;
use std::thread;

fn main() {
let (tx, rx) = mpsc::channel();

thread::spawn(move || {
let val = String::from("hi");
tx.send(val).unwrap();
});

let received = rx.recv().unwrap();
println!("Got: {}", received);
}

mpsc here stands for "multiple producers, single consumer", because this is how the channels from the standard library are implemented. The call to mpsc::channel returns a Sender<String> and Receiver<String> in a tuple, which we're assigning to tx and rx via a destructuring assignment.

As we saw in previous examples, we use move to move ownership of the Sender to the spawned thread. The thread needs to own the Sender in order to use it, which it does by calling tx.send. It's important to realize that send doesn't make a copy of the message being passed in, it takes ownership of the value and moves it to the receiving side. In effect the only thing being sent from sender to receiver is a pointer.

send will return an error if the receiver has already been dropped and there's nowhere to send this message. Here we're calling unwrap instead of properly handling the error since this is just an example.

On the receiving side, we call rx.recv to receive a value from the channel. This will block until a message is available. Like send, this will return an error if the transmitter has already been dropped (otherwise we could end up blocked waiting for a message forever). If we don't want the receiver to block, there's also a rx.try_recv which will return an error immediately if the channel has no messages for us. It's a fairly common pattern for the main thread to periodically check to see if there are messages available, and if not go do some other work and come back later.

Sending Multiple Values and Seeing the Receiver Waiting

The Receiver<T> type implements the Iterator trait, which lets us handle incoming messages with a for loop or using other Iterator functions we've seen:

src/main.rs
use std::sync::mpsc;
use std::thread;
use std::time::Duration;

fn main() {
let (tx, rx) = mpsc::channel();

thread::spawn(move || {
let vals = vec![
String::from("hi"),
String::from("from"),
String::from("the"),
String::from("thread"),
];

for val in vals {
tx.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});

for received in rx {
println!("Got: {}", received);
}
}

Notice that the sending thread is sleeping between sending each value. The for received in rx loop here will call recv, so it will block and wait for each message.

16.3 - Shared-State Concurrency

Using Mutexes to Allow Access to Data from One Thread at a Time

First let's have a look at a single threaded program that uses a Mutex<T>:

src/main.rs
use std::sync::Mutex;

fn main() {
let m = Mutex::new(5);

{
let mut num = m.lock().unwrap();
*num = 6;
}

println!("m = {:?}", m);
}

We acquire the lock with m.lock().unwrap(). If you're wondering what that unwrap is about: if a thread panics which it holds the mutex, then Rust marks this mutex as being poisoned. The call to lock actually returns a LockResult<T> which, in the case of a poisoned mutex, will be an Err. Generally here you would just call unwrap as we have done above, which would propagate the panic since, after a panic, you will have a hard time guaranteeing that anything is in a reasonable state. However you can acquire the lock from the poisoned mutex if you wish.

After we unwrap the LockResult, the num value here is bound to a MutexGuard. When the MutexGuard is dropped at the end of our inner scope, the mutex's lock will be released.

You'll notice that the mutex holds on to the value it's protecting inside of itself. Mutex<T> is basically another kind of smart pointer, or more accurately the MutexGuard is. MutexGuard implements Deref so we can use it like a reference to update the value inside.

Mutexes in rust are not reentrant - if you hold a lock and try to acquire it a second time, this will result in a deadlock. Rust's fancy type and ownership system can protect us from many common thread safety issues, but deadlocks aren't one of them, and trying to acquire the two locks from two threads in a different order will also result in a deadlock.

Sharing a Mutex<T> Between Multiple Threads

A Mutex on only one thread isn't very useful. If you recall back to how we shared a single Sender between multiple threads in our channels example, you might expect us to clone the mutex to pass it between multiple threads, but you'd be wrong - Mutex<T> doesn't implement Clone. We saw in chapter 15 how we could use the Rc<T> smart pointer to have a single piece of data with multiple owners, but we noted it isn't thread safe, so we can't use it here.

But there's a thread-safe version of Rc<T> called Arc<T> - the "atomic reference counted" smart pointer. Arc<T> uses the std::sync::atomic library to atomically increment and decrement it's reference count, which makes it thread safe. Why doesn't Rc<T> use the atomic library? How come everything isn't thread safe? Because thread safety comes with a performance penalty here, so Rc<T> is there when you don't need thread safety and Arc<T> is there for when you do.

Here's how we share a Mutex<T> with an Arc<T>:

src/main.rs
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];

for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();

*num += 1;
});
handles.push(handle);
}

for handle in handles {
handle.join().unwrap();
}

println!("Result: {}", *counter.lock().unwrap());
}

Very similar to our channels example, we clone the Arc whenever we want to move it into a new thread (so you were right - there was a clone in there somewhere). Note that we're still not cloning the Mutex<T> though. The Arc keeps ownership of the Mutex, allowing us to share a single mutex across multiple threads.

Notice that the counter variable above is declared as immutable. Much like with RefCell<T>, a Mutex<T> provides interior mutability, and we're allowed to change the value held in a mutex even if we have an immutable reference to the mutex.

16.4 - Extensible Concurrency with the Sync and Send traits

Send is a marker trait that indicates a type can be transferred between threads. Any type composed entirely of Send traits is automatically Send. Almost every type in Rust is Send, but we've already seen an example of one that isn't: Rc<T> isn't Send, since if you cloned it and transferred it between threads, the clone and the original might try to modify the reference count concurrently resulting in a data race. If you give this a try, you'll get a compile-time error, since Rc<T> is not Send. Raw pointers in rust (see chapter 19) are also not Send.

The closely related Sync marker trait is implemented by types that are safe to be referenced from multiple threads. To put it formally, a type T is Sync if &T is Send. If we can send an immutable reference to T to another thread, then T is Sync. RefCell<T> is not Sync. Mutex<T> is Sync which is why we can share an immutable reference across threads, as we did with Arc<T> in the example above.

In general we never have to implement Send and Sync on a type ourselves. They are just marker traits with no methods, and they're implemented automatically on a type if that type is composed entirely of Send/Sync members. The only cases where you'd want to implement these yourself is if you're creating new concurrency primitives, in which case you'll be using some unsafe code (see chapter 19). There are many safety guarantees you'll have to implement yourself if you're going down this path, and you should consult The Rustonomicon if you want to learn about this.

What's interesting to note here is that, aside from the Sync and Send traits, everything we've looked at in this chapter is implemented in the standard library instead of being part of the core Rust language. Many concurrency solutions are implemented in crates (such as the popular parking_lot crate). When we dig into async programming in chapter 21 we'll see the same thing there, with Rust providing the async and await keywords, but with the actual runtime behavior being provided by a crate.

Continue to chapter 17.

- + \ No newline at end of file diff --git a/ch17-object-oriented-features/index.html b/ch17-object-oriented-features/index.html index 45adac7..6b14d01 100644 --- a/ch17-object-oriented-features/index.html +++ b/ch17-object-oriented-features/index.html @@ -4,13 +4,13 @@ 17 - Object Oriented Features of Rust | The rs Book - +

17 - Object Oriented Features of Rust

17.1 - Characteristics of Object Oriented Languages

What makes an Object Oriented language? There are many different definitions, but generally an OO language has the following concepts:

  • objects package code (methods) together with data.
  • encapsulation means that some of the methods and data on an object are private, and some are public. The internal implementation of an object can change without the public API or any users of that object changing.
  • inheritance allows one object to inherit data and methods from a parent object or class.
  • polymorphism allows two objects with the same public API to be used in place of each other.

It's easy to see how Rust borrows from these concepts and can be used as an OO language. structs in Rust have data and can have methods defined on them and so are similar to objects. Members and methods of a struct can be pub or private (privacy in Rust is a little different than in other OO languages, but that's something that can be said of many OO languages).

Rust doesn't really have inheritance. But inheritance has fallen out of style in modern software design, often being replaced with composition instead, and traits allow us to provide default implementations for methods which allows a lot of the same sort of code reuse that inheritance traditionally gives us.

Using traits we can easily implement polymorphism in Rust, and we've already seen some examples of this; the Iterator trait allows us to pass any number of different types of objects to a for loop, for example. Trait objects let us take this a step further.

17.2 - Using Trait Objects That Allow for Values of Different Types

In chapter 8 we mentioned that a vector can only hold one type. We showed a workaround where we stored a SpreadsheetCell enum in a vector, and then used different variants of the enum to store different types. But let's suppose we were implementing a GUI library. We might want a vector of "components" we need to draw on the screen - buttons, select boxes, links, etc... We could use the enum trick here to represent all these different component types, but a common feature of GUI libraries is letting users define their own custom components. We can't possibly know all the custom component types ahead of time, so here an enum is going to let us down.

To do this in a class based language we might define a Component abstract class with a draw method, and then various subclasses of Component could implement draw differently. In Rust we don't have inheritance, so to do this we'll have to use traits.

Defining a Trait for Common Behavior

At runtime, a trait object will be a pair of pointers in memory - one to the instance of a specific type that implements our trait, and another to a table of methods defined on the trait to call at runtime (similar to a v-table in C++). We create a trait object by specifying a pointer (this could be a simple reference or a smart pointer like a Box<T>) and the dyn keyword (for "dynamic"). In chapter 19 we'll talk about the Sized trait, and why a pointer is required here.

Fo our GUI library, we'll create a Draw trait, with a single method called draw. Instead of storing a vector of buttons or a vector of dialogs, we'll store a vector of trait objects:

src/lib.rs
pub trait Draw {
fn draw(&self);
}

pub struct Screen {
pub components: Vec<Box<dyn Draw>>,
}

impl Screen {
pub fn run(&self) {
for component in self.components.iter() {
component.draw();
}
}
}

pub struct Button {
pub width: u32,
pub height: u32,
pub label: String,
}

impl Draw for Button {
fn draw(&self) {
// code to actually draw a button
}
}

The Draw trait should look familiar - if you skipped ahead in this book and this syntax looks unfamiliar, then see chapter 10.

The Screen struct has a components which has some new syntax: it is a vector of Box<dyn Draw>, or in other words a vector of trait objects that implement the Draw trait. Box<dyn Draw> is a stand-in for any type inside a Box that implements Draw. Screen also has a run method which calls the draw method on each member of components.

It's important to note that a trait object is very different from a trait bound. If we'd implemented Screen as a generic type with a trait bound:

// Generic version with trait bounds won't work.
pub struct Screen<T: Draw> {
pub components: Vec<T>,
}

impl<T> Screen<T>
where
T: Draw,
{
pub fn run(&self) {
for component in self.components.iter() {
component.draw();
}
}
}

then we could create a Screen<Button> or a Screen<SelectBox>, but any such screen would only be allowed to draw a single type. A screen that is only allowed to show buttons is not a very useful screen! By using a trait object here, we can have a collection of different types, and we don't have to know all the possible implementations at compile time. (Although, note that if you need a collection all of the same type, then this solution would be preferable as we can work out how to call draw on a concrete type at compile time instead of having to do this at runtime.)

We also added a Button type to our library that implements the Draw trait. If this were a real library it would probably implement more than just a Button, but that's enough for this example.

Let's look at a crate that uses this library in src/main.rs:

src/main.rs
use gui::{Button, Screen};

// Define a custom SelectBox component, not
// in the gui library.
struct SelectBox {
width: u32,
height: u32,
options: Vec<String>,
}

impl Draw for SelectBox {
fn draw(&self) {
// code to actually draw a select box
}
}

fn main() {
let screen = Screen {
components: vec![
Box::new(SelectBox {
width: 75,
height: 10,
options: vec![
String::from("Yes"),
String::from("Maybe"),
String::from("No"),
],
}),
Box::new(Button {
width: 50,
height: 10,
label: String::from("OK"),
}),
],
};

screen.run();
}

The user of our library has defined their own custom SelectBox component, and created a Screen with a SelectBox and a Button.

Trait Objects Perform Dynamic Dispatch

When we call into component.draw() in Screen, what actually happens at runtime is called dynamic dispatch. We have a pointer to the memory for the struct, and a pointer to a table of methods (in this case, just draw). For each trait object in the components list, we're going to use that table to figure out the correct draw function to call at runtime, which will depend on the underlying concrete type.

It's important to realize that this is very different from this example:

let button = Button {
width: 50,
height: 10,
label: String::from("OK"),
};

button.draw();

Here, the compiler knows that button is of type Button, and can work out which draw function to call at compile time. This is called static dispatch.

There's a small performance impact to dynamic dispatch, since we have this extra pointer to follow at runtime. Also, in the static dispatch case we can do performance optimizations like inlining which are not available in the dynamic dispatch case.

17.3 - Implementing an Object-Oriented Design Pattern

In this section we're going to implement a simple blogging server. A post on the server can be in one of three states: when first created a post will be a "draft". Once the user is done creating the draft, they can ask for a review which will move the post to the "review" state. Finally once reviewed, the post will move to the "published" state. We want to make sure the text for a post isn't published to our blog site until the post is in the published state.

This is a pretty simple example, and I'm sure you could easily imagine implementing this with a state enum and some methods on the Post, but since this is a chapter about OO design, we'll represent the state of the post using the state pattern, one of the original twenty-three design patterns documented by the Gang of Four. (We're going to actually implement this twice - once using the OO pattern, and once in a way that's a bit more natural for Rust.) You can find the finished code for this example in this book's github repo.

As Wikipedia puts it:

The state pattern is set to solve two main problems:

  • An object should change its behavior when its internal state changes.
  • State-specific behavior should be defined independently. That is, adding new states should not affect the behavior of existing states.

In src/lib.rs, let's write a quick unit test to walk through what our API and workflow will look like:

src/lib.rs
#[cfg(test)]
mod tests {
use super::*;

#[test]
fn post_workflow() {
let mut post = Post::new();

post.add_text("I ate a salad for lunch today");
assert_eq!("", post.content());

post.request_review();
assert_eq!("", post.content());

post.approve();
assert_eq!("I ate a salad for lunch today", post.content());
}
}

Notice that our public API doesn't know anything about our state pattern. The idea here will be that Post has a state which will be a state object. There will be a Draft struct, a PendingReview struct, and a Published struct that represent our different states and all are going to implement the State trait. When you call into a method on Post like post.request_review(), this method will delegate to the current state by doing roughly the equivalent of this.state = this.state.request_review(), so the state can control what the next state will be.

Here's the implementation of Post, also in src/lib.rs:

src/lib.rs
pub struct Post {
state: Option<Box<dyn State>>,
content: String,
}

trait State {
fn request_review(self: Box<Self>) -> Box<dyn State>;
fn approve(self: Box<Self>) -> Box<dyn State>;
fn content<'a>(&self, _post: &'a Post) -> &'a str {
""
}
}

impl Post {
pub fn new() -> Post {
Post {
state: Some(Box::new(Draft {})),
content: String::new(),
}
}

pub fn add_text(&mut self, text: &str) {
self.content.push_str(text);
}

pub fn request_review(&mut self) {
if let Some(s) = self.state.take() {
self.state = Some(s.request_review())
}
}

pub fn approve(&mut self) {
if let Some(s) = self.state.take() {
self.state = Some(s.approve())
}
}

pub fn content(&self) -> &str {
self.state.as_ref().unwrap().content(self)
}
}

The State trait has request_review, approve, and content methods. State has a default implementation of content, so not all the implementors have to reimplement it, but the other methods will need to be implemented by each State individually. The request_review and approve methods on State take a self: Box<Self> as their first parameter. This means this method will only be available on a Box<T> holding the type. This also takes ownership of the Box, effectively invalidating the previous state.

Post has some state and some content, both of which are private. We could have made content public, but we want to make the content of a Post hidden until the post is in the published state, so we created a content getter method which delegates to the current state.

Post's constructor creates a new Draft state, since this is the state we want to start out in. Since the state field is private, we can't create a Post in any other state. Post's state is an optional trait object of type Option<Box<dyn State>>. We'll talk about why it's an Option in just a second.

The add_text method takes a mutable reference to self, since it modifies the content of the post. Notice it doesn't interact with the state at all, because no matter what state a post is in, we want to be able to add text. All the other methods on Post delegate to the current state. The request_review and approve methods look very similar - they call into the current state to get the new state, and set self.state. But... they're also a little wordy:

    pub fn request_review(&mut self) {
if let Some(s) = self.state.take() {
self.state = Some(s.request_review())
}
}

Why not just self.state = self.state.request_review() here? The problem here is that self.state.request_review() would try to take ownership of self.state, but you can't take ownership of a part of a struct (at least, not without invalidating the struct). To get around this we make self.state an Option, and then self.state.take() will take ownership of the value in the Option and replace it with None temporarily. Since after the take we immediately reassign it, it's never None for more than an instant.

The content method also needs to deal with the fact that self.state is an Option:

    pub fn content(&self) -> &str {
self.state.as_ref().unwrap().content(self)
}

We call as_ref on the Option to convert the owned value into a ref, because we don't want to take ownership of the Box<dyn state> in the Option. (self.state is an immutable reference, so we can't take ownership of the value inside the Option even if we wanted to, since self, and by extension self.option are both immutable in this context.) We call unwrap() because we know that self.state will always contain a value. This is one of those examples of us knowing more than the compiler - we know self.state wil never be None here, so we can just panic instead of trying to deal with the case where it's None. After the unwrap we have a &Box<dyn State>, so deref coercion will take place until we ultimately call content on the current State's implementation.

Let's have a look at the Draft state:

src/lib.rs
struct Draft {}

impl State for Draft {
fn request_review(self: Box<Self>) -> Box<dyn State> {
Box::new(PendingReview {})
}

fn approve(self: Box<Self>) -> Box<dyn State> {
self
}
}

Calling request_review returns a new PendingReview state, effectively advancing us to some new state. Calling approve just returns self. No one should be trying to approve a Post in the Draft state, but if they do we want to just leave ourselves in the Draft state.

Here are our final two states:

src/lib.rs
struct PendingReview {}

impl State for PendingReview {
fn request_review(self: Box<Self>) -> Box<dyn State> {
self
}

fn approve(self: Box<Self>) -> Box<dyn State> {
Box::new(Published {})
}
}

struct Published {}

impl State for Published {
fn request_review(self: Box<Self>) -> Box<dyn State> {
self
}

fn approve(self: Box<Self>) -> Box<dyn State> {
self
}

fn content<'a>(&self, post: &'a Post) -> &'a str {
&post.content
}
}

The only thing interesting here is that Published overrides the default implementation of the content method. We need lifetime annotations on this method, since the returned reference will only be valid as long as the passed in Post.

Trade-offs of the State Pattern

This program certainly met the conditions we set out when attempting to use the state pattern. Behavior is controlled by each state, and we could add a new state without modifying the Post object at all. States are dependant on each other - the PendingReview state creates a Published state, for example - but this isn't terrible.

If we were to implement this instead with an enum for state and match statements in each of the methods on Post, it would be quite manageable for this small example with only three states, but it does mean we'd have to look in many different places to understand what being in the "published" state actually means, and adding a new state would involve updating potentially many different match expressions.

Here are a few exercises that were suggested from the original "The Rust Programming Language":

  • Add a reject method that changes the post's state from PendingReview back to Draft.
  • Require two calls to approve before the state can be changed to Published.
  • Allow users to add text content only when a post is in the Draft state. Hint: have the state object responsible for what might change about the content but not responsible for modifying the Post.

One downside here is that we have a lot of duplicated logic. The request_review and approve methods on post look almost identical to each other. These methods on the State implementors often just return self, and it would be nice to add default methods for these to the trait, but unfortunately we can't because the return value of a method needs to know the size of what it is returning, and in the trait we don't know what size the concrete implementation of State will be. Once solution here might be to define a macro (see chapter 19) to reduce the code repetition here.

The fact that Post.state needs to be wrapped in an Option makes parts of this implementation feel clumsy and unergonomic.

info

One way you might think to try to improve this code is to change the signature of the request_review method in the State trait to accept a mutable reference to Post:

trait State {
fn request_review(&self, _post: &mut Post) {}
fn approve(&self, _post: &mut Post) {}
fn content<'a>(&self, _post: &'a Post) -> &'a str {
""
}
}

We can just assign post.self inside approve and request_review on the State objects. Then we could convert Post.state into a Box<dyn State> and get rid of the Option which should simplify this a lot.

I encourage you to give this a try, but you may find ownership rules will make this more complicated than you think it will be.

Encoding States and Behavior as Types

Let's take a look at another way of implementing the same behavior, but we're not going to implement it in exactly the same way we would in a traditional OO language. We're going to instead try to encode our state and associated behavior as explicit types. You can find the finished code for this example in this book's github repo. First let's create a Post and a DraftPost:

src/lib.rs
pub struct Post {
content: String,
}

pub struct DraftPost {
content: String,
}

impl Post {
pub fn new() -> DraftPost {
DraftPost {
content: String::new(),
}
}

pub fn content(&self) -> &str {
&self.content
}
}

impl DraftPost {
pub fn add_text(&mut self, text: &str) {
self.content.push_str(text);
}
}

We can still call Post::new, but this now returns a new DraftPost type. DraftPost doesn't even implement content, so we can't even ask for the content of a DraftPost without generating a compile time error. This is an example of "making invalid state unrepresentable" - we don't want to let you get the content of a draft post, and now it's impossible to even write the code that would do such a thing. We want to be able to request a review on our DraftPost, so let's add a method for that:

src/lib.rs
// --snip--

impl DraftPost {
// --snip--

pub fn request_review(self) -> PendingReviewPost {
PendingReviewPost {
content: self.content,
}
}
}

pub struct PendingReviewPost {
content: String,
}

impl PendingReviewPost {
pub fn approve(self) -> Post {
Post {
content: self.content,
}
}
}

Now we have our three states - DraftPost, PendingReviewPost, and Post - encoded as types. The request_review method on DraftPost takes ownership of self. After calling it, the DraftPost will be dropped. This means this method converts a DraftPost into a PendingReviewPost, and there's no way for us to have any lingering DraftPost left over. Importantly this means there's no way to accidentally request two reviews for the same draft post without getting a compiler error. Also, the only way to get the content of a post is to have the full Post object, and the only way to get such an object is to call approve on a PendingReviewPost, so we know we can never get access to the content of a post if it hasn't been approved. All of our behavior is encoded into these types.

We can write a test case to see this in action:

src/lib.rs
#[cfg(test)]
mod tests {
use super::*;

#[test]
fn post_workflow() {
let mut post = Post::new();

post.add_text("I ate a salad for lunch today");

let post = post.request_review();

let post = post.approve();
assert_eq!("I ate a salad for lunch today", post.content());
}
}

This isn't better in every way than the previous example, rather there are trade offs here that are different. In this test case, you can see that whenever a post changes state, we have used variable shadowing to create a new variable with a new type. If we change our internal implementation so that add_text transitioned to a new state, then the caller here would break, so our implementation is not nearly as encapsulated as it was before. It's also a little more challenging in this model to create a vector of "posts" - we'd have to wrap these different post states in an enum or in some common trait and use a trait object to store them in a vector together, and both of those would "undo" some of the benefits we've just outlined in different ways.

Which of these tradeoffs you make are going to depend heavily on what you're trying to implement, but hopefully this chapter has given you some new tools to use to approach different problems.

Continue to chapter 18.

- + \ No newline at end of file diff --git a/ch18-patterns-and-matching/index.html b/ch18-patterns-and-matching/index.html index 1e0f678..ba05c33 100644 --- a/ch18-patterns-and-matching/index.html +++ b/ch18-patterns-and-matching/index.html @@ -4,13 +4,13 @@ 18 - Patterns and Matching | The rs Book - +

18 - Patterns and Matching

18.1 - All the Places Patterns Can Be Used

We've already talked about using patterns in match and if let expressions, but actually patterns are everywhere in Rust. A destructuring assignment is actually an example of a pattern.

match Arms

As seen in chapter 6, the arms of a match use patterns:

match VALUE {
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
}

The patterns in a match need to be exhaustive - they need to cover every possibility. The _ pattern will match anything and not bind to a variable, so it will often be used as a catch-all at the end of a match.

In this example, we extract a value from a Some. Note that value we extract in this example will shadow the outer variable:

match i {
None => None,
Some(i) => Some(i + 1),
}

Conditional if let Expressions

In chapter 6 we also so how to use an if let expression. These use patterns just like a match, but they don't have to be exhaustive (which can be an advantage or a disadvantage, depending on the situation) and we can mix patterns with different values. This example uses a number of different inputs to decide what color to use as a background color:

fn main() {
let favorite_color: Option<&str> = None;
let is_tuesday = false;
let age: Result<u8, _> = "34".parse();

if let Some(color) = favorite_color {
println!("Using your favorite color, {color}, as the background");
} else if is_tuesday {
println!("Tuesday is green day!");
} else if let Ok(age) = age {
if age > 30 {
println!("Using purple as the background color");
} else {
println!("Using orange as the background color");
}
} else {
println!("Using blue as the background color");
}
}

Note in this example we used let Ok(age) = age to create a shadowed variable for age, similar to what we did in our match example above.

while let Conditional Loops

We can create a while let loop, which is very similar to the if let syntax:

    let mut stack = Vec::new();

stack.push(1);
stack.push(2);
stack.push(3);

// This prints 3, 2, then 1. When `pop`
// returns `None`, this loop will stop.
while let Some(top) = stack.pop() {
println!("{}", top);
}

for Loops

In a for loop, the bit immediately after the for keyword is actually a pattern! We can use this to destructure values from an iterator:

    let v = vec!['a', 'b', 'c'];

for (index, value) in v.iter().enumerate() {
println!("{} is at index {}", value, index);
}

let statements

Simple let statements use patterns too:

let x = 5;

x here is a pattern, albeit a very boring one. Using it here is similar to using x as a pattern in a match. The fact that this is a pattern is what makes it possible to do destructuring assignment in Rust:

let (x, y, z) = (1, 2, 3);

Function and Closure Parameters

Similar to let, the parameters of a function are also patterns. We can use this to destructure a tuple or struct in a function declaration:

fn print_coordinates(&(x, y): &(i32, i32)) {
println!("Current location: ({}, {})", x, y);
}

fn main() {
let point = (3, 5);
print_coordinates(&point);
}

The matches! Macro

Rust provides a handy macro that can be used to check if a value matches a specific pattern:

let foo = 'f';
assert!(matches!(foo, 'A'..='Z' | 'a'..='z'));

let bar = Some(4);
assert!(matches!(bar, Some(x) if x % 2 == 0));

18.2 - Refutability: Whether a Pattern Might Fail to Match

In this example:

match i {
None => None,
Some(1) => Some(2),
x => Some(x + 2),
}

Some(1) and None are examples of refutable patterns. Either of these patterns, taken alone, might or might not match i. x is an example of an irrefutable pattern. x will always match, no matter what.

There are some places where we're only allowed to use irrefutable patterns. For example, consider the statement:

let Some(x) = value;

Here if value is Some(1), then we expect x to get the value 1. But if value were None, what would x be here? This statement makes no sense, and will result in a compiler error, because an assignment needs an irrefutable pattern. (Although we could fix this with an if let instead.)

There are also places where an irrefutable parameter is allowed, but is somewhat pointless, which will generate compiler warnings, such as this:

if let x = 5 {
println!("{}", x);
}

This is technically valid Rust code, but there aren't any good reasons to write something like this, so this is probably a mistake.

18.3 - Pattern Syntax

Matching Literals

let x = 1;

match x {
1 => println!("one"),
2 => println!("two"),
3 => println!("three"),
_ => println!("anything"),
}

Matching Named Variables

Named variables are irrefutable patterns that match any value:

let x = Some(5);
let y = 10;

match x {
Some(50) => println!("Got 50"),
y => println!("Matched, y = {y}"),
}

println!("at the end: x = {:?}, y = {y}", x);

Here y will match any value. Note that y does not match only 10 here.

Multiple Patterns

You can match more than one value with the | "or operator", or with a range expression:

let x = 1;

match x {
1 | 2 => println!("one or two"),
3..=5 => println!("three, four, or five"),
_ => println!("anything"),
}

Destructuring to Break Apart Values

We can destructure a struct or tuple with a pattern:

struct Point {
x: i32,
y: i32,
}

fn main() {
let p = Point { x: 0, y: 7 };

// Can rename the values when we destructure:
let Point { x: a, y: b } = p;
assert_eq!(0, a);
assert_eq!(7, b);

// Or not:
let Point { x, y } = p;
assert_eq!(0, x);
assert_eq!(7, y);
}

We can also destructure with literal values as part of a pattern. The first two arms of this match only match when y is 0 or x is 0, respectively:

fn main() {
let p = Point { x: 0, y: 7 };

match p {
Point { x, y: 0 } => println!("On the x axis at {x}"),
Point { x: 0, y } => println!("On the y axis at {y}"),
Point { x, y } => {
println!("On neither axis: ({x}, {y})");
}
}
}

We've seen examples already of destructuring enums:

enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}

fn main() {
let msg = Message::ChangeColor(0, 160, 255);

match msg {
Message::Quit => {
println!("The Quit variant has no data to destructure.");
}
Message::Move { x, y } => {
println!("Move in the x direction {x} and in the y direction {y}");
}
Message::Write(text) => {
println!("Text message: {text}");
}
Message::ChangeColor(r, g, b) => {
println!("Change the color to red {r}, green {g}, and blue {b}",)
}
}
}

We can even destructure nested fields out of an enum:

enum Color {
Rgb(i32, i32, i32),
Hsv(i32, i32, i32),
}

enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(Color),
}

fn main() {
let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));

match msg {
Message::ChangeColor(Color::Rgb(r, g, b)) => {
println!("Change color to red {r}, green {g}, and blue {b}");
}
Message::ChangeColor(Color::Hsv(h, s, v)) => {
println!("Change color to hue {h}, saturation {s}, value {v}")
}
_ => (),
}
}

And we can mix-and-match destructuring nested structs and tuples:

let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 });

Ignoring Values in a Pattern

We can ignore an entire value in a pattern with _. We've seen this as a catch-all in a match, but we can also use it to ignore a parameter in a function:

fn foo(_: i32, y: i32) {
println!("This code only uses the y parameter: {}", y);
}

fn main() {
foo(3, 4);
}

This can be useful when you need to implement a certain function signature in order to match the definition in a trait. We can also use _ to ignore parts of a value:

let mut setting_value = Some(5);
let new_setting_value = Some(10);

match (setting_value, new_setting_value) {
(Some(_), Some(_)) => {
println!("Can't overwrite an existing customized value");
}
_ => {
setting_value = new_setting_value;
}
}

println!("setting is {:?}", setting_value);

Or part of a destructuring assignment:

let numbers = (2, 4, 8, 16, 32);

match numbers {
(first, _, third, _, fifth) => {
println!("Some numbers: {first}, {third}, {fifth}")
}
}

Ignoring an Unused Variable by Starting Its Name with _

We can prefix an unused variable name with an _ to avoid a compiler warning:

fn main() {
let _x = 5;
}

There is one big difference between _ and _x: _x will still bind the variable, where _ will not. This can be important when we consider ownership:

let s = Some(String::from("Hello!"));

if let Some(_s) = s {
println!("found a string");
}

// `s` was moved into `_s` above, so
// this won't compile!
println!("{:?}", s);

If we used if let Some(_) here, this would have worked.

Ignoring Remaining Parts of a Value with ..

We can ignore part of a tuple or struct with ..:

struct Point {
x: i32,
y: i32,
z: i32,
}

let origin = Point { x: 0, y: 0, z: 0 };

// Ignore the `y` and `z` members
match origin {
Point { x, .. } => println!("x is {}", x),
}

let numbers = (2, 4, 8, 16, 32);

// Ignore all but the first and last numbers
match numbers {
(first, .., last) => {
println!("Some numbers: {first}, {last}");
}
}

Extra Conditionals with Match Guards

A match guard is an additional if condition attached to a pattern:

let num = Some(4);

match num {
Some(x) if x % 2 == 0 => println!("The number {} is even", x),
Some(x) => println!("The number {} is odd", x),
None => (),
}

A match guard applies to the entire pattern:

let x = 4;
let y = false;

match x {
4 | 5 | 6 if y => println!("yes"),
_ => println!("no"),
}

Here if y is true, the first arm will match 4 | 5 | 6. If y is false, the first arm will never match. It's (4 | 5 | 6) if y, not 4 | 5 | (6 if y).

One downside to match guards is that they generally require the match to have a catch-all at the end. You and I might know that this match is exhaustive:

match x {
Some(x) if y => println!("{x}"),
Some(x) if !y => println!("{x}"),
None => panic!("Silly compiler"),
}

But unfortunately the compiler isn't smart enough to figure this out.

One word of caution here (or any time you use a catch-all so you don't have to explicitly enumerate all cases):

let x = Some(8);
match x {
Non => panic!("Nothing to print"),
Some(x) if x <= 2 => println!("Little {x}"),
Some(x) if x > 2 => println!("Big {x}"),
}

You might expect this to print "Big 8", but it will actually print "Nothing to print" because we mistyped None and accidentally created a variable named Non. Fortunately it's difficult to do this without getting a compiler warning for our unused Non variable.

@ Bindings

Sometimes we want to test a value as part of a pattern, and also assign that value to a variable. We can do this with the at operator:

enum Message {
Hello { id: i32 },
}

let msg = Message::Hello { id: 5 };

match msg {
// Match `id` and bind it to `id_variable`.
Message::Hello {
id: id_variable @ 3..=7,
} => println!("Found an id in range: {}", id_variable),
Message::Hello { id: 10..=12 } => {
println!("Found an id in another range")
}
Message::Hello { id } => println!("Found some other id: {}", id),
}

Continue to chapter 19.

- + \ No newline at end of file diff --git a/ch19/ch19-01-unsafe/index.html b/ch19/ch19-01-unsafe/index.html index 0b2f33a..17d6248 100644 --- a/ch19/ch19-01-unsafe/index.html +++ b/ch19/ch19-01-unsafe/index.html @@ -4,13 +4,13 @@ 19.1 - Unsafe Rust | The rs Book - +

19.1 - Unsafe Rust

danger

This chapter is meant as an introduction to unsafe code, but if you find yourself actually writing unsafe code, it would be a good idea to read through the Rustonomicon. There are many things you can do in unsafe code that will result in undefined behavior, some of which you might surprise you if you're coming from a language like C/C++.

Rust enforces all sort of safety features for us, preventing us from dereferencing null pointers, preventing us from creating potential data races. Sometimes, though, we know better than the compiler.

Imagine we have a vector with six elements in it. We could create a mutable slice to elements 0-2 and a second mutable slice from elements 3-5, and there'd be no chance of a data race, since these two mutable references point to different regions of memory. The problem with this is that when we call &mut values[0..=2], we have a mutable reference to the underlying array, not to part of the array, and we won't be able to create a second one. Here the compiler is trying to protect us, but it's actually getting in our way.

Unsafe code in Rust is code where we're allowed to ignore or bypass some of the restrictions Rust places on us, and tell the compiler "Don't worry, I got this." Of course, sometimes we only think we know better than the compiler when in fact what we're actually doing is creating dreaded undefined behavior. So it's not a bad idea to keep unsafe code to a minimum.

But it's important to note that "unsafe" doesn't necessarily mean incorrect, it's just code that hasn't been inspected by the eagle eye of the Rust compiler. There are plenty of C programs in the world performing useful tasks that are correct (or reasonably correct) and C doesn't even have a borrow checker, so all C code is unsafe as far as a Rust programmer is concerned.

We can write code inside an unsafe block or inside an unsafe function:

fn main() {
unsafe {
// Do crazy stuff here!
}
}

Unsafe Superpowers

There are five unsafe superpowers. These are things you're allowed to do inside an unsafe block or function that you aren't allowed to do outside of them:

  • Dereference a raw pointer
  • Call another unsafe function or method
  • Access or modify a mutable static variable
  • Implement an unsafe trait
  • Access fields of a union

Other than these five things, unsafe code is mostly like safe code. The borrow checker is still checking your borrows, immutable references are still immutable, the sun still rises in the east. These five things do let you get into a surprising amount of trouble though - it's important to document your assumptions and invariants, and carefully ensure you're meeting the assumptions and invariants of any unsafe functions you're calling into.

Dereferencing a Raw Pointer

Raw pointers come in two types: *const T and *mut T. These are a lot closer to pointers in C than references in Rust:

  • You can have both immutable and mutable pointers pointing to the same location in memory.
  • Pointers can point to memory that has been freed or isn't valid.
  • Pointers can be null.
  • Pointers don't do any kind of automatic cleanup.

Since Rust doesn't make any guarantees about raw pointers, it's up to you to make sure you use them correctly by reasoning about your code. Let's see a couple of examples:

let mut num = 5;

// Create a mutable and const pointer to the same memory.
let r1 = &mut num as *mut i32;
let r2 = unsafe { &*r1 as *const i32 };

unsafe {
println!("r1 is: {}", *r1);
println!("r2 is: {}", *r2);
}

// Create a pointer to a specific address.
// (Hopefully this is memory we own!)
// Note the `as` keyword to cast the value
// into a raw pointer.
let address = 0x012345usize;
let r = address as *const i32;

We're allowed to create pointers outside of unsafe code. Creating a pointer never hurt anyone, it's dereferencing a pointer that gets us into trouble, so the dereference is only allowed to happen inside an unsafe block.

Why would you want to use a raw pointer instead of a reference? One case is for calling into C code. Another is when you want to build a "safe" abstraction that the borrow checker won't understand, like our "two mutable slices" example above. We'll see examples of both of these.

Calling an Unsafe Function or Method

The second of our superpowers is calling an unsafe function or method. If you want to call an unsafe function, you can only do so from an unsafe function or block:

unsafe fn dangerous() {}

unsafe {
dangerous();
}

Any function that's marked as unsafe like this is implicitly an unsafe block.

Creating a Safe Abstraction over Unsafe Code

Let's go back to our "two mutable slices" example from earlier. We want to write a function that will split a vector into two mutable slices:

let mut v = vec![1, 2, 3, 4, 5, 6];

let r = &mut v[..];

let (a, b) = r.split_at_mut(3);

assert_eq!(a, &mut [1, 2, 3]);
assert_eq!(b, &mut [4, 5, 6]);

split_at_mut is going to call unsafe code, but that doesn't mean that it also has to be unsafe. In fact, the above code works because vector has this method on it already!

What split_at_mut is doing here is creating a "safe abstraction". This is a very common pattern - we hide away the unsafe stuff behind an API that's easy and safe to use. This makes it so we only have to reason about our small API. Here's the implementation of split_at_mut:

use std::slice;

fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
let len = values.len();
let ptr = values.as_mut_ptr();

assert!(mid <= len);

unsafe {
(
slice::from_raw_parts_mut(ptr, mid),
slice::from_raw_parts_mut(ptr.add(mid), len - mid),
)
}
}

slice::from_raw_parts_mut is unsafe (because it uses a raw pointer to the underlying slice) so we need to call this inside an unsafe block.

Using extern Functions to Call External Code

Programming languages can call into code written in other languages via a Foreign Function Interface (FFI). If you wanted to use OpenSSL from Rust, for example, rather than rewriting OpenSSL in Rust you could just call into the existing C code. You might build a wrapper crate around OpenSSL to turn it into something easy to use from Rust, and provide safe abstractions around everything the library does.

Here's an example of calling abs from the C standard library:

extern "C" {
fn abs(input: i32) -> i32;
}

fn main() {
unsafe {
println!("Absolute value of -3 according to C: {}", abs(-3));
}
}

The extern "C" block tells Rust we're going to call an external function using the C application binary interface.

We can also use the extern keyword to create a function that can be called from C:

#[no_mangle]
pub extern "C" fn call_from_c() {
println!("Just called a Rust function from C!");
}

Accessing or Modifying a Mutable Static Variable

As mentioned in chapter 3, Rust has global variables, called static variables:

static HELLO_WORLD: &str = "Hello, world!";

fn main() {
println!("name is: {}", HELLO_WORLD);
}

When we use a constant in Rust, the compiler may duplicate the constant in multiple places in memory if they are referenced in multiple places. Static variables, on the other hand, are always guaranteed to occur only once in memory, so no matter where they are referenced in code you'll get back the same instance. Unlike constants, static variables can also be mut, but accessing or modifying a mutable static variable is always unsafe:

static mut COUNTER: u32 = 0;

fn add_to_count(inc: u32) {
unsafe {
COUNTER += inc;
}
}

fn main() {
add_to_count(3);

unsafe {
println!("COUNTER: {}", COUNTER);
}
}

It is quite difficult, especially in a multi-threaded program, to ensure that access to a mutable static variable doesn't create a data race.

Implementing an Unsafe Trait

We call a trait an unsafe trait when it has some invariant that the compiler can't verify for us. An example would be the Send and Sync traits. Any struct made entire of Send and Sync members automatically becomes Send and Sync. If we want to create a struct that contains a raw pointer, and we can guarantee that this struct is safe to send across threads or can be accessed from multiple threads, then we'll have to mark the type as Send and/or Sync ourselves.

In order to do this, we use an unsafe impl block:

unsafe trait Foo {
// methods go here
}

unsafe impl Foo for i32 {
// method implementations go here
}

Accessing Fields of a Union

Unions are included in Rust mainly for calling into C code that uses them. If you want to access a union, it has to be done from an unsafe block.

For the non-C programmers reading this, a union is like a struct, but each field in the union occupies the same memory. Only one of the fields is ever correct to access at a time, depending on what is stored in the union. This example, for instance, will be four bytes long and holds either a u32 or an f32:

#[repr(C)]
union MyUnion {
f1: u32,
f2: f32,
}

Rust has no idea what's stored in this union, and you'll get back a u32 or an f32 depending on which one you access, but odds are only one of them contains a meaningful value. You can learn more about unions in the Rust Reference.

Soundness

This is an example of an unsafe function, taken from the Rustonomicon:

fn index(idx: usize, arr: &[u8]) -> Option<u8> {
if idx < arr.len() {
unsafe {
Some(*arr.get_unchecked(idx))
}
} else {
None
}
}

This uses unsafe code, but it checks the bounds of the array before calling into get_unchecked, so we can prove this function is sound (it can't cause undefined behavior). Note that if you change the first line of this function to if idx <= arr.len() the function becomes unsound, even though we didn't modify any unsafe code!

Verifying unsafe code with Miri

TODO: Add section here about using Miri to test unsafe code. The Rust Playground has a "Run with Miri" under "Tools" in the upper right corner, which is handy for checking a function.

- + \ No newline at end of file diff --git a/ch19/ch19-02-advanced-traits/index.html b/ch19/ch19-02-advanced-traits/index.html index 9377b9c..378a545 100644 --- a/ch19/ch19-02-advanced-traits/index.html +++ b/ch19/ch19-02-advanced-traits/index.html @@ -4,13 +4,13 @@ 19.2 - Advanced Traits | The rs Book - +

19.2 - Advanced Traits

For an introduction to traits, see chapter 10.

Specifying Placeholder Types in Trait Definitions with Associated Types

Associated types are a bit like generics for traits. Associated types let us define a trait that uses some type without knowing what the concrete type is until the trait is implemented:

pub trait Iterator {
type Item;

fn next(&mut self) -> Option<Self::Item>;
}

The implementor is the one that specifies the concrete type of the associated type:

// An iterator that returns an unlimited supply of 7s:
struct ForeverSevenIterator {}

impl Iterator for ForeverSevenIterator {
// Must fill in the concrete type here.
type Item = i32;

fn next(&mut self) -> Option<i32> {
return Some(7);
}
}

Although of course if our type is generic, we can use the generic to fill in the associated type:

struct ForeverIterator<T> {
pub val: T,
}

impl<T> Iterator for ForeverIterator<T> {
type Item = T;

fn next(&mut self) -> Option<T> {
return Some(self.val);
}
}

How are associated types and generics different? Why is this not just:

pub trait GenericIterator<T> {
fn next(&mut self) -> Option<T>;
}

Well, actually, we can do this. You can have generic traits, but there's an important difference: a trait with an associated type can only be implemented for a given type once, but a trait with a generic type could be implemented for a given type multiple times for different generic types.

This means, practically speaking, that if someone implemented GenericIterator then whenever we called next, we'd have to explicitly annotate the type of the return value so we'd know which version of next to call.

struct ForeverSevenIterator {}

impl GenericIterator<i32> for ForeverSevenIterator {
fn next(&mut self) -> Option<i32> {
return Some(7);
}
}

impl GenericIterator<String> for ForeverSevenIterator {
fn next(&mut self) -> Option<String> {
return Some(String::from("seven"));
}
}

fn main() {
let mut iter = ForeverSevenIterator{};
// Need to type annotate here, because
// `iter.next()` can return an i32 or a string.
let v: Option<i32> = iter.next();
}

This isn't a problem for associated types, because we know there can only ever be one impl Iterator for Counter.

Default Generic Type Parameters and Operator Overloading

When we have a generic type, we can specify a default type parameter that will be used if no generic type is specified:

struct Point<T = i32> {
x: T,
y: T,
}

// Don't need to specify `Point<i32>` here.
fn foo(p: Point) {
println!("{}, {}", p.x, p.y)
}

Generally there are two cases where a default type parameter is useful. You can use it to make a non-generic type generic without breaking existing uses, and you can allow customization in places where most users won't need it.

Operator overloading lets you define custom behavior for certain operators. For example, we all understand what happens when we apply the + operator to two i32s. But, what if we want to add two Points together?

#[derive(Debug, Copy, Clone, PartialEq)]
struct Point {
x: i32,
y: i32,
}

fn main() {
assert_eq!(
Point { x: 1, y: 0 } + Point { x: 2, y: 3 },
Point { x: 3, y: 3 }
);
}

Rust lets us define the behavior of the + operator by implementing the Add trait:

use std::ops::Add;

impl Add for Point {
type Output = Point;

fn add(self, other: Point) -> Point {
Point {
x: self.x + other.x,
y: self.y + other.y,
}
}
}

The std:ops section of the standard library describes what operators you can overload this way. If we have a look at the Add trait, it has an Output associated type, but the Add trait is also generic, and lets us specify the Rhs or "right-hand-side":

trait Add<Rhs=Self> {
type Output;

fn add(self, rhs: Rhs) -> Self::Output;
}

Again, this is an example of a generic with a default type parameter. We didn't specify an Rhs above so it defaults to Self (or in this case Point). Generally when you want to add a thing to another thing, they're going to be of the same type, so here the default saves us some typing.

But having the Rhs be a generic type means we can also implement Add for cases where we're adding together two different types. Here's an example where we define a Millimeters and Meters type, and specify how to add meters to millimeters:

use std::ops::Add;

struct Millimeters(u32);
struct Meters(u32);

impl Add<Meters> for Millimeters {
type Output = Millimeters;

fn add(self, other: Meters) -> Millimeters {
Millimeters(self.0 + (other.0 * 1000))
}
}

Fully Qualified Syntax for Disambiguation: Calling Methods with the Same Name

The first time you saw that impl TRAIT for TYPE syntax, you probably realized you could have two different traits that each defined a function called foo, and then you could create a type that implemented both traits. In fact, you can also have a trait that defines a method named foo that differs from a method defined on the struct outside any trait also called foo. The Human struct in this next example has three different methods called fly:

trait Pilot {
fn fly(&self);
}

trait Wizard {
fn fly(&self);
}

struct Human;

impl Pilot for Human {
fn fly(&self) {
println!("This is your captain speaking.");
}
}

impl Wizard for Human {
fn fly(&self) {
println!("Up!");
}
}

impl Human {
fn fly(&self) {
println!("*waving arms furiously*");
}
}

fn main() {
let person = Human;

// If there's more than one `fly`, and you
// don't specify the one you want, this
// will call the one from the struct.
// This prints "*waving arms furiously*".
person.fly();

// We can also call this as:
Human::fly(&person);

// We can explicitly call the `fly` method
// from either trait:
Pilot::fly(&person);
Wizard::fly(&person);
}

When we call these methods explicitly like this, we have to pass in the self parameter, as if we were calling these like an associated function. (We've already seen an example of this syntax when we called Rc::clone, although we didn't know it at the time!)

Although, this brings up an interesting point; if we can call a method on a trait using the associated function syntax, can we define an associated function on a trait?

trait Animal {
fn baby_name() -> String;
}

struct Dog;

impl Animal for Dog {
fn baby_name() -> String {
String::from("puppy")
}
}

fn main() {
println!("A baby dog is called a {}", Dog::baby_name());
}

But what happens here if Dog also has an associated function also called baby_name?

impl Dog {
fn baby_name() -> String {
String::from("Spot")
}
}

Now this program will print "A baby dog is called a Spot", which is not what we want. We can't fix this with Animal::baby_name() either, since the compiler won't know whether to call the Dog version of Animal::baby_name() or some other version. We might have a Cat concrete type that also implements the Animal trait for example, and Animal::baby_name() would be ambiguous.

Here we can disambiguate with:

fn main() {
println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
}

You could use this same syntax in our Human example above:

    <Human as Wizard>::fly(&person);

These are actually all different examples of the same thing. The general syntax is <Type as Trait>::function(receiver_if_method, next_arg, ...), but you can omit any part of this that Rust can work out on it's own.

Using Supertraits to Require One Trait's Functionality Within Another Trait

Let's say we want to define a trait called OutlinePrint. Any type that implements OutlinePrint will have a method called outline_print that will print the value with a box made of *s around it:

**********
* *
* (1, 3) *
* *
**********

We can provide a default implementation of outline_print, but in order to do so we'd have to call into self.fmt(), which means that self has to implement fmt:Display.

We can write this trait like this:

use std::fmt;

trait OutlinePrint: fmt::Display {
fn outline_print(&self) {
let output = self.to_string();
let len = output.len();
println!("{}", "*".repeat(len + 4));
println!("*{}*", " ".repeat(len + 2));
println!("* {} *", output);
println!("*{}*", " ".repeat(len + 2));
println!("{}", "*".repeat(len + 4));
}
}

We say here that fmt::Display is a supertrait of OutlinePrint. This is kind of like adding a trait bounds to OutlinePrint - saying that in order to implement OutlinePrint, your type also has to implement fmt::Display. It's also kind of like saying that OutlinePrint inherits from fmt:Display which is why we call it a supertrait (although you can't define fmt in the impl block for OutlinePrint, so it's not quite like OO style inheritance).

We can implement this on a Point:

use std::fmt;

struct Point {
x: i32,
y: i32,
}

impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}

// No need to implement the outline_print method as we get
// the default definition, which automatically calls into
// `fmt` above.
impl OutlinePrint for Point {}

Using the Newtype Pattern to Implement External Traits on External Types

Back in chapter 10, we mentioned the "orphan rule". If you want to implement a trait on a type, then either the trait or the type (or both) need to be defined locally in your crate.

It's possible to get around this using the newtype pattern (borrowed from Haskell). The basic idea is to create a tuple "wrapper" around the existing type. Let's suppose we want to implement Display on Vec<T>. These are both from the standard library, so normally we couldn't do this. We'll use the newtype pattern here:

src/main.rs
use std::fmt;

// Create a newtype wrapper around `Vec<String>`.
struct Wrapper(Vec<String>);

// Implement `Display` trait on the wrapper.
impl fmt::Display for Wrapper {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[{}]", self.0.join(", "))
}
}

fn main() {
let w = Wrapper(vec![String::from("hello"), String::from("world")]);
println!("w = {}", w);
}

The disadvantage to this approach is that we have a new type Wrapper here, and we can't just treat w like we could a regular vector. Most of the methods we want to call on Vec aren't defined on Wrapper. We could redefine just the methods we want to call on Wrapper (which could ve an advantage if we want to present a subset of it's API as our API). We could also implement the Deref trait so we can treat a w like vector.

- + \ No newline at end of file diff --git a/ch19/ch19-03-advanced-types/index.html b/ch19/ch19-03-advanced-types/index.html index d1e614d..be06a30 100644 --- a/ch19/ch19-03-advanced-types/index.html +++ b/ch19/ch19-03-advanced-types/index.html @@ -4,13 +4,13 @@ 19.3 - Advanced Types | The rs Book - +

19.3 - Advanced Types

Using the Newtype Pattern for Type Safety and Abstraction

In the previous section, we discussed using the newtype pattern to wrap an existing type in a tuple.

The newtype pattern is useful in a few other scenarios too. If we create a Millisecond type:

struct Millisecond(u32);

fn sleep(duration: Millisecond) {
// --snip--
}

This makes it very clear that sleep expects a value in milliseconds (although in this particular example you'd be better off using std::time::Duration. The newtype pattern can also be used to wrap a type and give it a different public API.

Creating Type Synonyms with Type Aliases

We can give an existing type a type alias:

type Kilometers = i32;

let x: i32 = 5;
let y: Kilometers = 5;

println!("x + y = {}", x + y);

This creates a new type called Kilometers which is an alias for i32. You can now use these types interchangeably - if a function expects a Kilometers you can pass in an i32 instead or vice versa.

The main use case for type aliases is to reduce the length of long type names. We can take code like this:

let f: Box<dyn Fn() + Send + 'static> = Box::new(|| println!("hi"));

fn takes_long_type(f: Box<dyn Fn() + Send + 'static>) {
// --snip--
}

fn returns_long_type() -> Box<dyn Fn() + Send + 'static> {
// --snip--
}

and turn it into:

type Thunk = Box<dyn Fn() + Send + 'static>;

let f: Thunk = Box::new(|| println!("hi"));

fn takes_long_type(f: Thunk) {
// --snip--
}

fn returns_long_type() -> Thunk {
// --snip--
}

A meaningful name for your alias can make your code much easier to read and write. Another example of this is in std::io. Many functions here return a Result with a std::io::Error as the error type, so std:io defines:

type Result<T> = std::result::Result<T, std::io::Error>;

which makes a lot of function signatures in this module much shorter and easier to read.

The Never Type that Never Returns

There's a special type named !. This is the empty type or never type:

fn bar() -> ! {
// --snip--
}

Here this tells the compiler that the bar function will never return. Way back in chapter 2 we wrote this code:

loop {
// --snip--
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
// --snip--
}

The arms of a match are supposed to all be of the same type in order for it to compile. You can't have one arm evaluate to a u32 and another evaluate to a String. Here though, we know that the Err(_) arm isn't going to return anything - if we get here, we'll abort this run through the loop and continue. From a type perspective, the return value of continue is !, so here Rust knows it's safe to ignore this arm (or to put it another way, the ! type can be coerced to any other type).

The panic! macro is another example of something that evaluates to the ! type:

impl<T> Option<T> {
pub fn unwrap(self) -> T {
match self {
Some(val) => val,
None => panic!("called `Option::unwrap()` on a `None` value"),
}
}
}

A loop without a break is also of type !.

Dynamically Sized Types and the Sized Trait

When we create a variable on the stack, Rust needs to know how much space to allocate for that variable at compile time. For example:

fn add(a: i32, b: i32) {
println!("{}", a + b);
}

When someone calls add, Rust will need to allocate four bytes on the stack to hold a, and another four to hold b.

Consider a string, which holds a variable amount of data:

fn say_hello(name: &str) {
println!("Hello {name}");
}

Here name isn't actually a str but a &str or a string slice. The actual data from the string is stored "somewhere else" in memory, but the name variable itself is 16 bytes long on a 64-bit platform (two usizes). This is because &str is implemented as a pointer to the string data and a length value.

As a rule, to pass around dynamically sized types like a string, we need a pointer. This can be a Box or an Rc or a &, but some kind of pointer. Another example of a dynamically sized type is a trait object, which is why when we pass one it's usually in a Box<dyn Trait>. The size of the trait object itself is unknown, so we pass around a smart pointer to the trait object instead, allowing us to store the trait object on the heap.

Any type whose size is known at compile time automatically implements the Sized trait. Generic functions implicitly get a trait bounds for Sized:

// You write this:
fn my_generic_fn<T>(t: T) {
// --snip--
}

// But Rust implicitly turns this into:
fn my_generic_fn<T: Sized>(t: T) {
// --snip--
}

You can prevent this with this syntax:

// T doesn't have to be `Sized`.
fn generic<T: ?Sized>(t: &T) {
// --snip--
}

Note that in order to do this, we can't leave the t parameter of type T. We again need some kind of pointer, in this case we chose &T.

- + \ No newline at end of file diff --git a/ch19/ch19-04-advanced-functions-and-closures/index.html b/ch19/ch19-04-advanced-functions-and-closures/index.html index 5e0778f..0febb04 100644 --- a/ch19/ch19-04-advanced-functions-and-closures/index.html +++ b/ch19/ch19-04-advanced-functions-and-closures/index.html @@ -4,13 +4,13 @@ 19.4 - Advanced Functions and Closures | The rs Book - +

19.4 - Advanced Functions and Closures

Function Pointers

In chapter 13 we saw you could pass a closure to a function, but we can also pass a function to a function!

fn add_one(x: i32) -> i32 {
x + 1
}

// Note that this takes a function pointer as
// a parameter, and not a closure.
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
f(arg) + f(arg)
}

fn main() {
let answer = do_twice(add_one, 5);

println!("The answer is: {}", answer);
}

The first parameter to do_twice is called a function pointer. You may recall from chapter 13 that in order to pass a closure as a parameter, we declared a generic function and used a trait bound on the generic type to FnOnce, FnMut, or Fn. The difference between a closure and a function pointer is the function pointer is a named concrete type instead of a generic trait bound. (Technically any given closure has a concrete type as well, generated at compile time, but these are unnameable types.)

Because a function is like a closure that cannot capture any variables, function pointers implement all three generic traits (FnOnce, FnMut, and Fn) so you can always pass a function pointer to a function that expects a trait. For this reason, it's generally more flexible to write a function that takes a closure. You'll likely have to use a function pointer instead If you're interacting with C code.

Passing Functions In Place of a Closure

Here's an example of using a function in place of a closure:

let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec<String> =
list_of_numbers.iter().map(|i| i.to_string()).collect();

// This is equivalent to the above:
let list_of_strings2: Vec<String> =
list_of_numbers.iter().map(ToString::to_string).collect();

Each enum variant we define becomes an initializer function, so we can use them as function pointers (as we also could any constructor):

enum Status {
Value(u32),
Stop,
}

let list_of_statuses: Vec<Status> = (0u32..20).map(Status::Value).collect();

Returning Closures

Since a closure is defined using a trait, if you want to return one from a function you'll have to use a trait object:

fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
Box::new(|x| x + 1)
}
- + \ No newline at end of file diff --git a/ch19/ch19-05-macros/index.html b/ch19/ch19-05-macros/index.html index b3a76dc..54d3d59 100644 --- a/ch19/ch19-05-macros/index.html +++ b/ch19/ch19-05-macros/index.html @@ -4,13 +4,13 @@ 19.5 - Macros | The rs Book - +

19.5 - Macros

If you're coming to Rust from C or C++, then you're no doubt already familiar with macros. We're going to give a quick introduction to macros here, but if you want to read more you should check out The Little Book of Rust Macros.

Macros are a kind of "metaprogramming". When we write a Macro, we're actually writing Rust code that generates more Rust code.

  • Macros run at compile time, so they have no runtime performance impact (although they can generate code that runs at runtime, which might).
  • Macros can take a variable number of parameters (such as the println! marco does) which normal Rust functions cannot.
  • Macros must be brought into scope or defined locally before they are called.

Declarative Macros with macro_rules! for General Metaprogramming

Declarative macros are sometimes called "macros by example" or just "macros" (because these are the most common kind of macro you're going to encounter). Here is a very simple macro:

macro_rules! four {
() => {
1 + 3
};
}

fn main() {
let x = four!();
println!("{x}");
}

The macro_rules! four says we're going to declare a macro named four!. Inside the {}, the rest of this macro is similar to a match expression (in this example we only have one arm). Each rule in a macro_rules! is of the format (MATCHER) => {EXPANSION};. When we call a macro, we don't actually pass in parameters like i32s or &strs, instead we're passing in a snippet of Rust code. When the macro runs, it will try to match the passed in token tree to each matcher in turn. Once it finds a match, we'll replace the whole macro with whatever is in the expansion part.

In the case of our macro above, we just have a single "empty matcher". If you were to try calling let x = four!("hello");, you'd get an error telling you no rules expected the token `"hello"`.

A matcher can contain captures which let us capture some tokens to a metavariable. Metavariables start with $:

macro_rules! add_one {
($e:expr) => { $e + 1 };
}

Here if you called add_one!(2) would be replaced with 2 + 1. Let's have a look at the vec! macro, which is a bit more exciting:

#[macro_export]
macro_rules! vec {
( $( $x:expr ),* ) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x);
)*
temp_vec
}
};
}
info

This is actually a slightly simplified version of vec!. The original tries to preallocate the correct amount of data in the new vector.

First, notice we've added the #[macro_export] annotation. Without this annotation, this macro can't be used outside of the crate it is defined in.

The $(),* part of the matcher here is called a repetition. These have the form $ (...) sep rep, where ( ... ) is the part that's being repeated, sep is an optional separator token, and rep defines how many times the pattern can repeat - ? for zero or one, * for zero or more, and + for one or more (like in a regular expression). So ( $( $x:expr ),* ) matches zero or more expressions, separated by commas, and each time through the repetition we assign the matched part to the $x metavariable.

On the right hand side of the => we have the code we're going to expand this to. Inside the $() is the repetition part - this code will be inserted once for each time the repetition matches on the matcher side.

So if we were to write vec![1, 2, 3], at compile time this would get replaced with:

{
let mut temp_vec = Vec::new();
temp_vec.push(1);
temp_vec.push(2);
temp_vec.push(3);
temp_vec
}

Procedural Macros for Generating Code from Attributes

A procedural macro is a Rust function that takes in a TokenStream of some input source code and produces a TokenStream of some generated code. There are three kinds of procedural macros: custom derive, attribute-like, and function-like. When we #[derive()] a trait, it's going through a custom-derive macro. Procedural macros need to be defined in their own special crate for technical reasons we're going to hand wave away for this book, although this will likely change in the future.

How to Write a Custom derive Macro

Let's create some new projects:

mkdir projects
cd projects
cargo new hello_macro --lib
cd hello_macro
cargo new hello_macro_derive --lib

We created two projects, one inside the other. The outer project will contain our trait, and the inner wil going to contain our custom derive macro. We create these two projects one-inside-the-other because they are tightly related; if the code in the outer project changes, odds are the code in the inner project will too. Unfortunately we'll need to publish the two crates to crates.io separately.

In the outer project, we're going to create a trait:

hello_macro/src/lib.rs
pub trait HelloMacro {
fn hello_macro();
}

The idea here is that a when a consumer of our library implements this trait, we want to give them a derive macro that will implement the hello_macro method for them. Let's create one more project in the "projects" folder:

cd ..
cargo new pancakes

And then write a file that uses our derive macro:

pancakes/src/main.rs
use hello_macro::HelloMacro;
use hello_macro_derive::HelloMacro;

// This derive attribute will run our derive macro.
#[derive(HelloMacro)]
struct Pancakes;

fn main() {
// This will print "Hello, Macro! My name is Pancakes!"
Pancakes::hello_macro();
}

In our inner project, we're going to add some dependencies to Cargo.toml:

hello_macro/hello_macro_derive/Cargo.toml
[lib]
proc-macro = true

[dependencies]
syn = "1.0"
quote = "1.0"

The proc-macro = true line tells Cargo that this is a special library that contains procedural macros. This also gives us access to the proc_macro crate, which is where TokenStream comes from. syn is a crate for parsing Rust code into an abstract syntax tree or AST, and quote is a crate for turning a syntax tree back into Rust code. syn is going to take our Pancakes data structure above and turn it into something like:

DeriveInput {
// --snip--
ident: Ident {
ident: "Pancakes",
span: #0 bytes(95..103)
},
data: Struct(
DataStruct {
struct_token: Struct,
fields: Unit,
semi_token: Some(
Semi
)
}
)
}

The field we care about for our implementation is the ident or "identifier" for our struct. You can see what else will be passed to our macro in the syn::DeriveInput documentation.

Here's the code for our macro:

hello_macro/hello_macro_derive/src/lib.rs
use proc_macro::TokenStream;
use quote::quote;
use syn;

// This line tells Rust that this is the macro
// to call when someone does `#[derive(HelloMacro)]`.
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
// Construct a representation of Rust code as a syntax tree
// that we can manipulate
let ast = syn::parse(input).unwrap();

// Build the trait implementation
impl_hello_macro(&ast)
}

// It's very common to split the derive macro into one function
// that parses the input (`hello_macro_derive`) and one that
// generates the code (`impl_hello_macro`).
fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;

// `#name` will be replaced with `Pancakes` here.
let gen = quote! {
impl HelloMacro for #name {
fn hello_macro() {
println!("Hello, Macro! My name is {}!", stringify!(#name));
}
}
};

// Convert `gen` into a `TokenStream`.
gen.into()
}

The quote! macro here helps us define the code we want to generate. Note the #name template inside of quote!. quote! has other cool template tricks, so be sure to check out its documentation. The stringify! macro is built into rust and turns an expression like 1 + 2 into a string like "1 + 2", or here Pancakes into "Pancakes".

If you want to run this, there's just one thing left to do. In our pancakes project, we need to add dependencies to Cargo.toml so it can find our trait and macro:

pancakes/Cargo.toml
[dependencies]
hello_macro = { path = "../hello_macro" }
hello_macro_derive = { path = "../hello_macro/hello_macro_derive" }

Now you should be able to cargo run from the pancakes folder. If you run into trouble, the full source is available on GitHub.

Attribute-like macros

Attribute-like macros are another kind of procedural macros. They let you define custom attributes, for example:

#[route(GET, "/")]
fn index() {

Unlike a custom derive macro (which can only be applied to structs and enums), these can be applied to any Rust code. To define this macro, you'd create a macro function like this:

#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
// --snip--
}

The implementation would be just like the derive macro, except that there are two TokenStreams - one for the item we are adding this attribute to, and one for the parameters passed to the macro. Like the derive macro, this needs to be in a special crate by itself (or with other procedural macros).

Function-like macros

The last kind of procedural macro is the function-like macro. The name comes from the fact that we can call them like a function, similar to macro_rules! macros:

let sql = sql!(SELECT * FROM posts WHERE id=1);

This macro would be defined as:

#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {
// --snip--
}
- + \ No newline at end of file diff --git a/ch20/ch20-01-single-threaded-web-server/index.html b/ch20/ch20-01-single-threaded-web-server/index.html index eb8fc30..2acdde2 100644 --- a/ch20/ch20-01-single-threaded-web-server/index.html +++ b/ch20/ch20-01-single-threaded-web-server/index.html @@ -4,13 +4,13 @@ 20.1 - Building a Single-Threaded Web Server | The rs Book - +

20.1 - Building a Single-Threaded Web Server

In this chapter we're going to build a simple HTTP server to put together a number of things we've learned so far. As usual, the code for this project is available in the GitHub repo.

HTTP Requests

An HTTP GET request looks something like:

GET /index.html HTTP/1.1
Host: example.com
Accept-Language: en-us
Accept-Encoding: gzip, deflate
Connection: Keep-Alive

Each newline here is actually a CRLF or a \r\n. The first line is of the format Method Request-URI HTTP-Version CRLF. This is followed by one or more headers, followed by a blank line, and then optionally a body. (For our server, we'll assume only a maniac would send a GET with a body.)

The response looks very similar:

HTTP/1.1 200 OK
Content-Type: text
Content-Length: 26

<html>Hello, World!</html>

The first line is HTTP-Version Status-Code Reason-Phrase CRLF, and this is followed by headers, a blank line, and then the response body.

Some HTML to Serve

Let's create a project:

$ cargo new hello
$ cd hello

In order to create a server, first we need something to serve, so we'll create a couple of HTML file:

hello.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Hello!</title>
</head>
<body>
<h1>Hello!</h1>
<p>Hi from Rust</p>
</body>
</html>

and:

404.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Hello!</title>
</head>
<body>
<h1>Oops!</h1>
<p>Sorry, I don't know what you're asking for.</p>
</body>
</html>

And then here is the code for our server:

src/main.rs
use std::{
fs,
io::{prelude::*, BufReader},
net::{TcpListener, TcpStream},
};

fn main() {
let port = 7878u16;
let listen_address = format!("127.0.0.1:{port}");
let listener = TcpListener::bind(listen_address).unwrap();

println!("Listening on port {}", port);

for stream in listener.incoming() {
let stream = stream.unwrap();

handle_connection(stream);
}
}

fn handle_connection(mut stream: TcpStream) {
let buf_reader = BufReader::new(&mut stream);

// A line could be an error if it contains invalid
// UTF-8, or if there's a problem reading from the
// underlying stream. We ignore these errors here.
let http_request: Vec<_> = buf_reader
.lines()
.map(|result| result.unwrap())
.take_while(|line| !line.is_empty()) // Blank line is end of headers.
.collect();

let request_line = &http_request[0];

println!("Incoming request for {}", request_line);

if request_line == "GET / HTTP/1.1" {
send_response(stream, 200, "OK", "hello.html");
} else {
send_response(stream, 404, "NOT FOUND", "404.html");
}
}

fn send_response(mut stream: TcpStream, code: u16, reason: &str, filename: &str) {
let contents = fs::read_to_string(filename).unwrap();
let length = contents.len();
let response =
format!("HTTP/1.1 {code} {reason}\r\nContent-Length: {length}\r\n\r\n{contents}");

stream.write_all(response.as_bytes()).unwrap();
}

If we cargo run this and point a browser at http://localhost:7878/, we should see our web page!

Listening to the TCP Connection

Let's start with the main function. We call TcpListener::bind to start listening on a port. This returns a TcpListener instance, so it's basically a constructor for TcpListener. Note that we're binding to "127.0.0.1", so you'll only be able to access this web server from the same machine you're running it on. We could bind to "0.0.0.0" - the unspecified address - to bind to all local interfaces. bind can fail for a variety of reasons. For example, if we tried to bind to port 80 and we weren't root, this would fail because we don't have sufficient permissions, or some other process might have already bound the port. We're glossing over all the error handling with a call to unwrap.

Once we have out TcpListener we call incoming on it, which returns an iterator of Result<TcpStream, Error>. We'll get an item from this iterator every time a client tries to connect. Note this iterator will never return None! This loop is going to go on forever (or at least until we hit CTRL-C to terminate this program). A connection attempt can fail for a variety of reasons. In a production web server we'd want to handle these, but here we're once again just calling unwrap. Finally we hand of the connection to handle_connection.

Parsing the Request

Our handle_connection function creates a new buffered reader to read the incoming bytes from the stream. We user our reader to read in the request, split it into lines, then collect lines into a vector until we reach an empty line. As we've seen before, calling collect requires us to annotate the type of http_request so collect will know what kind of collection to return.

Once we have our request, we call into send_response to generate an appropriate response back to the client.

And that's all there is too it! Our server only runs in a single thread, so it can only handle a single request at a time. In the next section, we'll upgrade this server to run in multiple threads.

- + \ No newline at end of file diff --git a/ch20/ch20-02-multi-threaded-web-server/index.html b/ch20/ch20-02-multi-threaded-web-server/index.html index 9456c86..f5fb49b 100644 --- a/ch20/ch20-02-multi-threaded-web-server/index.html +++ b/ch20/ch20-02-multi-threaded-web-server/index.html @@ -4,13 +4,13 @@ 20.2 - Turning Our Single-Threaded Server into a Multithreaded Server | The rs Book - +

20.2 - Turning Our Single-Threaded Server into a Multithreaded Server

Simulating a Slow Request in the Current Server Implementation

Since our web server is single threaded, it will completely handle one request before moving on to the next request in the queue. If we had a request that took a long time to process, it would hold up all the subsequent requests.

src/main.rs
use std::{
fs,
io::{prelude::*, BufReader},
net::{TcpListener, TcpStream},
thread,
time::Duration,
};

// --snip--

fn handle_connection(mut stream: TcpStream) {
// --snip--

match &request_line[..] {
"GET / HTTP/1.1" => send_response(stream, 200, "OK", "hello.html"),
"GET /sleep HTTP/1.1" => {
thread::sleep(Duration::from_secs(5));
send_response(stream, 200, "OK", "hello.html");
}
_ => send_response(stream, 404, "NOT FOUND", "404.html")
}

// --snip--
}

We've switched from an if to a match, and added a "/sleep" route. We have to pass &request_line[..] to the match expression to explicitly convert it to a slice here, because match doesn't do automatic dereferencing like the equality method does.

The important thing here is, if you open up your browser and try to load http://localhost:7878/sleep, it'll take about five seconds for the page to load. If you tap CTRL-R to reload the page twice in quick succession, it will take about 10 seconds! Your browser sent two requests, and is waiting for the second one to finish.

Improving Throughput with a Thread Pool

We could solve this problem by just creating a new thread for each incoming connection:

for stream in listener.incoming() {
let stream = stream.unwrap();

thread::spawn(|| {
handle_connection(stream);
});
}

Starting up an OS level thread has some costs associated with it, and if we start up too many of them we may run out of system resources, so a common pattern for a situation like this is to use a thread pool. We pre-allocate a number of threads that will be sitting idle, and then whenever a request comes in we hand it off to an idle worker from the pool.

let pool = ThreadPool::new(4);
for stream in listener.incoming() {
let stream = stream.unwrap();

pool.execute(|| {
handle_connection(stream);
});
}

That's all there is too it! Except Rust can't find the ThreadPool symbol. We'll have to bring it into scope to use it, but before that we'll have to build a ThreadPool!

Building a ThreadPool

Before we show the code for a ThreadPool, let's take a moment to think through what it's going to look like. We want to store a collection of threads. We won't know the number of threads until runtime so a vector is a reasonable choice here, but what exactly is being stored in the vector? How do you store a thread? If we have a look at the signature for thread::spawn:

pub fn spawn<F, T>(f: F) -> JoinHandle<T>
where
F: FnOnce() -> T,
F: Send + 'static,
T: Send + 'static,
{
// --snip--
}

We can see it returns a JoinHandle<T>. The T here is the type the thread will "return" when it completes, but our threads are never going to complete, so we'll store a Vec<JoinHandle<()>>. Actually, in order to make our lives a little easier at debugging time, we'll give each thread a unique ID and combine ID and JoinHandle<()> into a Worker and then store a Vec<Worker>.

Here's what the Worker is going to look like:

struct Worker {
id: usize,
thread: JoinHandle<()>,
}

impl Worker {
/// Create a new Worker with the given id.
pub fn new(id: usize) -> Worker {
let thread = thread::spawn(|| {
todo!("Zhu Li, do the thing!");
});

Worker { id, thread }
}
}

We're going to execute jobs on these threads, but what's a job? We already know they are closures. Since we want our API to be similar to thread::spawn, a job is going to be the same type as F in thread::spawn above. It'll be FnOnce() since it's a function we want to call exactly once. It will also need to be Send so we can transfer it to our worker thread, and 'static because we don't know how long the thread will take to run. So we'll define Job as an alias for:

type Job = Box<dyn FnOnce() + Send + 'static>;

Whenever we call pool.execute and pass in a job, we want that job to be run by a free thread from the pool. How does this happen? What happens inside the thread we spawn inside the Worker? We've conveniently left this out of our Worker above. There are many ways we could do this, but the approach we will use here is to send each job over a channel.

Each Worker will hang on to the receiver side of a channel. The thread inside a Worker can just iterate on the channel and execute each job it receives in series. But you may recall that the channels we've been using are from the mpsc library, which stands for "multiple producers, single consumer". If we're creating four threads, we could create four channels and give one receiver from each to each worker. In this case, though, we'd have to decide which sender to send a new job to. How do we know which threads are free to accept new jobs?

What we really want here is the other way around: "single producer, multiple consumers". We know how to share a variable between multiple threads though; instead of having multiple channels, we can have just a single channel. We can wrap the receiver in a Mutex, and then wrap that in an Arc, and multiple threads will be able to safely call into the receiver one-at-a-time to fetch jobs.

Here's the code:

src/lib.rs
use std::{
sync::{mpsc, Arc, Mutex},
thread::{self, JoinHandle},
};

type Job = Box<dyn FnOnce() + Send + 'static>;

pub struct ThreadPool {
workers: Vec<Worker>,
sender: mpsc::Sender<Job>,
}

impl ThreadPool {
/// Create a new ThreadPool.
///
/// The size is the number of threads in the pool.
///
/// # Panics
///
/// The `new` function will panic if the size is zero.
pub fn new(size: usize) -> ThreadPool {
// Make sure `size` is valid.
assert!(size > 0);

// Create our sender and receiver
let (sender, receiver) = mpsc::channel();
let receiver = Arc::new(Mutex::new(receiver));

// Create a new vector. Pre-allocate the vector
// to be of length `size` so we know it can store
// all of our threads.
let mut workers = Vec::with_capacity(size);

// Create new workers and add them to the pool.
for id in 0..size {
workers.push(Worker::new(id, Arc::clone(&receiver)));
}

ThreadPool {
workers,
sender,
}
}

pub fn execute<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
// Send our job to a Worker.
let job = Box::new(f);
self.sender.send(job).unwrap();
}
}

struct Worker {
id: usize,
thread: JoinHandle<()>,
}

impl Worker {
/// Create a new Worker with the given id.
pub fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
let thread = thread::spawn(move || loop {
let job = receiver.lock().unwrap().recv().unwrap();
println!("Worker {id} got a job; executing.");
job();
});

Worker { id, thread }
}
}

If you give this a try, it should work (although you'll get some compiler warnings)! If you visit "/sleep", wait for it to load, and then double-tap "CTRL-R" to reload the page, the page should reload in about five seconds instead of ten. If you're running into problems, check out the code in the GitHub repo.

One thing you might have expected us to do in the worker was:

    let thread = thread::spawn(move || loop {
// This is not so good...
for job in receiver.lock().unwrap().iter() {
println!("Worker {id} got a job; executing.");
job();
}
});

If you give this a try, it will appear to work, but our "double-reload" example will be back to ten seconds again. Why? Because this code is equivalent to:

    let thread = thread::spawn(move || loop {
// Take the lock on the mutex...
let rx = receiver.lock().unwrap();
// Then loop forever, never giving up the lock.
for job in rx.iter() {
println!("Worker {id} got a job; executing.");
job();
}
});

One thread will take the mutex and then loop with it held, so one of our threads doing all the work.

There are also a few things wrong with this code as it stands. First, we're obviously glossing over some error handling, which is fine for this example. Second, if you reload the "/sleep" route many times, you'll find eventually it will start taking a long time to load. What's happening here is that we're queueing up jobs in the channel.

Ideally if all the workers are busy, we'd return a 503 to let the client know we are too busy to handle the request. We could do this in a few ways; we could use the atomic package to increment a counter when we start a job and decrement it when we finish one, so we know how many jobs are in progress. There's also a channel::sync_channel which allows creating a channel with a bounded size. The sender in this case has a try_send which will return an error if the channel is full. This is left as an exercise for the reader.

Next we'll look at how to adapt our web server to shut down gracefully.

- + \ No newline at end of file diff --git a/ch20/ch20-03-graceful-shutdown/index.html b/ch20/ch20-03-graceful-shutdown/index.html index 3e9907b..971fd55 100644 --- a/ch20/ch20-03-graceful-shutdown/index.html +++ b/ch20/ch20-03-graceful-shutdown/index.html @@ -4,13 +4,13 @@ 20.3 - Graceful Shutdown and Cleanup | The rs Book - +

20.3 - Graceful Shutdown and Cleanup

Right now when we hit CTRL-C to send an interrupt signal to our web server, it stops running, but it also stops any in-flight requests. Let's see if we can get our server to shut down gracefully.

The basic strategy here is going to be to implement the Drop trait on ThreadPool. When the ThreadPool is dropped, we'll signal all the threads that they should stop accepting new requests and quit, and then we'll call join on each one to give them the time they need to finish up.

If you're looking for the full source for this project, it's in the GitHub repo

Implementing the Drop Trait on ThreadPool

One problem we're going to run into is that, in order to call thread.join(), we're going to have to move the thread out of the Worker. We can't move part of a struct, so we're going to have to use the same trick we did in chapter 17 and store the thread in an Option so we can set it to None.

Calling join isn't enough though. This will wait until each thread quits, but right now the closure in each thread is an infinite loop! We need to somehow signal to the Worker's thread that it should stop accepting new jobs. We can do this by dropping the sender half of the channel. This will cause the receiver to wake up and return an error. We'll have to pull the same trick we did with thread and store the sender in an Option to make this work, otherwise there's no way for us to drop the sender. We'll also want to handle the error from recv correctly instead of just panicking.

Here's the updated library:

src/lib.rs
use std::{
sync::{mpsc, Arc, Mutex},
thread::{self, JoinHandle},
};

type Job = Box<dyn FnOnce() + Send + 'static>;

pub struct ThreadPool {
workers: Vec<Worker>,
sender: Option<mpsc::Sender<Job>>,
}

impl ThreadPool {
/// Create a new ThreadPool.
///
/// The size is the number of threads in the pool.
///
/// # Panics
///
/// The `new` function will panic if the size is zero.
pub fn new(size: usize) -> ThreadPool {
// Make sure `size` is valid.
assert!(size > 0);

// Create our sender and receiver
let (sender, receiver) = mpsc::channel();
let receiver = Arc::new(Mutex::new(receiver));

// Create a new vector. Pre-allocate the vector
// to be of length `size` so we know it can store
// all of our threads.
let mut workers = Vec::with_capacity(size);

for id in 0..size {
workers.push(Worker::new(id, Arc::clone(&receiver)));
}

ThreadPool {
workers,
sender: Some(sender),
}
}

pub fn execute<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
// Send our job to a Worker.
let job = Box::new(f);
self.sender.as_ref().unwrap().send(job).unwrap();
}
}

impl Drop for ThreadPool {
fn drop(&mut self) {
// Drop the sender to force all the workers to finish up.
drop(self.sender.take());

for worker in &mut self.workers {
println!("Shutting down worker {}", worker.id);

// If there's a thread in this worker, wait for
// it to finish. If thread is None, there's
// nothing to clean up.
if let Some(thread) = worker.thread.take() {
thread.join().unwrap();
}
}
}
}

struct Worker {
id: usize,
thread: Option<JoinHandle<()>>,
}

impl Worker {
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
let thread = thread::spawn(move || loop {
let message = receiver.lock().unwrap().recv();

match message {
Ok(job) => {
println!("Worker {id} got a job; executing.");
job();
}
Err(_) => {
println!("Worker {id} disconnected; shutting down.");
break;
}
}
});

Worker {
id,
thread: Some(thread),
}
}
}

Now we just need some way to make the server shut down. A simple way to do this for testing is to modify main:

src/main.rs
    // --snip--
for stream in listener.incoming().take(2) {
// --snip--

Now our server will shut down after two requests. Not exactly something we'd want to do in production, but it will prove our shutdown code is working here.

Next Steps

The original Rust book has some suggestions about places you could take this project further:

  • Add more documentation to ThreadPool and its public methods.
  • Add tests of the library's functionality.
  • Change calls to unwrap to more robust error handling.
  • Use ThreadPool to perform some task other than serving web requests.
  • Find a thread pool crate on crates.io and implement a similar web server using the crate instead. Then compare its API and robustness to the thread pool we implemented.

Another fun one might be to try to hook the SIGINT and SIGTERM signals so a CTRL-C will cause the server to shut down gracefully.

This is as far as the original Rust book went, but you can continue on to our special bonus chapter to find out how we can rewrite this web server using async Rust!

- + \ No newline at end of file diff --git a/ch21-async/index.html b/ch21-async/index.html index 4e513f6..33f1b77 100644 --- a/ch21-async/index.html +++ b/ch21-async/index.html @@ -4,13 +4,13 @@ 21 - Async Programming | The rs Book - +

21 - Async Programming

In this section we're going to re-implement our web server from chapter 20 using async functions. We're just going to give you enough here to get your feet wet. For further reading, check out Asynchronous Programming in Rust, and the Tokio Tutorial. As usual, if you're looking for the full source for this project, it's in the GitHub repo.

JavaScript

Wait... Isn't this supposed to be a book about Rust? It is, but we're going to start this chapter off talking about JavaScript. Love it or hate it, JavaScript is the most popular language in the world, and it is probably where most people were first exposed to the idea of async programming.

user.js
// JavaScript Code
import * as fs from "fs/promises";

async function getUserName() {
const username = await fs.readFile("./username.txt", { encoding: "utf-8" });
console.log(`Hello ${username}`);
}

Even if you don't know JavaScript, hopefully this example is simple enough that you can follow along. We're calling fs.readFile to read in a file. In JavaScript this is going to return a Promise<string>. A Promise in JavaScript is the result of some calculation we don't know yet (similar to a Future in Java, or as we'll see in a moment a Future in Rust). The magic in this function happens at the await keyword. When we await a promise, the current function stops executing, allowing other functions to run. At some future point in time when the promise resolves, this function will continue from where it left off.

In JavaScript, the above is actually more or less syntactic sugar for:

user.js
// JavaScript Code
import * as fs from 'fs/promises';

function getUserName() {
return fs.readFile("./username.txt", { encoding: 'utf-8' })
.then(username => console.log(`Hello ${username}`));

Here it's a little easier to understand how the execution of the function can be suspended. getUserName calls into readFile which creates a promise, and then getUserName returns. At some future point in time, when the promise resolves, someone will call into the closure we're passing to then. Running this closure is how we "continue" this function in JavaScript.

In Rust, we could rewrite the above example as something like:

use std::{error::Error};
use tokio::fs;

async fn get_user_name() -> Result<(), Box<dyn Error>> {
let username = fs::read_to_string("./username.txt").await?;
println!("Hello {username}");

Ok(())
}

This is very similar to the JavaScript example in many ways. Here fs::read_to_string returns a type that implements the Future trait (specifically Future<Output = Result<String, Error>>). When we call await on the future, execution of this function is suspended, and at some future point someone will resume execution and the result of the await will be a Result<String, Error>. The ? operator turns the Result into a String.

The important things to know here are that - in JavaScript or in Rust - you can only use await inside a function that's declared async, and await will temporarily suspend execution of this function.

The Runtime

In our JavaScript example, we glossed over one important detail. Someone calls calls into the closure we're passing to then, but who is this mysterious someone? In JavaScript, everything runs in an event loop which is part of the JavaScript runtime. When the promise eventually resolves, it will queue a task and the event loop will pick it up and call into the closure. In our Rust example, we have the same problem; who takes care of restarting get_user_name when the Future from fs::read_to_string completes? Here again, it's the runtime.

Except of course that Rust doesn't have a runtime. In Rust, the only code that runs in your application is code you write or code you bring in from a crate, so you need to either write your own runtime or pull one in from a crate! The most popular at the moment is Tokio, but there are other options. Also, unlike in JavaScript where everything is single threaded on the event loop, in Rust our async runtime could be implemented on a single thread or could be multithreaded (Tokio supports both).

Tokio provides us with a lot more than just a runtime. If you look at our Rust example above, you'll notice we're calling tokio::fs::read_to_string instead of std::io::read_to_string. The standard library version does the same thing, but it doesn't return a Future, it blocks until the file is read. If we were to use std::io::read_to_string here, it would block this thread for a while, potentially stopping other async code from running. Tokio provides async versions of many standard library functions in this way, and because of this, refactoring non-async code to async is usually not trivial.

An async Web Server

Let's write an async web server:

$ cargo new hello-async
$ cd hello-async

Update our Cargo.toml to include Tokio:

Cargo.toml
[package]
name = "hello-async"
version = "0.1.0"
edition = "2021"

[dependencies]
tokio = { version = "1", features = ["full"] }

Notice the features = ["full"]. Features allow us to conditionally compile only the parts of Tokio we need. Tokio provides duplicates of most of the standard library, and if you don't need parts of it you can remove them here to make your binary smaller. Here's the code:

src/main.rs
use std::{error::Error, time::Duration};
use tokio::{
fs,
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
net::{TcpListener, TcpStream},
};

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let port = 7878u16;
let listen_address = format!("127.0.0.1:{port}");
let listener = TcpListener::bind(listen_address).await.unwrap();
println!("Listening on port {}", port);

loop {
let (stream, _) = listener.accept().await.unwrap();
handle_connection(stream).await;
}
}

async fn handle_connection(mut stream: TcpStream) {
let buf_reader = BufReader::new(&mut stream);

let mut lines = buf_reader.lines();
let request_line = lines.next_line().await.unwrap().unwrap();

println!("Incoming request for {}", request_line);

match &request_line[..] {
"GET / HTTP/1.1" => send_response(stream, 200, "OK", "hello.html").await,
"GET /sleep HTTP/1.1" => {
tokio::time::sleep(Duration::from_secs(5)).await;
send_response(stream, 200, "OK", "hello.html").await;
}
_ => send_response(stream, 404, "NOT FOUND", "404.html").await,
}
}

async fn send_response(mut stream: TcpStream, code: u16, reason: &str, filename: &str) {
let contents = fs::read_to_string(filename).await.unwrap();
let length = contents.len();
let response =
format!("HTTP/1.1 {code} {reason}\r\nContent-Length: {length}\r\n\r\n{contents}");

stream.write_all(response.as_bytes()).await.unwrap();
}

If you want to run this, you'll need the hello.html and 404.html files from chapter 20.

This looks very similar to our previous single and multithreaded web servers. We have to use tokio::io::AsyncBufReadExt to be able to call buf_reader.lines in handle_connection, because in Tokio lines is defined on the AsyncBufReadExt trait, and similar for tokio::io::AsyncWriteExt and stream.write_all in send_response. We've also replaced some for loops as Rust doesn't (yet) support async for loops. (We also simplified the code for parsing the request, since we weren't actually using any of the headers in our previous examples so we don't bother reading them here.)

This is also very similar to our single threaded version because if you try reloading the "/sleep" route a few times, you'll see that this is only handling a single request at once. Isn't async supposed to fix that for us? The problem is that in our main loop, we're awaiting handle_connection:

    loop {
let (stream, _) = listener.accept().await.unwrap();
handle_connection(stream).await;
}

That await will cause the main loop to suspend until handle_connection completes. If you're an experienced JavaScript programmer, you might think you can just remove the await. This would work in JavaScript, but not in Rust. Rust futures are lazy, meaning they won't make any progress if no one is awaiting them.

info

If you have a look at the definition of the Future trait, you'll see that Future has only one method:

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;

When we await a future, what's happening under the covers is that the runtime will call into poll to get the future to make progress. If the future completes, it will return the Poll::Ready<Output> value. If not, it will return a Poll::Pending. When the Future is ready to make progress again, it will call into the Waker stored in the Context to let the runtime know it should be polled again.

If you're interested in the internals of async and Futures in Rust, this is all covered in much greater detail in Asynchronous Programming in Rust.

In order to fix this problem, we have to create this future, then let Tokio know we'd like it to be polled. Tokio's answer to this is something called a Task. We can spawn a task with tokio::spawn:

    loop {
let (stream, _) = listener.accept().await.unwrap();
tokio::spawn(async move {
handle_connection(stream).await;
});
}

You might have expected spawn to take a closure, but it actually takes a future! Here we're using an async block to create a future, and the move keyword to move ownership of the stream into that block. We could also have rewritten this as:

    loop {
let (stream, _) = listener.accept().await.unwrap();
let f = handle_connection(stream);
tokio::spawn(f);
}

But the async block is more idiomatic. spawn returns a tokio::task::JoinHandle<T> similar to the JoinHandle we get when you spawn a thread. You can await on this handle to wait for the underlying Future to complete.

Tasks are a form of "green thread". Spawning a task is very lightweight, involving only a single allocation and 64 bytes of memory, so you can easily spawn thousands or millions of tasks (which would be ill-advised if we were talking about OS threads).

If you've read this far, you've made it to the end of the book. If you enjoyed it, please star the book on GitHub, or buy me a coffee. Happy Rusting!

- + \ No newline at end of file diff --git a/index.html b/index.html index 74772ec..725431d 100644 --- a/index.html +++ b/index.html @@ -4,13 +4,13 @@ The Rust Book (Abridged) | The rs Book - +
-

The Rust Book (Abridged)

v0.2.1 - Draft

By Jason Walton

Based on "The Rust Programming Language" by Steve Klabnik and Carol Nichols.

PDF version of this book is available here.

What is this?

This is an abridged - or perhaps a better word would be condensed - version of "The Rust Programming Language" (AKA "the Rust Book"). This is not an original work - all the chapter names and examples in this book have been copied verbatim from the original, but all of the prose has been rewritten from scratch, leaving out anything that's not about learning Rust. This book is about 1/2 the length of the original, but I don't think it is missing anything that an experienced software developer wouldn't already know.

The Rust Book is a great resource for learning Rust, especially if you're new to programming. If you fall into this category, then I strongly suggest you put this book down and go read it instead. But... the Rust Book is quite wordy. If you're already familiar with one or more other programming languages, then you are likely already familiar with a lot of the concepts the book covers, and you might benefit from this shorter version. If you are already familiar with ideas like the stack and the heap, with test driven development, with the DRY principle, then this might be a better read.

This isn't meant to be a criticism of the Rust Book. It's excellent and well written, and there's a reason why it's highly recommended. The problem here is not with the original book, but more a mismatch when it comes to intended audience.

What's different about this book?

As mentioned above, the chapter names in this book are all the same as in the original, and in many cases the subsections in each chapter are the same. In most cases examples have been copied directly from the original. Keeping the original structure and examples hopefully makes it easy to jump back and forth between this book and the original, in case there are places where this book is unclear or covers concepts you are not familiar with.

Where the original would build up a code example piece by piece, in most cases this version presents the finished code so you can read through it, and then points out some interesting parts. Where possible I've tried to add in material I think an advanced reader would find interesting. In some places this explains things in a different way than the original. This also adds an extra bonus chapter about async programming!

I have a great deal of experience in TypeScript, Java, C/C++, Go, and a few other languages. I spent about two weeks putting this book together, reading the original, condensing it, and researching parts that weren't clear. Hopefully someone finds this useful! But I am new to Rust so if you find something that doesn't make sense, please feel free to raise an issue.

This book was written entirely by a human - none of this is generated by ChatGPT.

If you enjoy this book, please give it a star on GitHub.

Table of Contents

(This version of this book is based on commit c06006).

- +

The Rust Book (Abridged)

v0.3.0 - Draft

By Jason Walton

Based on "The Rust Programming Language" by Steve Klabnik and Carol Nichols.

PDF version of this book is available here.

What is this?

This is an abridged - or perhaps a better word would be condensed - version of "The Rust Programming Language" (AKA "the Rust Book"). This is not an original work - all the chapter names and examples in this book have been copied verbatim from the original, but all of the prose has been rewritten from scratch, leaving out anything that's not about learning Rust. This book is about 1/2 the length of the original, but I don't think it is missing anything that an experienced software developer wouldn't already know.

The Rust Book is a great resource for learning Rust, especially if you're new to programming. If you fall into this category, then I strongly suggest you put this book down and go read it instead. But... the Rust Book is quite wordy. If you're already familiar with one or more other programming languages, then you are likely already familiar with a lot of the concepts the book covers, and you might benefit from this shorter version. If you are already familiar with ideas like the stack and the heap, with test driven development, with the DRY principle, then this might be a better read.

This isn't meant to be a criticism of the Rust Book. It's excellent and well written, and there's a reason why it's highly recommended. The problem here is not with the original book, but more a mismatch when it comes to intended audience.

What's different about this book?

As mentioned above, the chapter names in this book are all the same as in the original, and in many cases the subsections in each chapter are the same. In most cases examples have been copied directly from the original. Keeping the original structure and examples hopefully makes it easy to jump back and forth between this book and the original, in case there are places where this book is unclear or covers concepts you are not familiar with.

Where the original would build up a code example piece by piece, in most cases this version presents the finished code so you can read through it, and then points out some interesting parts. Where possible I've tried to add in material I think an advanced reader would find interesting. In some places this explains things in a different way than the original. This also adds an extra bonus chapter about async programming!

I have a great deal of experience in TypeScript, Java, C/C++, Go, and a few other languages. I spent about two weeks putting this book together, reading the original, condensing it, and researching parts that weren't clear. Hopefully someone finds this useful! But I am new to Rust so if you find something that doesn't make sense, please feel free to raise an issue.

This book was written entirely by a human - none of this is generated by ChatGPT.

If you enjoy this book, please give it a star on GitHub.

Table of Contents

(This version of this book is based on commit c06006).

+ \ No newline at end of file diff --git a/zz-appendix/appendix-01-keywords/index.html b/zz-appendix/appendix-01-keywords/index.html index 14d955d..588c371 100644 --- a/zz-appendix/appendix-01-keywords/index.html +++ b/zz-appendix/appendix-01-keywords/index.html @@ -4,7 +4,7 @@ Appendix A: Keywords | The rs Book - + @@ -34,7 +34,7 @@ has a try function, you’ll need to use the raw identifier syntax, r#try in this case, to call that function from your 2018 edition code. See Appendix E for more information on editions.

- + \ No newline at end of file diff --git a/zz-appendix/appendix-02-operators/index.html b/zz-appendix/appendix-02-operators/index.html index af8b67f..3635a60 100644 --- a/zz-appendix/appendix-02-operators/index.html +++ b/zz-appendix/appendix-02-operators/index.html @@ -4,7 +4,7 @@ Appendix B: Operators and Symbols | The rs Book - + @@ -21,7 +21,7 @@ parameters.

Table B-4: Generics

SymbolExplanation
path<...>Specifies parameters to generic type in a type (e.g., Vec<u8>)
path::<...>, method::<...>Specifies parameters to generic type, function, or method in an expression; often referred to as turbofish (e.g., "42".parse::<i32>())
fn ident<...> ...Define generic function
struct ident<...> ...Define generic structure
enum ident<...> ...Define generic enumeration
impl<...> ...Define generic implementation
for<...> typeHigher-ranked lifetime bounds
type<ident=type>A generic type where one or more associated types have specific assignments (e.g., Iterator<Item=T>)

Table B-5 shows symbols that appear in the context of constraining generic type parameters with trait bounds.

Table B-5: Trait Bound Constraints

SymbolExplanation
T: UGeneric parameter T constrained to types that implement U
T: 'aGeneric type T must outlive lifetime 'a (meaning the type cannot transitively contain any references with lifetimes shorter than 'a)
T: 'staticGeneric type T contains no borrowed references other than 'static ones
'b: 'aGeneric lifetime 'b must outlive lifetime 'a
T: ?SizedAllow generic type parameter to be a dynamically sized type
'a + trait, trait + traitCompound type constraint

Table B-6 shows symbols that appear in the context of calling or defining macros and specifying attributes on an item.

Table B-6: Macros and Attributes

SymbolExplanation
#[meta]Outer attribute
#![meta]Inner attribute
$identMacro substitution
$ident:kindMacro capture
$(…)…Macro repetition
ident!(...), ident!{...}, ident![...]Macro invocation

Table B-7 shows symbols that create comments.

Table B-7: Comments

SymbolExplanation
//Line comment
//!Inner line doc comment
///Outer line doc comment
/*...*/Block comment
/*!...*/Inner block doc comment
/**...*/Outer block doc comment

Table B-8 shows symbols that appear in the context of using tuples.

Table B-8: Tuples

SymbolExplanation
()Empty tuple (aka unit), both literal and type
(expr)Parenthesized expression
(expr,)Single-element tuple expression
(type,)Single-element tuple type
(expr, ...)Tuple expression
(type, ...)Tuple type
expr(expr, ...)Function call expression; also used to initialize tuple structs and tuple enum variants
expr.0, expr.1, etc.Tuple indexing

Table B-9 shows the contexts in which curly braces are used.

Table B-9: Curly Brackets

ContextExplanation
{...}Block expression
Type {...}struct literal

Table B-10 shows the contexts in which square brackets are used.

Table B-10: Square Brackets

ContextExplanation
[...]Array literal
[expr; len]Array literal containing len copies of expr
[type; len]Array type containing len instances of type
expr[expr]Collection indexing. Overloadable (Index, IndexMut)
expr[..], expr[a..], expr[..b], expr[a..b]Collection indexing pretending to be collection slicing, using Range, RangeFrom, RangeTo, or RangeFull as the "index"
- + \ No newline at end of file diff --git a/zz-appendix/appendix-03-derivable-traits/index.html b/zz-appendix/appendix-03-derivable-traits/index.html index 8cf84f3..37b499d 100644 --- a/zz-appendix/appendix-03-derivable-traits/index.html +++ b/zz-appendix/appendix-03-derivable-traits/index.html @@ -4,7 +4,7 @@ Appendix C: Derivable Traits | The rs Book - + @@ -104,7 +104,7 @@ Option<T> instances, for example. If the Option<T> is None, the method unwrap_or_default will return the result of Default::default for the type T stored in the Option<T>.

- + \ No newline at end of file diff --git a/zz-appendix/appendix-04-useful-development-tools/index.html b/zz-appendix/appendix-04-useful-development-tools/index.html index 86c1cbe..bab962c 100644 --- a/zz-appendix/appendix-04-useful-development-tools/index.html +++ b/zz-appendix/appendix-04-useful-development-tools/index.html @@ -4,7 +4,7 @@ Appendix D - Useful Development Tools | The rs Book - + @@ -38,7 +38,7 @@ for installation instructions, then install the language server support in your particular IDE. Your IDE will gain abilities such as autocompletion, jump to definition, and inline errors.

- + \ No newline at end of file diff --git a/zz-appendix/appendix-05-editions/index.html b/zz-appendix/appendix-05-editions/index.html index a040b2f..2b1cdfc 100644 --- a/zz-appendix/appendix-05-editions/index.html +++ b/zz-appendix/appendix-05-editions/index.html @@ -4,7 +4,7 @@ Appendix E - Editions | The rs Book - + @@ -42,7 +42,7 @@ Guide is a complete book about editions that enumerates the differences between editions and explains how to automatically upgrade your code to a new edition via cargo fix.

- + \ No newline at end of file diff --git a/zz-appendix/appendix-06-licenses/index.html b/zz-appendix/appendix-06-licenses/index.html index d15b5e9..98f6329 100644 --- a/zz-appendix/appendix-06-licenses/index.html +++ b/zz-appendix/appendix-06-licenses/index.html @@ -4,13 +4,13 @@ Licenses | The rs Book - +

Licenses

Ferris the Crab was created by Karen Rustad Tölva, and is used here under public domain.

This book is an abridged edition of "The Rust Programming Language", by Steve Klabnik and Carol Nichols, with contributions from the Rust Community. The source for the original book can be found in https://github.com/rust-lang/book. This book contains many code samples that were copied directly from the original book, as well as several appendices which were copied verbatim, which requires us to reproduce the licenses from the original Rust book here:

Copyright (c) 2010 The Rust Project Developers

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
                              Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

1. Definitions.

"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.

"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.

"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.

"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.

"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.

"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.

"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).

"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.

"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."

"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.

2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.

3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.

4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:

(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and

(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and

(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and

(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.

You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.

5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.

6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.

7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.

8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.

9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

END OF TERMS AND CONDITIONS

APPENDIX: How to apply the Apache License to your work.

To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright 2010 The Rust Project Developers

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- + \ No newline at end of file