├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── hello-world ├── README.md ├── node │ └── hello-word.js └── rust │ └── hello_world.rs ├── http-requests ├── README.md ├── node │ ├── dist │ │ ├── index.d.ts │ │ ├── index.d.ts.map │ │ ├── index.js │ │ └── index.js.map │ ├── package-lock.json │ ├── package.json │ └── src │ │ ├── index.ts │ │ └── tsconfig.json └── rust │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ └── main.rs ├── package-manager ├── README.md ├── cargo-site.png ├── dependencies │ ├── node │ │ ├── dist │ │ │ ├── index.d.ts │ │ │ ├── index.d.ts.map │ │ │ ├── index.js │ │ │ └── index.js.map │ │ ├── package-lock.json │ │ ├── package.json │ │ └── src │ │ │ ├── index.ts │ │ │ └── tsconfig.json │ └── rust │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── src │ │ └── main.rs ├── meta-data │ ├── node │ │ ├── package-lock.json │ │ ├── package.json │ │ └── src │ │ │ └── index.js │ └── rust │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── src │ │ └── main.rs ├── npm-site.png └── publishing │ ├── node │ ├── .gitignore │ ├── .npmignore │ ├── LICENSE │ ├── README.md │ ├── package-lock.json │ ├── package.json │ └── src │ │ ├── index.ts │ │ └── tsconfig.json │ └── rust │ ├── .gitignore │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ └── src │ └── lib.rs ├── parse-json ├── README.md ├── node │ ├── dist │ │ ├── index.d.ts │ │ ├── index.d.ts.map │ │ ├── index.js │ │ └── index.js.map │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── index.ts │ │ └── tsconfig.json │ └── typings │ │ ├── globals │ │ └── node │ │ │ ├── index.d.ts │ │ │ └── typings.json │ │ └── index.d.ts └── rust │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ └── main.rs ├── read-files ├── README.md ├── node │ ├── hello.txt │ ├── package-lock.json │ ├── package.json │ └── src │ │ ├── index.ts │ │ └── tsconfig.json └── rust │ ├── Cargo.lock │ ├── Cargo.toml │ ├── hello.txt │ └── src │ └── main.rs ├── setup ├── README.md ├── node-site.png ├── rust-site.png └── vscode-site.png └── write-files ├── README.md ├── node ├── dist │ ├── index.d.ts │ ├── index.d.ts.map │ ├── index.js │ └── index.js.map ├── hello-world.txt ├── hello.txt ├── package-lock.json ├── package.json ├── src │ ├── index.ts │ └── tsconfig.json └── world.txt └── rust ├── Cargo.lock ├── Cargo.toml ├── hello-world.txt ├── hello.txt ├── src └── main.rs └── world.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | node_modules 3 | target 4 | /.idea/ 5 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "bungcip.better-toml", 4 | "esbenp.prettier-vscode", 5 | "rust-lang.rust" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "prettier.arrowParens": "always", 4 | "prettier.singleQuote": true 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust for Node developers 2 | 3 | > An introduction to the Rust programming language for Node developers. 4 | 5 | > 💡 **2nd edition.** I initially wrote this tutorial in the summer of 2016. Rust 1.0 was roughly a year old back than. This tutorial stayed quite popular over time even though I haven't added new chapters. As years passed by the Rust (and Node) ecosystem evolved further and this tutorial wasn't up-to-date with all changes. With the recent release of [_"Rust 2018"_](https://blog.rust-lang.org/2018/12/06/Rust-1.31-and-rust-2018.html) (which I'll explain later in more depth) I took the opportunity to update this tutorial as well. Enjoy the read! 🎉 6 | 7 | Hi there, I'm Donald. [I'm a JavaScript developer](https://github.com/donaldpipowitch) who wants to learn Rust and as a part of this process I'll write here about my learnings. But what is Rust and **why do I want to learn it**? Rust is a systems programming language like C or C++, but with influences from functional programming languages and even scripting languages like JavaScript. It _feels_ very modern - which is no surprise, because it is a relatively young language. [It went 1.0 in 2015!](http://blog.rust-lang.org/2015/05/15/Rust-1.0.html) That doesn't only mean it is _fun to write_, because it has less clutter to carry around, it is also _fun to use_, because it has a modern toolchain with a great package manager. Rust's most unique feature is probably the compile-time safety check: it catches errors like segfaults without introducing a garbage collector. Or to phrase it differently: maximum safety with maximum performance. 8 | 9 | Probably even more important than its features is the ecosystem and the community behind the language. Rust really shines here - especially for people who love the web. The language was (and still is) heavily influenced by developers from Mozilla. They have written [`servo`](https://github.com/servo/servo), a modern browser engine, in Rust and [parts of Firefox are now running Rust code](https://hacks.mozilla.org/2017/11/entering-the-quantum-era-how-firefox-got-fast-again-and-where-its-going-to-get-faster/). [Rust is also great for authoring WebAssembly code](https://www.rust-lang.org/what/wasm) (short: _Wasm_), a [binary format for the web](https://webassembly.org/), which is [supported in Firefox, Edge, Chrome and Safari](https://caniuse.com/#feat=wasm). 10 | 11 | To summarize: Rust is a young modern language with a great tooling and ecosystem, good safety and performance promises and which can be used for a lot of different projects - from low level tasks to command line tools and even web projects. 12 | 13 | Before we dive into our tutorial we want to look at least once into a real Rust file: 14 | 15 | ```rust 16 | fn main() { 17 | println!("Hello World!"); 18 | } 19 | ``` 20 | 21 | The JavaScript equivalent _could roughly_ look like this: 22 | 23 | ```js 24 | function main() { 25 | console.log('Hello World!'); 26 | } 27 | ``` 28 | 29 | Nothing too scary, right? The `!` behind `println` could be a bit confusing, but don't mind it for now. Just think of it as a special function. 30 | 31 | How do we move on from here? First I'll guide you how my current setup looks like to use Node and Rust. Many people seemed to like that I introduce some convenient tooling and IDE configurations _before_ actually speaking about Rust itself. But you can skip this chapter, if you want. After the setup step I'll create several kitchen sink like examples - first with Node and then with Rust. I'll try to explain them as best as I can, but _don't_ expect in-depth explanations in every case. Don't forget that I'm trying to learn Rust - _just like you_. Probably you need to explain _me_ some things, too! And before I forget it: my Node examples will be written in [TypeScript](https://www.typescriptlang.org/)! Writing them in TypeScript will make it a little bit easier to compare some examples to Rust. 32 | 33 | One word about the structure of this tutorial before we start. Every chapter has its own directory. If a chapter has sub-chapters they also have sub-directories. And if a (subd-)chapter contains code examples, you'll find a `node` and a `rust` directory which contain all the code of this (sub-)chapter. (One example: The chapter [_"Package Manager"_](package-manager/README.md) can be found inside [`package-manager/`](package-manager). It has the sub-chapter [_"Publishing"_](package-manager/README.md#publishing) and the corresponding code examples can be found in [`package-manager/publishing/node/`](package-manager/publishing/node) and [`package-manager/publishing/rust/`](package-manager/publishing/rust).) 34 | 35 | # Table of contents 36 | 37 | 1. [Setup](setup/README.md) 38 | 2. [Hello World](hello-world/README.md) 39 | 3. [Package Manager](package-manager/README.md) 40 | 4. [Read files](read-files/README.md) 41 | 5. [Write files](write-files/README.md) 42 | 6. [HTTP requests](http-requests/README.md) 43 | 7. [Parse JSON](parse-json/README.md) 44 | 45 | Thank you for reading this tutorial. ♥ 46 | 47 | I highly appreciate pull requests for grammar and spelling fixes as I'm not a native speaker. Thank you! 48 | -------------------------------------------------------------------------------- /hello-world/README.md: -------------------------------------------------------------------------------- 1 | # Hello world! 2 | 3 | Ah, yes! A classic _"Hello world!"_ example. This one will be really quick, but I'll show you an important difference between Node and Rust. 4 | 5 | Create a [`hello-world.js`](hello-world/node/hello-world.js) containing nothing more than this line: 6 | 7 | ```js 8 | console.log('Hello world!'); 9 | ``` 10 | 11 | And now run this file with Node: 12 | 13 | ```bash 14 | $ node hello-word.js 15 | Hello world! 16 | ``` 17 | 18 | _Yeah!_ You'll see `Hello world!` in your console. 19 | 20 | Now we'll do the same for Rust. Create a [`hello_world.rs`](hello-world/rust/hello_world.rs) with the following content: 21 | 22 | ```rust 23 | fn main() { 24 | println!("Hello world!"); 25 | } 26 | ``` 27 | 28 | Rust actually needs a special entry point to execute code. This is our `main` function and as you can see a function in Rust is declared like in JavaScript, but with the `fn` keyword instead of `function`. It is important to call the function `main` or the Rust compiler will throw an error. In Rust we typically use 4 spaces to indent code inside a function, while 2 spaces are more common for JavaScript projects. (But thankfully this is covered by prettier anyway and it could be covered by `rustfmt` when [my feature request will be solved](https://github.com/rust-lang/rls/issues/1198).) Rust also recommends `snake_case` naming style for directories and files while I think `kebab-case` is most common in JavaScript projects. `println` isn't a simple function call, but a macro - which is indicated by the `!`. For now think of a macro as some code which is transformed into other code at compile time. Last but not least you _need_ to wrap your string inside `"`, not `'`. In JavaScript there is no difference between `"` and `'` to create strings (and [many](https://github.com/feross/standard) prefer to use `'`, even though it's not prettiers default). In Rust a `"` creates a _string literal_ while `'` creates a _character literal_ which means it only accepts a _single character_. You could write `println!("H");` or `println!('H');` and both would compile, but `println!('Hello World!');` throws an error. 29 | 30 | Now compile our code with the following command: 31 | 32 | ```bash 33 | $ rustc hello_world.rs 34 | ``` 35 | 36 | You'll see... nothing on your console. Instead a new file called `hello_world` was created next to `hello_world.rs`. This file contains our compiled code. You can run it like this: 37 | 38 | ```bash 39 | $ ./hello_world 40 | Hello world! 41 | ``` 42 | 43 | Now you'll see `Hello world!` in your console. This shows a fundamental difference between JavaScript/Node and Rust. Rust needs to be compiled before our program can be executed. This extra step is not needed for JavaScript which makes the development cycle with JavaScript sometimes faster. However the compilation step catches a ton of bugs _before_ even executing your program. This can be _so_ useful that you probably want to introduce a similar sanity check to JavaScript - for example by using TypeScript. There is another big benefit: we can easily share our compiled `hello_world` program with other developers _without_ the need for them to have Rust installed. This is not possible with Node scripts. Everyone who wants to run our `hello-world.js` needs to have Node installed and in a version which is supported by our script. 44 | 45 | In the next chapter I'll introduce you to package managing. I choose to talk about this topic, because it is the last topic related to "project configuration" before we can actually focus on small programming examples to learn the language itself. 46 | 47 | --- 48 | 49 | ← [prev _"Setup"_](../setup/README.md) | [next _"Package Manager"_](../package-manager/README.md) → 50 | -------------------------------------------------------------------------------- /hello-world/node/hello-word.js: -------------------------------------------------------------------------------- 1 | console.log('Hello world!'); -------------------------------------------------------------------------------- /hello-world/rust/hello_world.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello world!"); 3 | } -------------------------------------------------------------------------------- /http-requests/README.md: -------------------------------------------------------------------------------- 1 | # HTTP requests 2 | 3 | ## Node 4 | 5 | This time we'll learn how we can send HTTP requests with Node and Rust. To show this we'll just make a `GET` on the GitHub API to get a user. For our Node example we start with our usual TypeScript setup known from the other examples. 6 | 7 | Because GitHubs API is served over `https` we'll use Node's `https` module. We could have used a 3rd party lib like [`superagent`](https://github.com/visionmedia/superagent), because for Rust we'll actually use a 3rd party lib called [`hyper`](http://hyper.rs/), but after trying `hyper` I think it is better to just use Node's built-in `https` for comparison, because they are equally low level. 8 | 9 | So this is our basic example: 10 | 11 | ```ts 12 | import { get } from 'https'; 13 | 14 | const host = 'api.github.com'; 15 | const path = '/users/donaldpipowitch'; 16 | 17 | get({ host, path }, (res) => { 18 | let buf = ''; 19 | res.on('data', (chunk) => (buf = buf + chunk)); 20 | res.on('end', () => console.log(`Response: ${buf}`)); 21 | }).on('error', (err) => { 22 | throw `Couldn't send request.`; 23 | }); 24 | ``` 25 | 26 | We import the `get` function from `https`. We declare a `host` and `path` (no need to set the protocol, when we already use the `https` module). After that we call `get` and pass an options object (containing our `host` and `path`) and callback which accepts a response object (`res`) as the _first_ parameter. Yes, `get` doesn't follow the usual callback style pattern of Node where the first param is an error and the second param is a result. It is more low level than that. Instead we have an request object (the return value of `get`) and a response object (`res`) which are both event emitters. We listen for `error` events on the request object and in case of an error we just `throw` `Couldn't send request.` to exit our program. 27 | 28 | We listen for `data` events on the response object and collect every new `chunk` of data in a string called `buf`. In the case of an `end` event on the response object we know we have our whole response body and log `buf`. 29 | 30 | Let us test this program: 31 | 32 | ``` 33 | $ npm run -s start 34 | Response: Request forbidden by administrative rules. Please make sure your request has a User-Agent header (http://developer.github.com/v3/#user-agent-required). Check https://developer.github.com for other possible causes. 35 | ``` 36 | 37 | What's going on here? It turns out that we need to set a _user agent_ in our HTTP headers so GitHub allows us to a `GET` on its API. If you follow the link in the error you can read that the user agent should be the name of our GitHub account or the name of our app. 38 | 39 | But there's more. This response is actually an _error_, because its status code is `403`. But it was not catched by our `error` event listener. Why? The `error` event listener is only called when our request couldn't be made. In this case we actually _made_ a request. The request worked correctly in technical terms. But the _server said_ that our request contains errors (the missing user agent). We need to manually throw client or server errors, if we are interested in them. Let's do that before we add a user agent. To do so I introduce two small helper functions: 40 | 41 | ```diff 42 | import { get } from 'https'; 43 | 44 | const host = 'api.github.com'; 45 | const path = '/users/donaldpipowitch'; 46 | 47 | +function isClientError(statusCode: number) { 48 | + return statusCode >= 400 && statusCode < 500; 49 | +} 50 | 51 | +function isServerError(statusCode: number) { 52 | + return statusCode >= 500; 53 | +} 54 | 55 | get({ host, path }, (res) => { 56 | let buf = ''; 57 | res.on('data', (chunk) => buf = buf + chunk); 58 | 59 | - res.on('end', () => console.log(`Response: ${buf}`)); 60 | + res.on('end', () => { 61 | + console.log(`Response: ${buf}`); 62 | + 63 | + if (isClientError(res.statusCode)) { 64 | + throw `Got client error: ${res.statusCode}`; 65 | + } 66 | + if (isServerError(res.statusCode)) { 67 | + throw `Got server error: ${res.statusCode}`; 68 | + } 69 | + }); 70 | }).on('error', (err) => { 71 | throw `Couldn't send request.`; 72 | }); 73 | ``` 74 | 75 | Test the program again: 76 | 77 | ``` 78 | $ npm run -s start 79 | Response: Request forbidden by administrative rules. Please make sure your request has a User-Agent header (http://developer.github.com/v3/#user-agent-required). Check https://developer.github.com for other possible causes. 80 | 81 | 82 | /Users/pipo/workspace/rust-for-node-developers/http-requests/node/dist/index.js:21 83 | throw "Got client error: " + res.statusCode; 84 | ^ 85 | Got client error: 403 86 | ``` 87 | 88 | The program exits correctly and logs the status code. It is not beautiful, but it works. 89 | 90 | Now we just need to add our headers. We use the name of this repository as our user agent: `Mercateo/rust-for-node-developers`. 91 | 92 | ```diff 93 | import { get } from 'https'; 94 | 95 | const host = 'api.github.com'; 96 | const path = '/users/donaldpipowitch'; 97 | 98 | function isClientError(statusCode: number) { 99 | return statusCode >= 400 && statusCode < 500; 100 | } 101 | 102 | function isServerError(statusCode: number) { 103 | return statusCode >= 500; 104 | } 105 | 106 | +const headers = { 107 | + 'User-Agent': 'Mercateo/rust-for-node-developers' 108 | +}; 109 | 110 | -get({ host, path }, (res) => { 111 | +get({ host, path, headers }, (res) => { 112 | let buf = ''; 113 | res.on('data', (chunk) => (buf = buf + chunk)); 114 | 115 | res.on('end', () => { 116 | console.log(`Response: ${buf}`); 117 | 118 | if (isClientError(res.statusCode)) { 119 | throw `Got client error: ${res.statusCode}`; 120 | } 121 | if (isServerError(res.statusCode)) { 122 | throw `Got server error: ${res.statusCode}`; 123 | } 124 | }); 125 | }).on('error', (err) => { 126 | throw `Couldn't send request.`; 127 | }); 128 | ``` 129 | 130 | Check the program: 131 | 132 | ``` 133 | $ npm run -s start 134 | Response: {"login":"donaldpipowitch","id":1152805, ... 135 | ``` 136 | 137 | It works! You'll see a nice big blob of JSON as our response. This is fine for now. I'll write about handling JSON in the next example. 138 | 139 | Time to move to Rust. 140 | 141 | ## Rust 142 | 143 | As I said earlier we'll use a 3rd party lib called [`hyper`](http://hyper.rs/) for our Rust example. It is the _de facto_ standard for working with HTTP(S) in Rust. But I have to tell you something about asynchronous APIs (like doing network requests) in Rust. 144 | 145 | As you may know JavaScript is a single-threaded lanugage and all asynchronous APIs are driven by [an event loop](https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop). You probably also know about [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises) which allow us to model asynchronous control flow and that the `async`/`await` syntax for functions are build on top of Promises. (Disclaimer: There are more ways to handle asynchronity than by using Promises. In our own chapter from above we used callbacks for example.) You may also recall that it took a while until Promises and `async`/`await` were fully standardized and landed natively in JavaScript. 146 | 147 | Rust is basically in the same progress. There will be an `async` keyword and an `await!` macro which will behave similar to JavaScripts `async`/`await`. There is also a concept similar to Promises which is called [Futures](https://github.com/rust-lang-nursery/futures-rs), but they aren't fully standardized yet. You can follow the whole progress on [_"Are we `async` yet?"_](https://areweasyncyet.rs/). But there are also key differences to JavaScript: Rust is multi-threaded and has no built-in event loop. 148 | 149 | So everything you'll now see can and will probably change in the future. Take it with a grain of salt. 150 | 151 | To use `hyper` we need to append this section to our `Cargo.toml`. This will also add [`hyper-tls`](https://github.com/hyperium/hyper-tls) which we need for HTTPS. It looks like this was kept modular to allow different resolution strategies to get certificates. 152 | 153 | ```toml 154 | [dependencies] 155 | hyper = "0.12.21" 156 | hyper-tls = "0.3.1" 157 | ``` 158 | 159 | Let's start with an high level overview of our file this time: 160 | 161 | ```rust 162 | use hyper::rt::{run, Future, Stream}; 163 | use hyper::{Client, Request}; 164 | use hyper_tls::HttpsConnector; 165 | use std::str::from_utf8; 166 | 167 | fn main() { 168 | run(get()); 169 | } 170 | 171 | fn get() -> impl Future { 172 | // more code here 173 | } 174 | ``` 175 | 176 | `hyper`'s `run` function - as far as I understand - solves a similar problem as our event loop in JavaScript. We can pass a Future to `run`, so it knows when the asynchronous APIs have "finished" its job. Futures are currently bundled in `hyper`. (Think about all the 3rd party Promise libs like [Bluebird](https://github.com/petkaantonov/bluebird) we used - and sometimes still use - in the JavaScript world, before Promises were added to the language.) 177 | 178 | As you can see I create a custom function called `get` which returns "something" that `impl`'emented Future. Just like Promises which can resolve or reject our Future can represent a successful (`Item`) or erroneous (`Error`) outcome. But in both cases I'm not really interested in the result, so I'll just return `()`. 179 | 180 | Now we only need to look into our `get` function. 181 | 182 | ```rust 183 | fn get() -> impl Future { 184 | // 4 is number of blocking DNS threads 185 | let https = HttpsConnector::new(4).unwrap(); 186 | 187 | let client = Client::builder().build(https); 188 | 189 | let req = Request::get("https://api.github.com/users/donaldpipowitch") 190 | .header("User-Agent", "Mercateo/rust-for-node-developers") 191 | .body(hyper::Body::empty()) 192 | .unwrap(); 193 | 194 | // more coded here 195 | } 196 | ``` 197 | 198 | First we create our `HttpsConnector` which is allows up to 4 blocking DNS threads. (If you don't understand that, it's fine. We don't need to technically understand that part to understand the example in general. The `HttpsConnector` just enables us to make HTTPS requests.) As you can see I used `unwrap()` here instead of our `?` operator. Currently it is not possible out of the box to use `?` inside a function which returns a Future. Sadly I couldn't find an RFC or discussions about this topic and I'd like to see how to handle this case more gracefully. 199 | 200 | The next thing we create is a `Client` which will make the actual requests. It is uses the [`builder` pattern](https://en.wikipedia.org/wiki/Builder_pattern) which is _really_ popular in the Rust ecosystem in my experience. In this case we just pass our `HttpsConnector` instance to the builder and get a client back. 201 | 202 | Last but not least we configure our request. It will be a `GET` request (that's why we use `Request::get`), we pass a url, we set the `User-Agent` header and set an empty body. 203 | 204 | Now we'll need to pass our configured request to the client so it actually executes the request and we can handle the response. 205 | 206 | ```rust 207 | fn get() -> impl Future { 208 | // previous code 209 | 210 | client 211 | .request(req) 212 | .and_then(|res| { 213 | let status = res.status(); 214 | 215 | let buf = res.into_body().concat2().wait().unwrap(); 216 | println!("Response: {}", from_utf8(&buf).unwrap()); 217 | 218 | if status.is_client_error() { 219 | panic!("Got client error: {}", status.as_u16()); 220 | } 221 | if status.is_server_error() { 222 | panic!("Got server error: {}", status.as_u16()); 223 | } 224 | 225 | Ok(()) 226 | }) 227 | .map_err(|_err| panic!("Couldn't send request.")) 228 | } 229 | ``` 230 | 231 | The client gets our request _and then_ we handle the response (by using `and_then` which is similar to `then` in a Promise) or handle the error (by using `map_err` which is similar to `catch` in a Promise) if the request couldn't been made at all. In the error case we just `panic!`. We'll do that for all error cases, that's why I just wrote `Error = ()` in the function signature as we don't return a useful error. 232 | 233 | If a request could be made we'll get the response (`res`). The response has several useful methods to extract the status (`status()`) and body (`into_body().concat2().wait()`, because the body comes in chunks - similar to our Node example). With `status.is_client_error()` and `status.is_server_error()` we can easily check for 4xx and 5xx error codes. `status.as_u16()` returns the plain status code (e.g. `403`) without the canonical reason (e.g. `Forbidden`). Note that we return `Ok(())` instead of just `()`, [because `()` doesn't implement the Future trait, but `Result` does it](https://stackoverflow.com/questions/46625376/the-trait-bound-futuresfuture-is-not-satisfied-when-using-tcpconnectionn/49331886#49331886). 234 | 235 | If you run the program now you should get the same output as we did in the Node example. 236 | 237 | ``` 238 | $ cargo -q run 239 | Response: {"login":"donaldpipowitch","id":1152805, ... 240 | ``` 241 | 242 | This is great, but I actually hid a problem from you. In my original code I had written this: 243 | 244 | ```diff 245 | - let status = res.status(); 246 | - 247 | - let buf = res.into_body().concat2().wait().unwrap(); 248 | - println!("Response: {}", from_utf8(&buf).unwrap()); 249 | 250 | + let buf = res.into_body().concat2().wait().unwrap(); 251 | + println!("Response: {}", from_utf8(&buf).unwrap()); 252 | + 253 | + let status = res.status(); 254 | ``` 255 | 256 | This made more sense in my opinion as I used the `status` _after_ I used the `buf`. But this throws a compiler error: 257 | 258 | ``` 259 | 26 | let buf = res.into_body().concat2().wait().unwrap(); 260 | | --- value moved here 261 | ... 262 | 29 | let status = res.status(); 263 | | ^^^ value borrowed here after move 264 | ``` 265 | 266 | This error occurs when an attempt is made to use a variable after its contents have been "moved" elsewhere. This is Rust's _ownership_ model which we already mentioned in a [previous chapter](../read-files/README.md) in action. For me it's by far the most complex new concept to understand in Rust. There can only be one "owner" of some content or data at a single point in time. Originally `res` held the data corresponding to response body, but by calling `res.into_body()` the ownership is transferred and is given to our `buf` variable at the end. After this line no one is allowed to access `res` anymore. It wouldn't be a problem if we could create a _reference_ to the body by calling `res.body()` (similar to `res.status()` which gives us a reference to the status), but I'm not sure if it's possible to get the actual body content as a string from a _referenced_ body. 267 | 268 | Nice. In the next example I'll show you how to actually handle a JSON response. 269 | 270 | --- 271 | 272 | ← [prev _"Write files"_](../write-files/README.md) | [next _"Parse JSON"_](../parse-json/README.md) → 273 | -------------------------------------------------------------------------------- /http-requests/node/dist/index.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | //# sourceMappingURL=index.d.ts.map -------------------------------------------------------------------------------- /http-requests/node/dist/index.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /http-requests/node/dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var https_1 = require("https"); 4 | var host = 'api.github.com'; 5 | var path = '/users/donaldpipowitch'; 6 | function isClientError(statusCode) { 7 | return statusCode >= 400 && statusCode < 500; 8 | } 9 | function isServerError(statusCode) { 10 | return statusCode >= 500; 11 | } 12 | var headers = { 13 | // 'user-agent': 'Mercateo/rust-for-node-developers' 14 | }; 15 | https_1.get({ host: host, path: path, headers: headers }, function (res) { 16 | var buf = ''; 17 | res.on('data', function (chunk) { return (buf = buf + chunk); }); 18 | res.on('end', function () { 19 | console.log("Response: " + buf); 20 | if (isClientError(res.statusCode)) { 21 | throw "Got client error: " + res.statusCode; 22 | } 23 | if (isServerError(res.statusCode)) { 24 | throw "Got server error: " + res.statusCode; 25 | } 26 | }); 27 | }).on('error', function (err) { 28 | throw "Couldn't send request."; 29 | }); 30 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /http-requests/node/dist/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAAA,+BAA4B;AAE5B,IAAM,IAAI,GAAG,gBAAgB,CAAC;AAC9B,IAAM,IAAI,GAAG,wBAAwB,CAAC;AAEtC,SAAS,aAAa,CAAC,UAAkB;IACvC,OAAO,UAAU,IAAI,GAAG,IAAI,UAAU,GAAG,GAAG,CAAC;AAC/C,CAAC;AAED,SAAS,aAAa,CAAC,UAAkB;IACvC,OAAO,UAAU,IAAI,GAAG,CAAC;AAC3B,CAAC;AAED,IAAM,OAAO,GAAG;AACd,oDAAoD;CACrD,CAAC;AAEF,WAAG,CAAC,EAAE,IAAI,MAAA,EAAE,IAAI,MAAA,EAAE,OAAO,SAAA,EAAE,EAAE,UAAC,GAAG;IAC/B,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,UAAC,KAAK,IAAK,OAAA,CAAC,GAAG,GAAG,GAAG,GAAG,KAAK,CAAC,EAAnB,CAAmB,CAAC,CAAC;IAE/C,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE;QACZ,OAAO,CAAC,GAAG,CAAC,eAAa,GAAK,CAAC,CAAC;QAEhC,IAAI,aAAa,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE;YACjC,MAAM,uBAAqB,GAAG,CAAC,UAAY,CAAC;SAC7C;QACD,IAAI,aAAa,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE;YACjC,MAAM,uBAAqB,GAAG,CAAC,UAAY,CAAC;SAC7C;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,EAAE,UAAC,GAAG;IACjB,MAAM,wBAAwB,CAAC;AACjC,CAAC,CAAC,CAAC"} -------------------------------------------------------------------------------- /http-requests/node/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "write-files", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/node": { 8 | "version": "10.12.18", 9 | "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz", 10 | "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==", 11 | "dev": true 12 | }, 13 | "typescript": { 14 | "version": "3.2.2", 15 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.2.tgz", 16 | "integrity": "sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==", 17 | "dev": true 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /http-requests/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "write-files", 3 | "version": "1.0.0", 4 | "private": true, 5 | "main": "dist/index.js", 6 | "typings": "dist/index.d.ts", 7 | "scripts": { 8 | "start": "npm run build && node dist", 9 | "build": "tsc --build src" 10 | }, 11 | "devDependencies": { 12 | "@types/node": "^10.12.18", 13 | "typescript": "^3.2.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /http-requests/node/src/index.ts: -------------------------------------------------------------------------------- 1 | import { get } from 'https'; 2 | 3 | const host = 'api.github.com'; 4 | const path = '/users/donaldpipowitch'; 5 | 6 | function isClientError(statusCode: number) { 7 | return statusCode >= 400 && statusCode < 500; 8 | } 9 | 10 | function isServerError(statusCode: number) { 11 | return statusCode >= 500; 12 | } 13 | 14 | const headers = { 15 | 'User-Agent': 'Mercateo/rust-for-node-developers' 16 | }; 17 | 18 | get({ host, path, headers }, (res) => { 19 | let buf = ''; 20 | res.on('data', (chunk) => (buf = buf + chunk)); 21 | 22 | res.on('end', () => { 23 | console.log(`Response: ${buf}`); 24 | 25 | if (isClientError(res.statusCode)) { 26 | throw `Got client error: ${res.statusCode}`; 27 | } 28 | if (isServerError(res.statusCode)) { 29 | throw `Got server error: ${res.statusCode}`; 30 | } 31 | }); 32 | }).on('error', (err) => { 33 | throw `Couldn't send request.`; 34 | }); 35 | -------------------------------------------------------------------------------- /http-requests/node/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "outDir": "../dist", 5 | "declaration": true, 6 | "sourceMap": true, 7 | "declarationMap": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /http-requests/rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "http-requests" 3 | version = "1.0.0" 4 | publish = false 5 | edition = "2018" 6 | 7 | [dependencies] 8 | hyper = "0.12.21" 9 | hyper-tls = "0.3.1" -------------------------------------------------------------------------------- /http-requests/rust/src/main.rs: -------------------------------------------------------------------------------- 1 | use hyper::rt::{run, Future, Stream}; 2 | use hyper::{Client, Request}; 3 | use hyper_tls::HttpsConnector; 4 | use std::str::from_utf8; 5 | 6 | fn main() { 7 | run(get()); 8 | } 9 | 10 | fn get() -> impl Future { 11 | // 4 is number of blocking DNS threads 12 | let https = HttpsConnector::new(4).unwrap(); 13 | 14 | let client = Client::builder().build(https); 15 | 16 | let req = Request::get("https://api.github.com/users/donaldpipowitch") 17 | .header("User-Agent", "Mercateo/rust-for-node-developers") 18 | .body(hyper::Body::empty()) 19 | .unwrap(); 20 | 21 | client 22 | .request(req) 23 | .and_then(|res| { 24 | let status = res.status(); 25 | 26 | let buf = res.into_body().concat2().wait().unwrap(); 27 | println!("Response: {}", from_utf8(&buf).unwrap()); 28 | 29 | if status.is_client_error() { 30 | panic!("Got client error: {}", status.as_u16()); 31 | } 32 | if status.is_server_error() { 33 | panic!("Got server error: {}", status.as_u16()); 34 | } 35 | 36 | Ok(()) 37 | }) 38 | .map_err(|_err| panic!("Couldn't send request.")) 39 | } 40 | -------------------------------------------------------------------------------- /package-manager/README.md: -------------------------------------------------------------------------------- 1 | # Package Manager 2 | 3 | ## Meta Data 4 | 5 | Node and Rust are both installed together with a package manager. 6 | 7 | * Node's package manager is called _npm_, its packages are called _node modules_ and its official website is [npmjs.com](https://www.npmjs.com/). 8 | * Rust's package manager is called _Cargo_, its packages are called _crates_ and its official website is [crates.io](https://crates.io/). 9 | 10 | ![official npm website](./npm-site.png) 11 | ![official Cargo website](./cargo-site.png) 12 | 13 | If you followed my [Setup](../setup/README.md) and you use Node v10.14.2 your npm version is probably 6.4.1. You can check this by running the following command: 14 | 15 | ```bash 16 | $ npm --version 17 | 6.4.1 18 | ``` 19 | 20 | If you want to update npm you can run this: 21 | 22 | ```bash 23 | $ npm install -g npm 24 | ``` 25 | 26 | Let us check our installed Cargo version. I have 1.31.0: 27 | 28 | ```bash 29 | $ cargo --version 30 | cargo 1.31.0 (339d9f9c8 2018-11-16) 31 | ``` 32 | 33 | As you can see it prints the same version as our installed Rust compiler. It's best practice to update both tools in tandem with `rustup`: 34 | 35 | ```bash 36 | $ rustup update 37 | ``` 38 | 39 | The _manifest file_ - the file which contains meta-data of your project like its name, its version, its dependencies and so on - is called `package.json` in the Node world and `Cargo.toml` in Rust. We'll now add manifest files to our [_"Hello World"_ examples](../hello-world/README.md), we created earlier. 40 | 41 | Lets have a look at a typical `package.json` without dependencies: 42 | 43 | ```json 44 | { 45 | "name": "hello-world", 46 | "version": "0.1.0", 47 | "author": "John Doe (https://github.com/john.doe)", 48 | "contributors": [ 49 | "Jane Doe (https://github.com/jane.doe)" 50 | ], 51 | "private": true, 52 | "description": "This is just a demo.", 53 | "license": "MIT OR Apache-2.0", 54 | "keywords": ["demo", "test"], 55 | "homepage": "https://github.com/john.doe/hello-world", 56 | "repository": { 57 | "type": "git", 58 | "url": "https://github.com/john.doe/hello-world" 59 | }, 60 | "bugs": "https://github.com/john.doe/hello-world/issues" 61 | } 62 | ``` 63 | 64 | The `Cargo.toml` looks really similar (besides being a `.toml` and not `.json`): 65 | 66 | ```toml 67 | [package] 68 | name = "hello-world" 69 | version = "0.1.0" 70 | authors = ["John Doe ", 71 | "Jane Doe "] 72 | publish = false 73 | description = "This is just a demo." 74 | license = "MIT OR Apache-2.0" 75 | keywords = ["demo", "test"] 76 | homepage = "https://github.com/john.doe/hello-world" 77 | repository = "https://github.com/john.doe/hello-world" 78 | documentation = "https://github.com/john.doe/hello-world" 79 | ``` 80 | 81 | So what have we here? Both manifest formats offer `name` and `version` fields which are **mandatory**. Adding the authors of a project is slightly different between the modules, but optional for both. npm assumes a main `author` for every package and multiple `contributors` while in Cargo you just fill an `authors` array. The `authors` field is actually **mandatory** for Cargo. As a value you use a string with the pattern `name (url)` in npm and `name ` in Cargo. (Maybe `(url)` will be added in the future, but [currently it is not used](https://github.com/rust-lang/cargo/issues/2736) by anyone in Cargo.) Note that `` and `(url)` are optional and that `name` doesn't have to be a person. You can just use you company name as well or something like `my cool team`. 82 | 83 | If you don't accidentally want to publish a module to a public repository you can do that with either `"private": true` in npm or `publish = false` in Cargo. You can optionally add a `description` field to describe your project. (While you technically could use [MarkDown](https://commonmark.org/) in your descriptions, the support is spotty in both ecosystems and it isn't rendered properly most of the time.) 84 | 85 | To add a single license you write `"license": "MIT"` in npm and `license = "MIT"` in Cargo. In both cases the value needs to be an [SPDX license identifier](https://spdx.org/licenses/). If you use multiple licences you can use an [SPDX license expression](https://spdx.org/spdx-specification-21-web-version#h.jxpfx0ykyb60) like `"license": "MIT OR Apache-2.0"` for npm or `license = "MIT OR Apache-2.0"` for Cargo. 86 | 87 | You can also optionally add multiple `keywords`, so your package can be found more easily in the official repositories. 88 | 89 | You can add a link to your `homepage` and `repository` in both files (with a slightly different format for `repository`). npm allows you to add a link to reports `bugs` while Cargo allows you to add a link to find `documentation`. 90 | 91 | ## Build tool 92 | 93 | Cargo can be used to build your Rust project and you can add custom build scripts to npm as well. (Remember that you don't _need_ a build step in the Node ecosystem, but if you rely on something like TypeScript it is needed. I'll show this in more in-depth when I introduce TypeScript to our Node projects.) 94 | 95 | For now I just added a `main` and `scripts.start` field to our [`package.json`](meta-data/node/package.json): 96 | 97 | ```json 98 | { 99 | // ...your previous code 100 | "main": "src/index.js", 101 | "scripts": { 102 | "start": "node src/index.js" 103 | } 104 | } 105 | ``` 106 | 107 | A `main` field points to your packages entry file. This is the file that will be loaded, if someone requires _your_ package. `scripts.start` is a convention to point to the file which should be loaded, if you want to _run_ your package by calling `$ npm start`: 108 | 109 | ```bash 110 | $ npm start 111 | 112 | > hello-world@0.1.0 start /Users/pipo/workspace/rust-for-node-developers/package-manager/meta-data/node 113 | > node src 114 | 115 | Hello world! 116 | ``` 117 | 118 | To ignore the npm output use `-s` (for `--silent`): 119 | 120 | ```bash 121 | $ npm -s start 122 | Hello world! 123 | ``` 124 | 125 | In this case the entry file to our package specified in `main` and the file which should be run if you call `$ npm start` point to the same file, but this doesn't have to be the case. Additionally you could specify [multiple executable files in a field called `bin`](https://docs.npmjs.com/files/package.json#bin). 126 | 127 | Cargo on the other hand will look for a `src/main.rs` file to build and/or run and if it finds a `src/lib.rs` file, it will build a library which than can be required by a different crate. 128 | 129 | You run your Rust project with Cargo like this: 130 | 131 | ```bash 132 | $ cargo run 133 | Compiling hello-world v0.1.0 (/Users/pipo/workspace/rust-for-node-developers/package-manager/meta-data/rust) 134 | Finished dev [unoptimized + debuginfo] target(s) in 0.61s 135 | Running `target/debug/hello-world` 136 | Hello world! 137 | ``` 138 | 139 | To ignore the Cargo output use `-q` (for `--quiet`): 140 | 141 | ```bash 142 | $ cargo -q run 143 | Hello world! 144 | ``` 145 | 146 | You'll see that Cargo created a new file in your directory: a `Cargo.lock`. (It also placed your compiled program in a `target` directory.) The `Cargo.lock` file basically works like a `package-lock.json` in the Node world (or a [`yarn.lock`](https://yarnpkg.com/lang/en/docs/yarn-lock/), if you use yarn instead of npm), but is also generated during builds. Just to be complete let us generate a `package-lock.json` as well: 147 | 148 | ```bash 149 | $ npm install 150 | npm notice created a lockfile as package-lock.json. You should commit this file. 151 | up to date in 0.924s 152 | found 0 vulnerabilities 153 | ``` 154 | 155 | This should be your `package-lock.json`: 156 | 157 | ```json 158 | { 159 | "name": "hello-world", 160 | "version": "0.1.0", 161 | "lockfileVersion": 1 162 | } 163 | ``` 164 | 165 | This should be your `Cargo.lock`: 166 | 167 | ```toml 168 | [[package]] 169 | name = "hello-world" 170 | version = "0.1.0" 171 | ``` 172 | 173 | Both files become more interesting if you use dependencies in your project to ensure everyone uses the same dependencies (and dependencies of dependencies) at any time. 174 | 175 | Before we move on let us make a slight adjustment to our [`Cargo.toml`](meta-data/rust/Cargo.toml) by adding the line `edition = "2018"`. This will add support for [_Rust 2018_](https://blog.rust-lang.org/2018/12/06/Rust-1.31-and-rust-2018.html) to our package. _Editions_ are a feature which allow us to make backwards incompatible changes in Rust without introducing new major versions. You basically opt-in into new language features per package and your dependencies can be a mix of _different_ editions. Currently there are two different editions available: _Rust 2015_ (which is the default) and _Rust 2018_ (which is the newest). 176 | 177 | ## Publishing 178 | 179 | Before we learn how to install and use dependencies we will actually publish a package that we can require afterwards. It will just export a `Hello world!` string. I call both packages `rfnd-hello-world` (with `rfnd` as an abbreviation for _"Rust for Node developers"_). npm offers namespacing of modules called [_scoped packages_](https://docs.npmjs.com/misc/scope). If I'd have used that feature our module could have looked like this: `@rfnd/hello-world`. Cargo doesn't support namespacing and this is an [intended limitation](https://internals.rust-lang.org/t/crates-io-package-policies/1041). By the way... even if `snake_case` is preferred for files and directories in Rust the module names in Cargo should use `kebab-case` [by convention](https://users.rust-lang.org/t/is-it-good-practice-to-call-crates-hello-world-hello-world-or-does-it-not-matter/6114). This is probably used most of time in npm world, too. 180 | 181 | I'll introduce TypeScript for our Node module in this step. It isn't _that_ necessary currently, but it'll simplify some comparisons between Node and Rust in the next chapters when I use types or modern language features like [ES2015 modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import). First we need to install TypeScript as a `devDependency`, which is a dependency we only need for development, but not for our package itself at runtime: 182 | 183 | ```bash 184 | $ npm install --save-dev typescript 185 | ``` 186 | 187 | To build the project we need to call the TypeScript compiler (`tsc`) by adding a new `build` field in the `scripts` object of the `package.json`. We also add a `prepublishOnly` entry which _always_ runs the build process before we'll publish our module: 188 | 189 | ```json 190 | { 191 | "scripts": { 192 | "build": "tsc --build src", 193 | "prepublishOnly": "npm run build" 194 | } 195 | } 196 | ``` 197 | 198 | By using `--build src` the TypeScript will look for a [`tsconfig.json` in the `src/` directory](package-manager/publishing/node/src/tsconfig.json) which configures the actual output. It looks like this: 199 | 200 | ```json 201 | { 202 | "compilerOptions": { 203 | "module": "commonjs", 204 | "declaration": true, 205 | "outDir": "../dist", 206 | "sourceMap": true, 207 | "declarationMap": true 208 | } 209 | } 210 | ``` 211 | 212 | Note that we'll generate CommonJS modules (because this is a Node project), it will generate `declaration` files (so other TypeScript projects know our types and interfaces even when they use the generated JavaScript files), all JavaScript and declaration files will be placed in a `dist` folder and finally we generate Source Maps to map the generated JavaScript back to the original TypeScript code (useful for debugging). 213 | 214 | This also means that the `main` field in our [`package.json`](package-manager/publishing/node/package.json) now points to `dist/index.js` - our compiled JavaScript code. And we also add a `typings` field which shows other modules where our generated declarations are stored. 215 | 216 | ```json 217 | { 218 | "main": "dist/index.js", 219 | "typings": "dist/index.d.ts" 220 | } 221 | ``` 222 | 223 | Note that we don't want to commit our `node_modules` and `dist` to our repository, because these directories contain external or generated code. But be **warned**! If you place these directories in a `.gitignore` npm will _not_ include them in our published package. This okay for `node_modules` (which are _never_ included anyway), but a package without `dist` is pointless. You'll actually need to add an empty `.npmignore`, so npm ignores the `.gitignore`. (A little bit tricky, I know.) You can use the `.npmignore` to ignore files and directories which are committed in your repository, but shouldn't be included in your published package. In our case it'd be fine to include everything. As an alternative you could also explicitly list all files which should be included in a [`files` field](https://docs.npmjs.com/files/package.json#files) in your `package.json`. 224 | 225 | With this setup aside this is our actual package under `index.ts`: 226 | 227 | ```ts 228 | export const HELLO_WORLD = 'Hello world!'; 229 | ``` 230 | 231 | We `export` a `const` with the value `'Hello world!'`. This is ES2015 module syntax and we write our exported variable name in `UPPER_CASES` which is a common convention for constants. Call `$ npm run build` to build your project. 232 | 233 | This is how our generated `dist/index.js` looks: 234 | 235 | ```js 236 | 'use strict'; 237 | exports.__esModule = true; 238 | exports.HELLO_WORLD = 'Hello world!'; 239 | //# sourceMappingURL=index.js.map 240 | ``` 241 | 242 | Nothing fancy. Basically the same code in a different module syntax. The second line tells other tools that it was originaly an ES2015 module. The last line links our file to the corresponding Source Map. 243 | 244 | The generated declaration file `dist/index.d.ts` is also worth a look: 245 | 246 | ```ts 247 | export declare const HELLO_WORLD = 'Hello world!'; 248 | //# sourceMappingURL=index.d.ts.map 249 | ``` 250 | 251 | You see that TypeScript could infer the type of `HELLO_WORLD` as a `'Hello world!'`. This is a _value type_ which is in this case a special variant of the type `string` with the concrete value `'Hello world!'`. 252 | 253 | We didn't need to tell TypeScript the type explicitly, but we _could_ have done that. It would have looked like that: 254 | 255 | ```ts 256 | export const HELLO_WORLD: 'Hello world!' = 'Hello world!'; 257 | ``` 258 | 259 | Or like this, if we'd just want to tell others that it is a string: 260 | 261 | ```ts 262 | export const HELLO_WORLD: string = 'Hello world!'; 263 | ``` 264 | 265 | Great. This is our module. Now it needs to be published. You need to create an account at [npmjs.com](https://www.npmjs.com/signup). If you have done that you'll get a profile like [this](https://www.npmjs.com/~donaldpipowitch). Now call `$ npm login` and enter your credentials from your new account. After that you can just call `$ npm publish`; 266 | 267 | ```bash 268 | $ npm publish 269 | 270 | > rfnd-hello-world@1.0.1 prepublishOnly . 271 | > npm run build 272 | 273 | 274 | > rfnd-hello-world@1.0.1 build /Users/pipo/workspace/rust-for-node-developers/package-manager/publishing/node 275 | > tsc --build src 276 | 277 | # some output from npm notice... 278 | 279 | + rfnd-hello-world@1.0.1 280 | ``` 281 | 282 | Congratulations! 🎉 You successfully created a package which can be seen [here](https://www.npmjs.com/package/rfnd-hello-world). 283 | 284 | Time for Rust! We first create a [`.gitignore`](publishing/rust/.gitignore) with the following content: 285 | 286 | ``` 287 | Cargo.lock 288 | target 289 | ``` 290 | 291 | As pointed out earlier `Cargo.lock` behaves similar to `package-lock.json`, but while the `package-lock.json` can always be committed into your version control the `Cargo.lock` should only be committed [for binary projects, not libraries](https://doc.rust-lang.org/cargo/faq.html#why-do-binaries-have-cargolock-in-version-control-but-not-libraries). Npm ignores the `package-lock.json` in libraries, but Cargo doesn't do the same for `Cargo.lock`. 292 | 293 | The `target` directory will contain generated code, so it is also ignored. 294 | 295 | Actually this is all the setup we need. Now dive into our package (living in [`src/lib.rs`](publishing/rust/src/lib.rs), because this will be a library): 296 | 297 | ```rust 298 | pub const HELLO_WORLD: &str = "Hello world!"; 299 | ``` 300 | 301 | As you can see this line of code in Rust is really similar to our TypeScript code (when we excplicitly set the type to `string`) which looked like this: 302 | 303 | ```ts 304 | export const HELLO_WORLD: string = 'Hello world!'; 305 | ``` 306 | 307 | Let's go through the Rust line of code word for word: 308 | 309 | * `pub` makes our variable _public_ - very much like `export` in JavaScript, so it can be used by other packages. 310 | * `const` in Rust is different than `const` in JavaScript though. In Rust this is a real constant - a value which can't be changed. 311 | * In JavaScript it is a constant _binding_ which means we can't assign another value to the same name (in this case our variable name is `HELLO_WORLD`). But the value itself can be changed, if it is a non-[primitive](https://developer.mozilla.org/en-US/docs/Glossary/Primitive) value. (E.g. `const a = { b: 1 }; a.b = 2;` is possible.) 312 | * Unlike TypeScript we _need_ to declare the type of `HELLO_WORLD` here by adding `&str` or we'll get compiler errors. Rust also supports type inferring, but `const` _always_ requires an [explicit type annotation](https://doc.rust-lang.org/rust-by-example/custom_types/constants.html#constants). 313 | * `&str` is pronounced as _string slice_ (and [as a reminder](../hello-world/README.md) `"Hello world!"` is pronounced as a _string literal_). 314 | * Rust actually has another String type called just `String`. A `&str` has a fixed size and cannot be mutated while a `String` is heap-allocated and has a dynamic size. A `&str` can be easily converted to a `String` with the `to_string` method like this: `"Hello world!".to_string();`. We'll see more of that in later examples, but you can already see methods can be invoked in the same way as we do in JavaScript and that built-in types come with a couple of built-in methods (like `'hello'.toUpperCase()` in JavaScript for example). 315 | 316 | We only need to publish our new crate now. You need to login on [crates.io/](https://crates.io/) with your GitHub account to do so. If you've done that visit your [account settings](https://crates.io/me) to get your API key. Than call `cargo login` and pass your API key: 317 | 318 | ```bash 319 | $ cargo login 320 | ``` 321 | 322 | You can inspect what will be published by packaging your Crate locally like this: 323 | 324 | ```bash 325 | $ cargo package 326 | ``` 327 | 328 | Much like npm Cargo ignores all your directories and files in your `.gitignore`, too. That is fine. We don't need to ignore more files (or less) in this case. (If you _do_ need to change that, you can modify your `Cargo.toml` [as explained in the documentation](https://doc.rust-lang.org/cargo/reference/manifest.html#the-exclude-and-include-fields-optional).) 329 | 330 | Now we only need to publish the crate like this: 331 | 332 | ```bash 333 | $ cargo publish 334 | Updating crates.io index 335 | Packaging rfnd-hello-world v1.0.1 (/Users/pipo/workspace/rust-for-node-developers/package-manager/publishing/rust) 336 | Verifying rfnd-hello-world v1.0.1 (/Users/pipo/workspace/rust-for-node-developers/package-manager/publishing/rust) 337 | Compiling rfnd-hello-world v1.0.1 (/Users/pipo/workspace/rust-for-node-developers/package-manager/publishing/rust/target/package/rfnd-hello-world-1.0.1) 338 | Finished dev [unoptimized + debuginfo] target(s) in 1.48s 339 | Uploading rfnd-hello-world v1.0.1 (/Users/pipo/workspace/rust-for-node-developers/package-manager/publishing/rust) 340 | ``` 341 | 342 | Awesome! Your crate is now published and can be seen [here](https://crates.io/crates/rfnd-hello-world). 343 | 344 | Remember that you can publish your package in the same version only _once_. This is true for Cargo and npm as well. To publish your package again with changes you need to change the version as well. The quickest way which doesn't introduce additional tooling is by just changing the value of `version` in `package.json` or `Cargo.toml` manually. Both communities follow [SemVer-style versioning](https://semver.org/) (more or less). 345 | 346 | This is probably the minimum you need to know to get started in publishing your own packages, but I only scratched the surface. Have a look at the [npm documentation](https://docs.npmjs.com/) and [Cargo documentation](https://doc.rust-lang.org/cargo/index.html) to learn more. 347 | 348 | Now that we published two packages we can try to require them in other projects as dependencies. 349 | 350 | ## Dependencies 351 | 352 | Let us start with Node again to show you how using dependencies work. To be honest... we already used a dependency, right? TypeScript. We added it to the `devDependencies` and use it in every example now: 353 | 354 | ```bash 355 | $ npm install --save-dev typescript 356 | ``` 357 | 358 | `devDependencies` are only needed when we develop our Node application, but not at runtime. We use our recently published package as a real `dependency`. Install it like this: 359 | 360 | ```bash 361 | $ npm install --save rfnd-hello-world 362 | ``` 363 | 364 | You should see the following dependencies in your `package.json`: 365 | 366 | ```json 367 | { 368 | "devDependencies": { 369 | "typescript": "^3.2.2" 370 | }, 371 | "dependencies": { 372 | "rfnd-hello-world": "^1.0.1" 373 | } 374 | } 375 | ``` 376 | 377 | We should also change our `start` script so it behaves similar to `$ cargo run` - build the project and run it: 378 | 379 | ```json 380 | { 381 | "scripts": { 382 | "start": "npm run build && node dist", 383 | "build": "tsc --build src" 384 | } 385 | } 386 | ``` 387 | 388 | The final `package.json` looks pretty much like our previous example, just with less meta data. I'll show it to you, so we are on the same page: 389 | 390 | ```json 391 | { 392 | "name": "use-hello-world", 393 | "version": "0.1.0", 394 | "private": true, 395 | "main": "dist/index.js", 396 | "typings": "dist/index.d.ts", 397 | "scripts": { 398 | "start": "npm run build && node dist", 399 | "build": "tsc --build src" 400 | }, 401 | "devDependencies": { 402 | "typescript": "^3.2.2" 403 | }, 404 | "dependencies": { 405 | "rfnd-hello-world": "^1.0.1" 406 | } 407 | } 408 | ``` 409 | 410 | The `tsconfig.json` is just copy and pasted without modifications. 411 | 412 | We installed our dependencies, now we can use them like this: 413 | 414 | ```ts 415 | import { HELLO_WORLD } from 'rfnd-hello-world'; 416 | 417 | console.log(`Required "${HELLO_WORLD}".`); 418 | ``` 419 | 420 | Let's run our example: 421 | 422 | ```bash 423 | $ npm start 424 | 425 | > use-hello-world@0.1.0 start /Users/pipo/workspace/rust-for-node-developers/package-manager/dependencies/node 426 | > npm run build && node dist 427 | 428 | 429 | > use-hello-world@0.1.0 build /Users/pipo/workspace/rust-for-node-developers/package-manager/dependencies/node 430 | > tsc --build src 431 | 432 | Required "Hello world!". 433 | ``` 434 | 435 | Good. Now we switch to Rust. We can't add dependencies to our project with Cargo without additional tooling. That's why we need to add it to our `Cargo.toml` manually in a section called `[dependencies]`. (You can watch [this issue](https://github.com/rust-lang/cargo/issues/5586) about adding a `$ cargo add ` command which will work similar to `$ npm install --save `.) 436 | 437 | ```bash 438 | [dependencies] 439 | rfnd-hello-world = "1.0.1" 440 | ``` 441 | 442 | The crate will be _automatically_ fetched as soon as we compile our program. Note that using `1.0.1` actually translates to `^1.0.1`! If you want a very specific version you should use `=1.0.1`. 443 | 444 | This is how our [`src/main.rs`](dependencies/rust/src/main.rs) looks like: 445 | 446 | ```rust 447 | use rfnd_hello_world::HELLO_WORLD; 448 | 449 | fn main() { 450 | println!("Required: {}.", HELLO_WORLD); 451 | } 452 | ``` 453 | 454 | Note that even though our external crate is called `rfnd-hello-world` we access it with `rfnd_hello_world`. Aside from the import we do with the `use` keyword, you can see how the string interpolation works with the `println!()` macro where `{}` is a placeholder and we pass the value as the second parameter. (Printing to the terminal can be actually quite complex. Read [this article](https://doc.rust-lang.org/rust-by-example/hello/print.html) to learn more.) In case you didn't know: `console.log()` in Node can behave quite similar. We could rewrite `` console.log(`Required "${HELLO_WORLD}".`); `` to `console.log('Required "%s".', HELLO_WORLD);`. Try it! 455 | 456 | As we use `HELLO_WORLD` just a single time we could also have written the example like this: 457 | 458 | ```rust 459 | fn main() { 460 | println!("Required: {}.", rfnd_hello_world::HELLO_WORLD); 461 | } 462 | ``` 463 | 464 | If `rfnd_hello_world` would expose more than one constant we can use a syntax similar to ES2015 destructing. 465 | 466 | ```rust 467 | use rfnd_hello_world::{HELLO_WORLD, SOME_OTHER_VALUE}; 468 | 469 | fn main() { 470 | println!("Required: {}.", HELLO_WORLD); 471 | println!("Also: {}.", SOME_OTHER_VALUE); 472 | } 473 | ``` 474 | 475 | Nice. Now test your programm: 476 | 477 | ```bash 478 | $ cargo run 479 | Updating registry `https://github.com/rust-lang/crates.io-index` 480 | Compiling use-hello-world v0.1.0 (file:///Users/donaldpipowitch/Workspace/rust-for-node-developers/package-manager/dependencies/rust) 481 | Running `target/debug/use-hello-world` 482 | Required "Hello world!". 483 | ``` 484 | 485 | It works! :tada: 486 | 487 | To summarize: `use rfnd_hello_world::HELLO_WORLD;` (or `use rfnd_hello_world::{HELLO_WORLD};` for multiple imports) works similar to `import { HELLO_WORLD } from 'rfnd-hello-world';`, but we can also inline the "import" as `println!("Required: {}.", rfnd_hello_world::HELLO_WORLD);` which would be very similar to `` console.log(`Required "${require('rfnd-hello-world').HELLO_WORLD}".`); ``. 488 | 489 | --- 490 | 491 | ← [prev _"Hello World"_](../hello-world/README.md) | [next](../read-files/README.md) → 492 | -------------------------------------------------------------------------------- /package-manager/cargo-site.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mercateo/rust-for-node-developers/4a14f616d36f4d9c0f2a7e717f6bf2ac37863541/package-manager/cargo-site.png -------------------------------------------------------------------------------- /package-manager/dependencies/node/dist/index.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | //# sourceMappingURL=index.d.ts.map -------------------------------------------------------------------------------- /package-manager/dependencies/node/dist/index.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /package-manager/dependencies/node/dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var rfnd_hello_world_1 = require("rfnd-hello-world"); 4 | console.log("Required \"" + rfnd_hello_world_1.HELLO_WORLD + "\"."); 5 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /package-manager/dependencies/node/dist/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAAA,qDAA+C;AAE/C,OAAO,CAAC,GAAG,CAAC,gBAAa,8BAAW,QAAI,CAAC,CAAC"} -------------------------------------------------------------------------------- /package-manager/dependencies/node/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "use-hello-world", 3 | "version": "0.1.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "rfnd-hello-world": { 8 | "version": "1.0.1", 9 | "resolved": "https://registry.npmjs.org/rfnd-hello-world/-/rfnd-hello-world-1.0.1.tgz", 10 | "integrity": "sha512-oUqe0rtU0GRhNl6K1c/57+X5wd8r67BxU+kvWdCvquIg2cGJgyE9Fhufp++vCzE+2nmd8yYtSc15EbYqBoGGlw==" 11 | }, 12 | "typescript": { 13 | "version": "3.2.2", 14 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.2.tgz", 15 | "integrity": "sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==", 16 | "dev": true 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /package-manager/dependencies/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "use-hello-world", 3 | "version": "0.1.0", 4 | "private": true, 5 | "main": "dist/index.js", 6 | "typings": "dist/index.d.ts", 7 | "scripts": { 8 | "start": "npm run build && node dist", 9 | "build": "tsc --build src" 10 | }, 11 | "devDependencies": { 12 | "typescript": "^3.2.2" 13 | }, 14 | "dependencies": { 15 | "rfnd-hello-world": "^1.0.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /package-manager/dependencies/node/src/index.ts: -------------------------------------------------------------------------------- 1 | import { HELLO_WORLD } from 'rfnd-hello-world'; 2 | 3 | console.log(`Required "${HELLO_WORLD}".`); -------------------------------------------------------------------------------- /package-manager/dependencies/node/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "outDir": "../dist", 5 | "declaration": true, 6 | "sourceMap": true, 7 | "declarationMap": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /package-manager/dependencies/rust/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "rfnd-hello-world" 3 | version = "1.0.1" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | 6 | [[package]] 7 | name = "use-hello-world" 8 | version = "0.1.0" 9 | dependencies = [ 10 | "rfnd-hello-world 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 11 | ] 12 | 13 | [metadata] 14 | "checksum rfnd-hello-world 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3a3c0501ca84498b0327925c8eba1e18aed8d5c3a08afcbd825c9de01324bc9" 15 | -------------------------------------------------------------------------------- /package-manager/dependencies/rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "use-hello-world" 3 | version = "0.1.0" 4 | publish = false 5 | edition = "2018" 6 | 7 | [dependencies] 8 | rfnd-hello-world = "1.0.1" -------------------------------------------------------------------------------- /package-manager/dependencies/rust/src/main.rs: -------------------------------------------------------------------------------- 1 | use rfnd_hello_world::HELLO_WORLD; 2 | 3 | fn main() { 4 | println!("Required: {}.", HELLO_WORLD); 5 | } 6 | -------------------------------------------------------------------------------- /package-manager/meta-data/node/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world", 3 | "version": "0.1.0", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /package-manager/meta-data/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world", 3 | "version": "0.1.0", 4 | "author": "John Doe (https://github.com/john.doe)", 5 | "contributors": [ 6 | "Jane Doe (https://github.com/jane.doe)" 7 | ], 8 | "private": true, 9 | "description": "This is just a demo.", 10 | "license": "MIT OR Apache-2.0", 11 | "keywords": [ 12 | "demo", 13 | "test" 14 | ], 15 | "homepage": "https://github.com/john.doe/hello-world", 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/john.doe/hello-world" 19 | }, 20 | "bugs": "https://github.com/john.doe/hello-world/issues", 21 | "main": "src/index.js", 22 | "scripts": { 23 | "start": "node src/index.js" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /package-manager/meta-data/node/src/index.js: -------------------------------------------------------------------------------- 1 | console.log('Hello world!'); -------------------------------------------------------------------------------- /package-manager/meta-data/rust/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "hello-world" 3 | version = "0.1.0" 4 | 5 | -------------------------------------------------------------------------------- /package-manager/meta-data/rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello-world" 3 | version = "0.1.0" 4 | authors = ["John Doe ", 5 | "Jane Doe "] 6 | publish = false 7 | description = "This is just a demo." 8 | license = "MIT OR Apache-2.0" 9 | keywords = ["demo", "test"] 10 | homepage = "https://github.com/john.doe/hello-world" 11 | repository = "https://github.com/john.doe/hello-world" 12 | documentation = "https://github.com/john.doe/hello-world" 13 | edition = "2018" 14 | -------------------------------------------------------------------------------- /package-manager/meta-data/rust/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello world!"); 3 | } -------------------------------------------------------------------------------- /package-manager/npm-site.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mercateo/rust-for-node-developers/4a14f616d36f4d9c0f2a7e717f6bf2ac37863541/package-manager/npm-site.png -------------------------------------------------------------------------------- /package-manager/publishing/node/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /package-manager/publishing/node/.npmignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mercateo/rust-for-node-developers/4a14f616d36f4d9c0f2a7e717f6bf2ac37863541/package-manager/publishing/node/.npmignore -------------------------------------------------------------------------------- /package-manager/publishing/node/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /package-manager/publishing/node/README.md: -------------------------------------------------------------------------------- 1 | # rfnd-hello-world 2 | 3 | > This module exports a "Hello world!" string. 4 | 5 | This module is part of ["Rust for Node Developers"](https://github.com/Mercateo/rust-for-node-developers) project. -------------------------------------------------------------------------------- /package-manager/publishing/node/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rfnd-hello-world", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "typescript": { 8 | "version": "3.2.2", 9 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.2.tgz", 10 | "integrity": "sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==", 11 | "dev": true 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /package-manager/publishing/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rfnd-hello-world", 3 | "version": "1.0.1", 4 | "author": "Donald Pipowitch (https://github.com/donaldpipowitch)", 5 | "description": "This module exports a \"Hello world!\" string.", 6 | "license": "Apache-2.0", 7 | "keywords": [ 8 | "demo", 9 | "hello-world" 10 | ], 11 | "homepage": "https://github.com/Mercateo/rust-for-node-developers", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/Mercateo/rust-for-node-developers" 15 | }, 16 | "bugs": "https://github.com/Mercateo/rust-for-node-developers/issues", 17 | "main": "dist/index.js", 18 | "typings": "dist/index.d.ts", 19 | "scripts": { 20 | "build": "tsc --build src", 21 | "prepublishOnly": "npm run build" 22 | }, 23 | "devDependencies": { 24 | "typescript": "^3.2.2" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /package-manager/publishing/node/src/index.ts: -------------------------------------------------------------------------------- 1 | export const HELLO_WORLD = 'Hello world!'; 2 | -------------------------------------------------------------------------------- /package-manager/publishing/node/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "outDir": "../dist", 5 | "declaration": true, 6 | "sourceMap": true, 7 | "declarationMap": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /package-manager/publishing/rust/.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target -------------------------------------------------------------------------------- /package-manager/publishing/rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rfnd-hello-world" 3 | version = "1.0.1" 4 | authors = ["Donald Pipowitch "] 5 | description = "This module exports a \"Hello world!\" string." 6 | license = "Apache-2.0" 7 | keywords = ["demo", "hello-world"] 8 | homepage = "https://github.com/donaldpipowitch/rust-hello-world" 9 | repository = "https://github.com/donaldpipowitch/rust-hello-world" 10 | documentation = "https://github.com/donaldpipowitch/rust-hello-world" 11 | -------------------------------------------------------------------------------- /package-manager/publishing/rust/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /package-manager/publishing/rust/README.md: -------------------------------------------------------------------------------- 1 | # rfnd-hello-world 2 | 3 | > This module exports a "Hello world!" string. 4 | 5 | This module is part of ["Rust for Node Developers"](https://github.com/Mercateo/rust-for-node-developers) project. -------------------------------------------------------------------------------- /package-manager/publishing/rust/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub const HELLO_WORLD: &str = "Hello world!"; -------------------------------------------------------------------------------- /parse-json/README.md: -------------------------------------------------------------------------------- 1 | # Parse JSON 2 | 3 | ## Node 4 | 5 | In our last example we made an HTTPS request against the GitHub API with Node and Rust. That worked fine, but we got back a raw string as JSON. Not very usefull. This time we'll learn how to parse the JSON and extract the information we need. For this example we'll request the repositories of a specific GitHub user and we'll log the name and description of the repository and if it was forked. 6 | 7 | Our [Node example](../http-requests/README.md) doesn't need to change much to achieve that: 8 | 9 | ```diff 10 | import { get } from 'https'; 11 | 12 | const host = 'api.github.com'; 13 | -const path = '/users/donaldpipowitch'; 14 | +const path = '/users/donaldpipowitch/repos'; 15 | 16 | function isClientError(statusCode: number) { 17 | return statusCode >= 400 && statusCode < 500; 18 | } 19 | 20 | function isServerError(statusCode: number) { 21 | return statusCode >= 500; 22 | } 23 | 24 | const headers = { 25 | 'User-Agent': 'Mercateo/rust-for-node-developers' 26 | }; 27 | 28 | +type Repository = { 29 | + name: string; 30 | + description: string | null; 31 | + fork: boolean; 32 | +}; 33 | 34 | get({ host, path, headers }, (res) => { 35 | let buf = ''; 36 | res.on('data', (chunk) => (buf = buf + chunk)); 37 | 38 | res.on('end', () => { 39 | - console.log(`Response: ${buf}`); 40 | 41 | if (isClientError(res.statusCode)) { 42 | throw `Got client error: ${res.statusCode}`; 43 | } 44 | if (isServerError(res.statusCode)) { 45 | throw `Got server error: ${res.statusCode}`; 46 | } 47 | 48 | + const repositories: Repository[] = JSON.parse(buf).map( 49 | + ({ name, description, fork }) => ({ name, description, fork }) 50 | + ); 51 | + console.log('Result is:\n', repositories); 52 | }); 53 | }).on('error', (err) => { 54 | throw `Couldn't send request.`; 55 | }); 56 | ``` 57 | 58 | Our raw response which is stored in `buf` can be easily parsed with the global `JSON` object and its `parse` method. After that we `map` over the returned array to extract just the `name`, `description` and `fork` fields. (The actual response has much more data!) Note that we only _assume_ that `JSON.parse(buf)` returns an array. We are optimistic here, because we think we know the GitHub API, but to be really safe, we should check if our parsed response actually is an array. We _assume_ that `name`, `description` and `fork` exist and are strings, booleans or maybe `null` in case of the `description`, too! Again this is somehow optimistic. GitHub could send us different data. It is up to you as a developer to decide how many safety checks you want to make here. Is this a critical part of your application? How much do you trust GitHub and their API contract? 59 | 60 | We also added a `type` called `Repository` to describe our response format. The parsed response has the type `Repository[]` (which means it is an array containing `Repository`'s and can also be written as `Array`) and is saved in `repositories`. It is _not mandatory_ to tell TypeScript the type of `repositories` in this case, but it would make further usage of `repositories` easier and safer, because TypeScript would check incorrect usage of `repositories` now. Without adding the type explicitly TypeScript would default to treat `respositories` as [`any`](https://www.typescriptlang.org/docs/handbook/basic-types.html#any) which would result in doing no type checks at all when we use `repositories`. 61 | 62 | For the scope of this example it is sufficient not do more runtime checks. Let us test our program: 63 | 64 | ``` 65 | $ npm run -s start 66 | Result is: 67 | [ { name: 'afpre', 68 | description: ' CLI for the AWS Federation Proxy', 69 | fork: true }, 70 | { name: 'ajv', 71 | description: 'The fastest JSON schema Validator. Supports v5 proposals', 72 | fork: true }, 73 | ... 74 | ``` 75 | 76 | It works! Nothing complicated and you probably have done this a thousand times, if you use APIs regularly. 77 | 78 | ## Rust 79 | 80 | The state of art way of deserializing a string to JSON is by using the [`serde`](https://github.com/serde-rs/serde) and [`serde_json`](https://github.com/serde-rs/json) crates. 81 | 82 | Add all three crates to your `Cargo.toml`: 83 | 84 | ```diff 85 | [package] 86 | -name = "http-requests" 87 | +name = "parse-json" 88 | version = "1.0.0" 89 | publish = false 90 | 91 | [dependencies] 92 | hyper = "0.12.21" 93 | hyper-tls = "0.3.1" 94 | +serde = { version = "1.0", features = ["derive"] } 95 | +serde_json = "1.0" 96 | ``` 97 | 98 | What you see here is the possibility to configure a single crate within the `Cargo.toml`. In this case we enabled a feature called `derive` for `serde` which isn't enabled by default. This allows us to automatically deserialize a JSON string into a custom `struct`. 99 | 100 | We do this with a language construct called [_attributes_](https://doc.rust-lang.org/reference/attributes.html). Attributes change the _meaning of an item_ to which they are applied. An item can be a struct declaration for example. They are written as `#[test]` or `#![test]`. `#[test]` would be applied to the _next_ item and `#![test]` would be applied to the _enclosing_ item. E.g.: 101 | 102 | ```rust 103 | #[hello] 104 | struct SomeStruct; 105 | 106 | fn some_function() { 107 | #![world] 108 | } 109 | ``` 110 | 111 | We can pass additional data to attributes (`#[inline(always)]`) or keys and values (`#[cfg(target_os = "macos")]`). 112 | 113 | The attribute we are interested in is called `derive`. It automatically implements certain traits to a custom data structure (in this case a `struct`). The trait we want to derive is called `Deserialize` from the `serde` crate. We'll also derive the build-in `Debug` trait, so we can `println!` our `struct`. A custom `struct` can be created with the `struct` keyword. In our case it has three fields: `name` (which is a `string`), `fork` (which is a `bool`) and `description` (which _maybe_ is a `string`). To express a potentially unavailable value we can use `Option`. `Option` is a little bit like `Result` in the sense that it shows two possible outcomes: `Result` has the successful (`Ok`) and failured (`Err`) cases while `Option` either has _no_ value (the `None` case) or it _has_ a value (the `Some` case). 114 | 115 | Having that said this is how we define our custom `struct` called `Repository`: 116 | 117 | ```rust 118 | #[derive(Deserialize, Debug)] 119 | struct Repository { 120 | name: String, 121 | description: Option, 122 | fork: bool, 123 | } 124 | ``` 125 | 126 | Let's add that to the example from the [previous chapter](../http-requests/README.md) and also parse our string: 127 | 128 | ```diff 129 | use hyper::rt::{run, Future, Stream}; 130 | use hyper::{Client, Request}; 131 | use hyper_tls::HttpsConnector; 132 | +use serde::Deserialize; 133 | use std::str::from_utf8; 134 | 135 | +#[derive(Deserialize, Debug)] 136 | +struct Repository { 137 | + name: String, 138 | + description: Option, 139 | + fork: bool, 140 | +} 141 | 142 | fn main() { 143 | run(get()); 144 | } 145 | 146 | fn get() -> impl Future { 147 | // 4 is number of blocking DNS threads 148 | let https = HttpsConnector::new(4).unwrap(); 149 | 150 | let client = Client::builder().build(https); 151 | 152 | - let req = Request::get("https://api.github.com/users/donaldpipowitch") 153 | + let req = Request::get("https://api.github.com/users/donaldpipowitch/repos") 154 | .header("User-Agent", "Mercateo/rust-for-node-developers") 155 | .body(hyper::Body::empty()) 156 | .unwrap(); 157 | 158 | client 159 | .request(req) 160 | .and_then(|res| { 161 | let status = res.status(); 162 | 163 | - let buf = res.into_body().concat2().wait().unwrap(); 164 | - println!("Response: {}", from_utf8(&buf).unwrap()); 165 | 166 | if status.is_client_error() { 167 | panic!("Got client error: {}", status.as_u16()); 168 | } 169 | if status.is_server_error() { 170 | panic!("Got server error: {}", status.as_u16()); 171 | } 172 | 173 | + let buf = res.into_body().concat2().wait().unwrap(); 174 | + let json = from_utf8(&buf).unwrap(); 175 | + let repositories: Vec = serde_json::from_str(&json).unwrap(); 176 | + println!("Result is:\n{:#?}", repositories); 177 | 178 | Ok(()) 179 | }) 180 | .map_err(|_err| panic!("Couldn't send request.")) 181 | } 182 | ``` 183 | 184 | Two new things can be seen here. 185 | 186 | We used the `Vec` type here, because we get multiple `Repository`'s from the response. ([Remember](../read-files/README.md) that we already used the `vec!` macro in a previous chapter, which created a `Vec`.) 187 | 188 | The other new thing is the usage of `{:#?}` inside `println!`. So far when we logged a value we used the `println!` macro like this: `println!("Log: {}", some_value);`. To do that `some_value` actually needs to implement the [`Display`](https://doc.rust-lang.org/stable/std/fmt/trait.Display.html) trait. Coming from a JavaScript background you can think of implementing the `Display` trait as providing a nicely formatted `toString` on custom data structures. Sadly `Display` can't be derived automatically. But when all fields in a struct implement `Debug`, we can derive it automatically for custom structs. That's why we use it here. It is an easy way to log custom structs. The usage with `println!` is just a little bit different. You use `{:?}` instead of just `{}`. And if you use `{:#?}` the output will be _pretty printed_. (If you're curious the [string formatting](https://doc.rust-lang.org/std/fmt/index.html) in Rust allows you to do even more cool things, like printing numbers with leading zeros.) 189 | Let us try our program: 190 | 191 | ```bash 192 | $ cargo -q run 193 | Result is: 194 | Result is: 195 | [ 196 | Repository { 197 | name: "afpre", 198 | description: Some( 199 | " CLI for the AWS Federation Proxy" 200 | ), 201 | fork: true 202 | }, 203 | Repository { 204 | name: "ajv", 205 | description: Some( 206 | "The fastest JSON schema Validator. Supports v5 proposals" 207 | ), 208 | fork: true 209 | }, 210 | ... 211 | ] 212 | ``` 213 | 214 | Nice. Applaud yourself. You really learned a lot. 215 | 216 | Thank you for reading my articles so far. If you liked them, please let me know. With a little bit of luck I'm able to add new chapters in the future. Maybe about generating WASM and using it in Node Modules? Would you like that? Until then, have a nice day! 👋 217 | 218 | --- 219 | 220 | ← [prev _"HTTP requests"_](../http-requests/README.md) 221 | -------------------------------------------------------------------------------- /parse-json/node/dist/index.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | //# sourceMappingURL=index.d.ts.map -------------------------------------------------------------------------------- /parse-json/node/dist/index.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /parse-json/node/dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var https_1 = require("https"); 4 | var host = 'api.github.com'; 5 | var path = '/users/donaldpipowitch/repos'; 6 | function isClientError(statusCode) { 7 | return statusCode >= 400 && statusCode < 500; 8 | } 9 | function isServerError(statusCode) { 10 | return statusCode >= 500; 11 | } 12 | var headers = { 13 | 'User-Agent': 'Mercateo/rust-for-node-developers' 14 | }; 15 | https_1.get({ host: host, path: path, headers: headers }, function (res) { 16 | var buf = ''; 17 | res.on('data', function (chunk) { return (buf = buf + chunk); }); 18 | res.on('end', function () { 19 | if (isClientError(res.statusCode)) { 20 | throw "Got client error: " + res.statusCode; 21 | } 22 | if (isServerError(res.statusCode)) { 23 | throw "Got server error: " + res.statusCode; 24 | } 25 | var repositories = JSON.parse(buf).map(function (_a) { 26 | var name = _a.name, description = _a.description, fork = _a.fork; 27 | return ({ name: name, description: description, fork: fork }); 28 | }); 29 | console.log('Result is:\n', repositories); 30 | }); 31 | }).on('error', function (err) { 32 | throw "Couldn't send request."; 33 | }); 34 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /parse-json/node/dist/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAAA,+BAA4B;AAE5B,IAAM,IAAI,GAAG,gBAAgB,CAAC;AAC9B,IAAM,IAAI,GAAG,8BAA8B,CAAC;AAE5C,SAAS,aAAa,CAAC,UAAkB;IACvC,OAAO,UAAU,IAAI,GAAG,IAAI,UAAU,GAAG,GAAG,CAAC;AAC/C,CAAC;AAED,SAAS,aAAa,CAAC,UAAkB;IACvC,OAAO,UAAU,IAAI,GAAG,CAAC;AAC3B,CAAC;AAED,IAAM,OAAO,GAAG;IACd,YAAY,EAAE,mCAAmC;CAClD,CAAC;AAQF,WAAG,CAAC,EAAE,IAAI,MAAA,EAAE,IAAI,MAAA,EAAE,OAAO,SAAA,EAAE,EAAE,UAAC,GAAG;IAC/B,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,UAAC,KAAK,IAAK,OAAA,CAAC,GAAG,GAAG,GAAG,GAAG,KAAK,CAAC,EAAnB,CAAmB,CAAC,CAAC;IAE/C,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE;QACZ,IAAI,aAAa,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE;YACjC,MAAM,uBAAqB,GAAG,CAAC,UAAY,CAAC;SAC7C;QACD,IAAI,aAAa,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE;YACjC,MAAM,uBAAqB,GAAG,CAAC,UAAY,CAAC;SAC7C;QAED,IAAM,YAAY,GAAiB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CACpD,UAAC,EAA2B;gBAAzB,cAAI,EAAE,4BAAW,EAAE,cAAI;YAAO,OAAA,CAAC,EAAE,IAAI,MAAA,EAAE,WAAW,aAAA,EAAE,IAAI,MAAA,EAAE,CAAC;QAA7B,CAA6B,CAC/D,CAAC;QACF,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,YAAY,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,EAAE,UAAC,GAAG;IACjB,MAAM,wBAAwB,CAAC;AACjC,CAAC,CAAC,CAAC"} -------------------------------------------------------------------------------- /parse-json/node/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parse-json", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/node": { 8 | "version": "10.12.19", 9 | "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.19.tgz", 10 | "integrity": "sha512-2NVovndCjJQj6fUUn9jCgpP4WSqr+u1SoUZMZyJkhGeBFsm6dE46l31S7lPUYt9uQ28XI+ibrJA1f5XyH5HNtA==", 11 | "dev": true 12 | }, 13 | "typescript": { 14 | "version": "3.2.4", 15 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.4.tgz", 16 | "integrity": "sha512-0RNDbSdEokBeEAkgNbxJ+BLwSManFy9TeXz8uW+48j/xhEXv1ePME60olyzw2XzUqUBNAYFeJadIqAgNqIACwg==", 17 | "dev": true 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /parse-json/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parse-json", 3 | "version": "1.0.0", 4 | "private": true, 5 | "main": "dist/index.js", 6 | "typings": "dist/index.d.ts", 7 | "scripts": { 8 | "start": "npm run build && node dist", 9 | "build": "tsc --build src" 10 | }, 11 | "devDependencies": { 12 | "@types/node": "^10.12.18", 13 | "typescript": "^3.2.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /parse-json/node/src/index.ts: -------------------------------------------------------------------------------- 1 | import { get } from 'https'; 2 | 3 | const host = 'api.github.com'; 4 | const path = '/users/donaldpipowitch/repos'; 5 | 6 | function isClientError(statusCode: number) { 7 | return statusCode >= 400 && statusCode < 500; 8 | } 9 | 10 | function isServerError(statusCode: number) { 11 | return statusCode >= 500; 12 | } 13 | 14 | const headers = { 15 | 'User-Agent': 'Mercateo/rust-for-node-developers' 16 | }; 17 | 18 | type Repository = { 19 | name: string; 20 | description: string | null; 21 | fork: boolean; 22 | }; 23 | 24 | get({ host, path, headers }, (res) => { 25 | let buf = ''; 26 | res.on('data', (chunk) => (buf = buf + chunk)); 27 | 28 | res.on('end', () => { 29 | if (isClientError(res.statusCode)) { 30 | throw `Got client error: ${res.statusCode}`; 31 | } 32 | if (isServerError(res.statusCode)) { 33 | throw `Got server error: ${res.statusCode}`; 34 | } 35 | 36 | const repositories: Repository[] = JSON.parse(buf).map( 37 | ({ name, description, fork }) => ({ name, description, fork }) 38 | ); 39 | console.log('Result is:\n', repositories); 40 | }); 41 | }).on('error', (err) => { 42 | throw `Couldn't send request.`; 43 | }); 44 | -------------------------------------------------------------------------------- /parse-json/node/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "outDir": "../dist", 5 | "declaration": true, 6 | "sourceMap": true, 7 | "declarationMap": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /parse-json/node/typings/globals/node/typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "resolution": "main", 3 | "tree": { 4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/6abf99dda133ff859e16e3520b0b00b6b1239341/node/node.d.ts", 5 | "raw": "registry:dt/node#6.0.0+20160613154055", 6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/6abf99dda133ff859e16e3520b0b00b6b1239341/node/node.d.ts" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /parse-json/node/typings/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /parse-json/rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "parse-json" 3 | version = "1.0.0" 4 | publish = false 5 | edition = "2018" 6 | 7 | [dependencies] 8 | hyper = "0.12.21" 9 | hyper-tls = "0.3.1" 10 | serde = { version = "1.0", features = ["derive"] } 11 | serde_json = "1.0" 12 | -------------------------------------------------------------------------------- /parse-json/rust/src/main.rs: -------------------------------------------------------------------------------- 1 | use hyper::rt::{run, Future, Stream}; 2 | use hyper::{Client, Request}; 3 | use hyper_tls::HttpsConnector; 4 | use serde::Deserialize; 5 | use std::str::from_utf8; 6 | 7 | #[derive(Deserialize, Debug)] 8 | struct Repository { 9 | name: String, 10 | description: Option, 11 | fork: bool, 12 | } 13 | 14 | fn main() { 15 | run(get()); 16 | } 17 | 18 | fn get() -> impl Future { 19 | // 4 is number of blocking DNS threads 20 | let https = HttpsConnector::new(4).unwrap(); 21 | 22 | let client = Client::builder().build(https); 23 | 24 | let req = Request::get("https://api.github.com/users/donaldpipowitch/repos") 25 | .header("User-Agent", "Mercateo/rust-for-node-developers") 26 | .body(hyper::Body::empty()) 27 | .unwrap(); 28 | 29 | client 30 | .request(req) 31 | .and_then(|res| { 32 | let status = res.status(); 33 | 34 | if status.is_client_error() { 35 | panic!("Got client error: {}", status.as_u16()); 36 | } 37 | if status.is_server_error() { 38 | panic!("Got server error: {}", status.as_u16()); 39 | } 40 | 41 | let buf = res.into_body().concat2().wait().unwrap(); 42 | let json = from_utf8(&buf).unwrap(); 43 | let repositories: Vec = serde_json::from_str(&json).unwrap(); 44 | println!("Result is:\n{:#?}", repositories); 45 | 46 | Ok(()) 47 | }) 48 | .map_err(|_err| panic!("Couldn't send request.")) 49 | } 50 | -------------------------------------------------------------------------------- /read-files/README.md: -------------------------------------------------------------------------------- 1 | # Read files 2 | 3 | ## Node 4 | 5 | We'll start creating more complex examples now. We aim to read a [file called `hello.txt`](node/hello.txt) which is located in our project root. The file contains the text `Hello world!` and we log this content to the console. 6 | 7 | Before we start with our Node example we'll install type declarations for Nodes built-in modules. Only then TypeScript knows about modules like `fs` which we'll need to read files. The declarations are available in a package called `@types/node`): 8 | 9 | ```bash 10 | $ npm install --save-dev @types/node 11 | ``` 12 | 13 | Great! With that out of the way we can create our example. 14 | 15 | To read a file and log its content I probably would write a program like this: 16 | 17 | ```ts 18 | import { readFile } from 'fs'; 19 | 20 | readFile('hello.txt', 'utf8', (err, data) => { 21 | if (err) { 22 | console.log(`Couldn't read: ${err.message}`); 23 | process.exit(1); 24 | } else { 25 | console.log(`Content is: ${data}`); 26 | } 27 | }); 28 | ``` 29 | 30 | You import the `readFile` function, pass the file path (`'hello.txt'`) when you call this function, optionally pass an encoding (`'utf8'`) and pass a callback which is called when the file was read _or_ if an error appeared. By convention the first param of this callback is the error object (`err`) - _if_ an error appeared - and the second param is the content of the file (`data`) - if _no_ error appeared. We check both of these cases with an `if`/`else` statement. Note that in the case of an error we log its `message`, a human readable error description. It is not available on every `err` object, but for all typical errors associated with file reading (like `ENOENT: no such file or directory, open 'hello.txt'`, if the file couldn't be found). I also call `process.exit(1);` to stop the execution of the program and mark the `process` as a failure. This is a good style to stop your program and useful if this program is used by other scripts or inside continuous integration. If no error happened we just log our content: `` console.log(`Content is: ${data}`); ``. 31 | 32 | Anyway... let's rewrite this example so it will be easier to compare to our Rust program. Have a look [at the new code](node/src/index.ts): 33 | 34 | ```ts 35 | import { openSync, readSync, fstatSync, Stats } from 'fs'; 36 | 37 | let fileDescriptor: number; 38 | try { 39 | fileDescriptor = openSync('hello.txt', 'r'); 40 | } catch (err) { 41 | console.log(`Couldn't open: ${err.message}`); 42 | process.exit(1); 43 | } 44 | 45 | let stat: Stats; 46 | try { 47 | stat = fstatSync(fileDescriptor); 48 | } catch (err) { 49 | console.log(`Couldn't get stat: ${err.message}`); 50 | process.exit(1); 51 | } 52 | 53 | const buffer = Buffer.alloc(stat.size); 54 | 55 | try { 56 | readSync(fileDescriptor, buffer, 0, stat.size, null); 57 | } catch (err) { 58 | console.log(`Couldn't read: ${err.message}`); 59 | process.exit(1); 60 | } 61 | 62 | let data: string; 63 | try { 64 | data = buffer.toString(); 65 | } catch (err) { 66 | console.log(`Couldn't convert buffer to string: ${err.message}`); 67 | process.exit(1); 68 | } 69 | 70 | console.log(`Content is: ${data}`); 71 | ``` 72 | 73 | The first thing you'll notice is the usage of some `*Sync` functions instead of our asynchronous style we used earlier. Currently Rust only offers synchronous APIs to read and write files in the standard library. While asynchronous APIs _will be_ standardized really soon there are currently no plans to add asynchronous APIs for file operations like reading or writing to the standard library as far as I know. The second thing you'll notice is that we need to _open_ our file now (with `openSync`), before we can _read_ the content (with `readSync`). This is a lot more low-level than our `readFile` function which abstracts this away. But as you know... low-level functions are more powerful in general, too. If you need to read the content of a file in multiple steps or in slices it is better to open a file _once_ and perform all the read steps you need instead of opening the file for every read operation. Note that `openSync` returns a [file descriptor](https://en.wikipedia.org/wiki/File_descriptor) which is a reference to our file. The flag `'r'` tells `openSync` that we just want to read the file later on. In the next step we call `fstatSync` and pass the file descriptor to get the actual `size` of our file. This is needed to initialize our `buffer` which will store our file data when we call `readSync` and to tell `readSync` how _much_ to read. (Remember... with `readSync` we could also just read slices of a file, but in this case we want to read the whole file. That's why we pass `0`, `stat.size` and `null` as the last params. Have [a look at the docs](https://nodejs.org/docs/latest-v10.x/api/fs.html#fs_fs_readsync_fd_buffer_offset_length_position) to learn more about `readSync`.) As the final step we convert our `buffer` to a string. Note that we wrap every `*Sync` call and `buffer.toString` in a `try/catch`. This is analogous to our `if (err) {} else {}` logic in the asynchronous style and mirrors the following Rust example. 74 | 75 | Let us test the program now: 76 | 77 | ```bash 78 | $ npm -s start 79 | Content is: Hello world! 80 | ``` 81 | 82 | Sweet. Now to Rust. 83 | 84 | ## Rust 85 | 86 | Let us have a look at the whole Rust program: 87 | 88 | ```rust 89 | use std::error::Error; 90 | use std::fs::File; 91 | use std::io::Read; 92 | use std::str::from_utf8; 93 | 94 | fn main() { 95 | let mut file = match File::open("hello.txt") { 96 | Err(err) => panic!("Couldn't open: {}", err.description()), 97 | Ok(value) => value, 98 | }; 99 | 100 | let stat = match file.metadata() { 101 | Err(err) => panic!("Couldn't get stat: {}", err.description()), 102 | Ok(value) => value, 103 | }; 104 | 105 | let mut buffer = vec![0; stat.len() as usize]; 106 | 107 | match file.read(&mut buffer) { 108 | Err(err) => panic!("Couldn't read: {}", err.description()), 109 | Ok(_) => (), 110 | }; 111 | 112 | let data = match from_utf8(&buffer) { 113 | Err(err) => panic!("Couldn't convert buffer to string: {}", err.description()), 114 | Ok(value) => value, 115 | }; 116 | 117 | println!("Content is: {}", data); 118 | } 119 | ``` 120 | 121 | I _think_ you can read the code and grasp what it does. It _should_ look very similar to our Node example. Does it work? 122 | 123 | ```bash 124 | $ cargo -q run 125 | Content is: Hello world! 126 | ``` 127 | 128 | Yes! 129 | 130 | Let us step through all lines now. Even some of its imported modules are quite interesting! 131 | 132 | ```rust 133 | use std::error::Error; 134 | ``` 135 | 136 | This is surprising... if you look into our example we actually never use anything like `Error`. Try to remove this line and run `$ cargo -q run`. You'll get this error: 137 | 138 | ``` 139 | error[E0599]: no method named `description` found for type `std::io::Error` in the current scope 140 | --> src/main.rs:7:53 141 | | 142 | 7 | Err(err) => panic!("Couldn't open: {}", err.description()), 143 | | ^^^^^^^^^^^ 144 | | 145 | = help: items from traits can only be used if the trait is in scope 146 | help: the following trait is implemented but not in scope, perhaps add a `use` for it: 147 | | 148 | 1 | use std::error::Error; 149 | | 150 | ``` 151 | 152 | Our `err` is an instance of a so called `struct`, which is a _data structure_. For now you can think of it like an object in JavaScript, but you'll learn more about `struct`'s later. By default our `err` has no method called `description`. It is only available when we add `use std::error::Error;`. Why? 153 | 154 | If you read the error message again you'll see that _items from traits can only be used if the trait is in scope_. Okay. Whatever a _`trait`_ is, it looks like `std::error::Error` actually _is_ a `trait`. The compiler even recommends it for this use case. And if you `use` the `trait` it is added to the _current scope_. 155 | 156 | So what is a trait? To quote [Rust by Example](http://rustbyexample.com/trait.html): _A `trait` is a collection of methods defined for an unknown type: `Self`._ A trait can specify method signatures (like an `interface` in other languages), but it can also provide fully implemented methods, so they become available for that type. 157 | 158 | If I would need to compare this to something in the Node world, I probably would think of manipulating the `prototype`. Think about this example: 159 | 160 | ```ts 161 | import './get-second-item'; 162 | 163 | const two = [1, 2, 3].getSecondItem(); 164 | console.log(two); // logs `2` 165 | ``` 166 | 167 | With `./get-second-item` being: 168 | 169 | ```ts 170 | Array.prototype.getSecondItem = function getSecondItem() { 171 | return this[1]; 172 | }; 173 | ``` 174 | 175 | This is _not_ the same as a Rust trait, but it helps _me_ to understand them. If I don't `import './get-second-item';` I can't call `getSecondItem`. If I don't `use std::error::Error;` I can't call `description`. But while manipulating the `prototype` is a bad practice in JavaScript using `traits` in Rust is very idiomatic. Thanks to features like _scoping_ we don't inherit the problems of manipulating the `prototype`. 176 | 177 | ```rust 178 | use std::fs::File; 179 | ``` 180 | 181 | `std::fs` behaves much like `fs` in Node world. It contains core `struct`'s and `trait`'s for accessing the file system. In this case we only `use` `File` which is a `struct` and has the `open` method to open our file (similar to Nodes `openSync`) on its instances. 182 | 183 | ```rust 184 | use std::io::Read; 185 | ``` 186 | 187 | `Read` is a `trait`, too. It allows us to call `read` on our `File` instance. 188 | 189 | ```rust 190 | use std::str::from_utf8; 191 | ``` 192 | 193 | `std::str::from_utf8` is just a function which we need to convert our `buffer` (actually a slice of bytes) to a string slice (`&str`). 194 | 195 | Next follows our `main` function. The entry point of our program: 196 | 197 | ```rust 198 | fn main() { 199 | // ... 200 | } 201 | ``` 202 | 203 | The first thing we do in `main` is opening a file: 204 | 205 | ```rust 206 | let mut file = match File::open("hello.txt") { 207 | Err(err) => panic!("Couldn't open: {}", err.description()), 208 | Ok(value) => value, 209 | }; 210 | ``` 211 | 212 | Wow! A lot to see here. We declare a variable with `let` called `file`. `file` is marked as `mut` which stands for mutability. You probably know the concept of mutability and immutability already - it _basically_ means that we can change the value of a variable (it is mutable) or not (it is immutable). _Every_ variable in Rust is _immutable by default_. When we read the `file` later on this will change the _reading position_ of `file` internally, so it needs to be `mut`. 213 | 214 | Next is `match File::open("hello.txt") {}`. Let me say this first: Rust has _no_ `try`/`catch` keywords, because you can't `throw` an exception! The possibility of an error is expressed by _types_ instead. `File::open` actually returns the `Result` type. The `Result` type represents either success (`Ok`) or failure (`Err`). For now you can think of it as a synchronous variant of a JavaScript `Promise` which either _fulfills_ (similar to `Ok`) or _rejects_ (similer to `Err`). The last part to understand this code snippet is the `match` keyword used for pattern matching. You can think of `match` as a super powerful `switch`/`case` (which isn't available in Rust at all, because it uses the more powerful `match`). What makes it so powerful? It enforces you to cover _every_ case. It is not possible to forget one. (If you're curious `match` has [even more features](https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html).) 215 | 216 | If `Result` is either `Ok` or `Err`, you need to handle both cases. So we cover both cases. Every case can give us a variable. `Err` can contain an `err` and `Ok` can contain the actual result (`value`). In the case of `Ok` we just _return_ `value` so it saved in `file`. (Yes, we can return values in pattern matching and save them directly to a variable. You don't see a `return` keyword here, so think of `Ok(value) => value` as an automatically invoked `(value) => value` in JavaScript for now.) In the case of `Err` we call `panic!`. Remember that `!` marks a macro - which I introduced as _some code which is transformed into other code at compile time_ earlier. This explanation should still be enough to understand macros for now. `panic!` will log our error message and exit the program. So `panic!("Couldn't open: {}", err.description())` really works very much like `` console.log(`Couldn't get stat: ${err.message}`); process.exit(1); `` in our Node example. 217 | 218 | Now move on to the next piece of code: 219 | 220 | ```rust 221 | let stat = match file.metadata() { 222 | Err(err) => panic!("Couldn't get stat: {}", err.description()), 223 | Ok(value) => value, 224 | }; 225 | ``` 226 | 227 | Nothing completely new here. `file.metadata` returns a `Result` type like `File::open` so we use pattern matching again. If `file.metadata` is succesful we get metadata for our file very much like `fstatSync(file)` in JavaScript. `stat` is not marked as `mut`, because we don't change its values and just read them. (`stat.len()` will give us the size of our file like `stat.size` in our JavaScript example.) 228 | 229 | ```rust 230 | let mut buffer = vec![0; stat.len() as usize]; 231 | ``` 232 | 233 | Here we create a `Vec` (pronounced as _vector_) called `buffer`. `vec!` is a macro to create a `Vec` more easily with an `array`-like syntax. An alternative would be to use `Vec::new()`. I say `array`-like, because Rust actually has `array`'s, too, and they look a lot like JavaScripts arrays (e.g. `[1, 2]`). However they don't behave like JavaScripts arrays. A JavaScript array is much more similar to Rust's `Vec`. `Vec` and `array` can be compared to `String` and `&str` in this regard. A `Vec` and a `String` can have a dynamic size and behave similar to JavaScripts arrays and strings while Rusts `array` and `&str` have a fixed size. 234 | 235 | So we create a `Vec` and use a _repeat expression_ by using `;` in `vec![x; N]`. That means our `Vec` is filled with `0`'s `stat.len()` times. (You can also create `array`'s or vectors with a `,` like `[1, 2, 3]` as you would in JavaScript.) We need to cast `stat.len()` (which is the type `u64`) to `usize`, because `N` needs to be `usize`. This is done with the `as` keyword and really works just [like TypeScript](https://www.typescriptlang.org/docs/handbook/basic-types.html#type-assertions). 236 | 237 | Finally our `buffer` is flagged as `mut`, because it will change its values when we read our file. This is done in the next step: 238 | 239 | ```rust 240 | match file.read(&mut buffer) { 241 | Err(err) => panic!("Couldn't read: {}", err.description()), 242 | Ok(_) => (), 243 | }; 244 | ``` 245 | 246 | We pass `buffer` to `file.read` with `&mut`. That means that `buffer` is passed to `file.read` as a _mutable reference_ (the `&` marks a reference). This is needed to _allow_ `file.read` to change `buffer`. (It is not enough to flag `buffer` as `mut` in general, we need to allow this to other functions or method in every case, where it is intended.) _Allowing_ this is actually a core feature of Rust called [_ownership_](https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html). `file.read` _borrows_ `buffer` for as long as `file.read` _runs_. If it quits our `main` function becomes the owner of `buffer` again. Doing so ensures that only _one_ function is the owner of a piece of memory at a time and prevents data races. This makes Rust so safe. 247 | 248 | `file.read` has no return value which we are interested in, so we do nothing in the `Ok` case: just `Ok(_) => ()`. You can think of this as a [_noop_](https://en.wikipedia.org/wiki/NOP): the `_` in `Ok(_)` is just a placeholder and the last `()` is the so called _unit type_, which is used to mark a meaningless value. (Every function which doesn't return a meaningfull value implicitly returns `()`, just like JavaScript functions return `undefined` by default.) 249 | 250 | Now the last snippet: 251 | 252 | ```rust 253 | let data = match from_utf8(&buffer) { 254 | Err(err) => panic!("Couldn't convert buffer to string: {}", err.description()), 255 | Ok(value) => value, 256 | }; 257 | 258 | println!("Content is: {}", data); 259 | ``` 260 | 261 | Nothing fancy here. We just convert our `buffer` to a `&str` with `from_utf8`. Note that we pass `&buffer` to `from_utf8` which means that `from_utf8` gets a _reference_ (because we used `&`) of `buffer`. So `&` is a _reference_ to a resource and `&mut` is a _mutable reference_ to a resource. `from_utf8` doesn't need to change `buffer`'s values so the reference doesn't need to be mutable. 262 | 263 | At the end we just print out our file content. 264 | 265 | Nice. I hope you could follow the example. Are we done? Well... we could be done. But as we moved our Node example from a higher level `readFile` function to some lower level functions to make it a little bit easier to compare to Rust, we could do the opposite now and use some more higher level function in Rust as well. This is possible, because we read our complete file at once instead of in several steps. 266 | 267 | This is our simplified example: 268 | 269 | ```rust 270 | use std::error::Error; 271 | use std::fs::read_to_string; 272 | 273 | fn main() { 274 | match read_to_string("hello.txt") { 275 | Err(err) => panic!("Couldn't read: {}", err.description()), 276 | Ok(data) => println!("Content is: {}", data), 277 | }; 278 | } 279 | ``` 280 | 281 | The example should be quite explanatory. You just pass the path to a file to `read_to_string` and it returns a `Result`. In the `Ok` case we print out the content of the file. 282 | 283 | We could simplify our example even more with less verbose error handling as you'll see in [the next chapter](../write-files/README.md). 284 | 285 | --- 286 | 287 | ← [prev _"Package Manager"_](../package-manager/README.md) | [next _"Write files"_](../write-files/README.md) → 288 | -------------------------------------------------------------------------------- /read-files/node/hello.txt: -------------------------------------------------------------------------------- 1 | Hello world! -------------------------------------------------------------------------------- /read-files/node/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "read-files", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/node": { 8 | "version": "10.12.18", 9 | "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz", 10 | "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==", 11 | "dev": true 12 | }, 13 | "typescript": { 14 | "version": "3.2.2", 15 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.2.tgz", 16 | "integrity": "sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==", 17 | "dev": true 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /read-files/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "read-files", 3 | "version": "1.0.0", 4 | "private": true, 5 | "main": "dist/index.js", 6 | "typings": "dist/index.d.ts", 7 | "scripts": { 8 | "start": "npm run build && node dist", 9 | "build": "tsc --build src" 10 | }, 11 | "devDependencies": { 12 | "@types/node": "^10.12.18", 13 | "typescript": "^3.2.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /read-files/node/src/index.ts: -------------------------------------------------------------------------------- 1 | import { openSync, readSync, fstatSync, Stats } from 'fs'; 2 | 3 | let fileDescriptor: number; 4 | try { 5 | fileDescriptor = openSync('hello.txt', 'r'); 6 | } catch (err) { 7 | console.log(`Couldn't open: ${err.message}`); 8 | process.exit(1); 9 | } 10 | 11 | let stat: Stats; 12 | try { 13 | stat = fstatSync(fileDescriptor); 14 | } catch (err) { 15 | console.log(`Couldn't get stat: ${err.message}`); 16 | process.exit(1); 17 | } 18 | 19 | const buffer = Buffer.alloc(stat.size); 20 | 21 | try { 22 | readSync(fileDescriptor, buffer, 0, stat.size, null); 23 | } catch (err) { 24 | console.log(`Couldn't read: ${err.message}`); 25 | process.exit(1); 26 | } 27 | 28 | let data: string; 29 | try { 30 | data = buffer.toString(); 31 | } catch (err) { 32 | console.log(`Couldn't convert buffer to string: ${err.message}`); 33 | process.exit(1); 34 | } 35 | 36 | console.log(`Content is: ${data}`); 37 | -------------------------------------------------------------------------------- /read-files/node/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "outDir": "../dist", 5 | "declaration": true, 6 | "sourceMap": true, 7 | "declarationMap": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /read-files/rust/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "read-files" 3 | version = "1.0.0" 4 | 5 | -------------------------------------------------------------------------------- /read-files/rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "read-files" 3 | version = "1.0.0" 4 | publish = false 5 | edition = "2018" -------------------------------------------------------------------------------- /read-files/rust/hello.txt: -------------------------------------------------------------------------------- 1 | Hello world! -------------------------------------------------------------------------------- /read-files/rust/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::fs::File; 3 | use std::io::Read; 4 | use std::str::from_utf8; 5 | 6 | fn main() { 7 | let mut file = match File::open("hello.txt") { 8 | Err(err) => panic!("Couldn't open: {}", err.description()), 9 | Ok(value) => value, 10 | }; 11 | 12 | let stat = match file.metadata() { 13 | Err(err) => panic!("Couldn't get stat: {}", err.description()), 14 | Ok(value) => value, 15 | }; 16 | 17 | let mut buffer = vec![0; stat.len() as usize]; 18 | 19 | match file.read(&mut buffer) { 20 | Err(err) => panic!("Couldn't read: {}", err.description()), 21 | Ok(_) => (), 22 | }; 23 | 24 | let data = match from_utf8(&buffer) { 25 | Err(err) => panic!("Couldn't convert buffer to string: {}", err.description()), 26 | Ok(value) => value, 27 | }; 28 | 29 | println!("Content is: {}", data); 30 | } 31 | 32 | // or a little bit simpler: 33 | 34 | // use std::error::Error; 35 | // use std::fs::read_to_string; 36 | 37 | // fn main() { 38 | // match read_to_string("hello.txt") { 39 | // Err(err) => panic!("Couldn't read: {}", err.description()), 40 | // Ok(data) => println!("Content is: {}", data), 41 | // }; 42 | // } -------------------------------------------------------------------------------- /setup/README.md: -------------------------------------------------------------------------------- 1 | # Setup 2 | 3 | ## Install Node and Rust 4 | 5 | I use Mac OS X 10.14 to write my examples. You probably have Node installed already, but just so we are on the same page we'll do it again together. You can install a single Node version like I describe now or install a tool to manage multiple Node versions as I describe in the next section and which is my recommended way. 6 | 7 | The installation process is really simple. Just visit Nodes official site [nodejs.org](https://nodejs.org/en/) and download the installer for Node. At the time of writing the latest version with _long time support_ (short: _LTS_) is [v10.14.2](https://nodejs.org/dist/v10.14.2/node-v10.14.2.pkg) and the most _current_ version is [v11.4.0](https://nodejs.org/dist/v11.4.0/node-v11.4.0.pkg). I'll use v10.14.2, because the LTS release is often the lowest common denominator used by most library authors. A minor release happens about every _two weeks_ and you can find the release notes [in the Node blog](https://nodejs.org/en/blog/). 8 | 9 | ![official Node website](./node-site.png) 10 | 11 | If you write `node --version` in your terminal you should see `v10.14.2` in your window: 12 | 13 | ```bash 14 | $ node --version 15 | v10.14.2 16 | ``` 17 | 18 | Rust's installation process is a little bit different. If you visit Rust's official site on [rust-lang.org](https://www.rust-lang.org/) and click on the _Install_ link in the header you'll not immediately see a link to install Rust, but to install a tool called `rustup`. While it is still possible to _just_ install Rust via an installer just as we did for Node, it is no longer the recommended way. So we go straight to the next section. 19 | 20 | Just as a small addition: At the time of writing the latest stable Rust version is 1.31.0. A minor release happens about every _six weeks_ and you can find the release notes [in the Rust blog](https://blog.rust-lang.org/). Currently there are no LTS releases like they can be seen in the Node world, because every minor release was backwards compatible as no major release has happened after 1.0 so far. 21 | 22 | ![official Rust website](./rust-site.png) 23 | 24 | ## Manage multiple versions of Node and Rust 25 | 26 | If you use Node or Rust for more serious work chances are pretty high that you want to upgrade easily to new versions or switch back to an old version, if you open an old project. You can do this for Node with a tool called [`nvm`](https://github.com/creationix/nvm) and for Rust with the already mentioned [`rustup`](https://github.com/rust-lang-nursery/rustup.rs). While `rustup` is the officially recommended way to manage Rust, `nvm` is a community project. 27 | 28 | The installation process for both tools is very easy. To download and install `nvm` you just write this in your terminal: 29 | 30 | ```bash 31 | $ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash 32 | ``` 33 | 34 | After that you need to close and reopen your terminal. Check if the installation was successful with `nvm --version`: 35 | 36 | ```bash 37 | $ nvm --version 38 | 0.33.11 39 | ``` 40 | 41 | You can install a specific Node version with `nvm install` and passing a version like `v10.14.2`: 42 | 43 | ```bash 44 | $ nvm install v10.14.2 45 | ``` 46 | 47 | This will install `v10.14.2` and switch this version. You can manually switch between versions with `npm use` like this: 48 | 49 | ```bash 50 | $ nvm use v4.4.5 51 | Now using node v4.4.5 (npm v2.15.5) 52 | ``` 53 | 54 | If you want to set a default version which will be used everytime you fire up your terminal you can run `nvm alias default` and pass a version: 55 | 56 | ```bash 57 | $ nvm alias default v4.4.5 58 | default -> v4.4.5 59 | ``` 60 | 61 | To download and install `rustup` you just write this in your terminal: 62 | 63 | ```bash 64 | $ curl https://sh.rustup.rs -sSf | sh 65 | ``` 66 | 67 | You'll be prompted with three options after `rustup` was installed: 68 | 69 | ```bash 70 | 1) Proceed with installation (default) 71 | 2) Customize installation 72 | 3) Cancel installation 73 | ``` 74 | 75 | Just choose `1` to install Rust. 76 | 77 | ```bash 78 | stable installed - rustc 1.31.0 (abe02cefd 2018-12-04) 79 | 80 | 81 | Rust is installed now. Great! 82 | 83 | To get started you need Cargo's bin directory ($HOME/.cargo/bin) in your PATH 84 | environment variable. Next time you log in this will be done automatically. 85 | 86 | To configure your current shell run source $HOME/.cargo/env 87 | ``` 88 | 89 | `rustc` was installed now - that is the Rust compiler. 90 | 91 | You can verify if Rustup was installed correctly and which version you have that way: 92 | 93 | ```bash 94 | $ rustup --version 95 | rustup 1.16.0 (beab5ac2b 2018-12-06) 96 | ``` 97 | 98 | Now you can install a specific Rust version with `rustup install` and passing a version like `1.29.0`: 99 | 100 | ```bash 101 | $ rustup install 1.29.0 102 | info: syncing channel updates for '1.29.0-x86_64-apple-darwin' 103 | info: latest update on 2018-09-13, rust version 1.29.0 (aa3ca1994 2018-09-11) 104 | info: downloading component 'rustc' 105 | 57.7 MiB / 57.7 MiB (100 %) 2.1 MiB/s ETA: 0 s 106 | info: downloading component 'rust-std' 107 | 45.8 MiB / 45.8 MiB (100 %) 2.4 MiB/s ETA: 0 s 108 | info: downloading component 'cargo' 109 | 3.3 MiB / 3.3 MiB (100 %) 2.3 MiB/s ETA: 0 s 110 | info: downloading component 'rust-docs' 111 | 8.2 MiB / 8.2 MiB (100 %) 3.6 MiB/s ETA: 0 s 112 | info: installing component 'rustc' 113 | info: installing component 'rust-std' 114 | info: installing component 'cargo' 115 | info: installing component 'rust-docs' 116 | 117 | 1.29.0-x86_64-apple-darwin installed - rustc 1.29.0 (aa3ca1994 2018-09-11) 118 | ``` 119 | 120 | This installed version `1.29.0`. If you want to choose a default version, run this: 121 | 122 | ```bash 123 | $ rustup default 1.29.0 124 | info: using existing install for '1.29.0-x86_64-apple-darwin' 125 | info: default toolchain set to '1.29.0-x86_64-apple-darwin' 126 | 127 | 1.29.0-x86_64-apple-darwin unchanged - rustc 1.29.0 (aa3ca1994 2018-09-11) 128 | ``` 129 | 130 | After we feel comfortable in switching Node and Rust versions with `nvm` and `rustup` we'll now switch to the following version and verify that we'll use the same versions across the whole tutorial: 131 | 132 | ```bash 133 | $ nvm alias default v10.14.2 134 | default -> v10.14.2 135 | $ node --version 136 | v10.14.2 137 | 138 | $ rustup default 1.31.0 139 | 1.31.0-x86_64-apple-darwin installed - rustc 1.31.0 (abe02cefd 2018-12-04) 140 | $ rustc --version 141 | rustc 1.31.0 (abe02cefd 2018-12-04) 142 | ``` 143 | 144 | ## Setup VS Code as your IDE 145 | 146 | The IDE of my choice for Node and Rust projects is [VS Code](https://code.visualstudio.com/). It has awesome Typescript support as both projects are developed by Microsoft and their teams work closely together. But it also has awesome Rust support, as the _language server_ of Rust has an official reference implementation as a VS Code extension. (In case you're not familiar with the term _language server_: This is the piece of software which powers a lot of your typical IDE features like _code completion_ or _go to definition_.) 147 | 148 | First install VS Code, if you haven't already. Just visit [code.visualstudio.com](https://code.visualstudio.com/) and download the installer. The _Download_ button is in the upper right corner. 149 | 150 | ![official VS Code website](./vscode-site.png) 151 | 152 | On the following site you can choose between stable and insider releases. The insider releases offer new experimental features. I'll use the stable release which is version 1.30.0 at the time of writing. 153 | 154 | If you open this project with VS Code you should see a small notification which asks to install all _recommended extensions_. This is a nice feature from VS Code which can be configured inside `.vscode/extensions.json`. Nevertheless - if you don't see the notification these are the extensions we want to install: 155 | 156 | We need to install [Rust (rls)](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) - the Rust extension for VS Code. Just open VS Code, press `⌘P` (Quick Open) and type `ext install rust-lang.rust`. Press enter and reload VS Code. The extension should now be installed. As soon as you open an `.rs` file (the file extension used for Rust code) the Rust extension will ask you to install the corresponding language service, if you haven't installed it already. Just accept it and everything should work fine. 157 | 158 | One caveat: In order to fully work you need to have a `Cargo.toml` file in the root of your project. I'll explain what a `Cargo.toml` is in the chapter [_"Package Manager"_](../package-manager/README.md), but for now you can think of it like a `package.json` for Rust packages and that [Cargo](https://crates.io/) is used to manage dependencies in your Rust project, just like [npm](https://www.npmjs.com/) in Node projects. (We can workaround this by using a feature called [_workspaces_](https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html), which is really similar to [yarn workspaces](https://yarnpkg.com/lang/en/docs/workspaces/), if you ever used [yarn](https://yarnpkg.com/en/) instead of npm to manage dependencies in a Node project, but it _shouldn't_ be necessary, in my opinion. [I raised this as a feature request for Rust's language service.](https://github.com/rust-lang/rls/issues/1198)) 159 | 160 | But this brings us to the next VS Code extension we need: [Better TOML](https://marketplace.visualstudio.com/items?itemName=bungcip.better-toml), which supports syntax highlighting for `.toml` files. Open VS Code, press `⌘P` (Quick Open), type `ext install bungcip.better-toml` and press enter. 161 | 162 | The Rust extension we installed earlier automatically supports formatting Rust files (by using a tool called [`rustfmt`](https://github.com/rust-lang/rustfmt)). Let's add the same functionality to our JavaScript and TypeScript files by installing an extension for the popular [_prettier_](https://prettier.io/). Open VS Code, press `⌘P` (Quick Open), type `ext install esbenp.prettier-vscode` and press enter. 163 | 164 | I like to format all my files _on every save_ (and I like to slighlty adjust the default prettier config), so I put a [`.vscode/settings.json`](.vscode/settings.json) in the root of our project. This file is automatically picked up by VS Code, so you use the same config as I do by default. 165 | 166 | We could add even more tooling support now, for example to support linters. (In the Rust ecosystem exists a popular linter called [Clippy](https://github.com/rust-lang/rust-clippy) which has the same purpose as [ESLint](https://eslint.org/) in the Node world.) But I think we're ready to go now and finally write some code! 🎉 167 | 168 | --- 169 | 170 | [next _"Hello World"_](../hello-world/README.md) → 171 | -------------------------------------------------------------------------------- /setup/node-site.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mercateo/rust-for-node-developers/4a14f616d36f4d9c0f2a7e717f6bf2ac37863541/setup/node-site.png -------------------------------------------------------------------------------- /setup/rust-site.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mercateo/rust-for-node-developers/4a14f616d36f4d9c0f2a7e717f6bf2ac37863541/setup/rust-site.png -------------------------------------------------------------------------------- /setup/vscode-site.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mercateo/rust-for-node-developers/4a14f616d36f4d9c0f2a7e717f6bf2ac37863541/setup/vscode-site.png -------------------------------------------------------------------------------- /write-files/README.md: -------------------------------------------------------------------------------- 1 | # Write files 2 | 3 | ## Node 4 | 5 | In our [last example](../read-files/README.md) we used `readFile` to read the content of a file and switched to a combination of `openSync` and `readSync` to achieve the same goal in a low-level manner and synchronous way to make it easier to compare to Rust. Now we switch to `readFileSync` to keep things a little bit easier. We reuse our basic project structure for the next example as well. 6 | 7 | In this example we want to read two files: `hello.txt` and `world.txt`. They contain just a single word - `Hello` and `world`. We concatenate the content of both files and write it into a new file `hello-world.txt`. It should contain `Hello world!` at the end. (The space and the `!` will be added by us.) 8 | 9 | The program could look like this: 10 | 11 | ```ts 12 | import { readFileSync, writeFileSync } from 'fs'; 13 | 14 | let hello: string; 15 | try { 16 | hello = readFileSync('hello.txt', 'utf8'); 17 | } catch (err) { 18 | throw `Couldn't read 'hello.txt'.`; 19 | } 20 | 21 | let world: string; 22 | try { 23 | world = readFileSync('world.txt', 'utf8'); 24 | } catch (err) { 25 | throw `Couldn't read 'world.txt'.`; 26 | } 27 | 28 | let helloWorld = `${hello} ${world}!`; 29 | 30 | try { 31 | writeFileSync('hello-world.txt', helloWorld); 32 | } catch (err) { 33 | throw `Couldn't write 'hello-world.txt'.`; 34 | } 35 | 36 | console.log(`Wrote file 'hello-world.txt' with content: ${helloWorld}`); 37 | ``` 38 | 39 | You'll note that we switched our error handling style from something like that: 40 | 41 | ```ts 42 | let someValue; 43 | try { 44 | someValue = getSomeValue(); 45 | } catch (err) { 46 | console.log(`Couldn't get value.`); 47 | process.exit(1); 48 | } 49 | ``` 50 | 51 | To something like that: 52 | 53 | ```ts 54 | let someValue; 55 | try { 56 | someValue = getSomeValue(); 57 | } catch (err) { 58 | throw `Couldn't get value.`; 59 | } 60 | ``` 61 | 62 | It does _roughly_ the same. We throw an exeption with a custom error message and let Node handle the exiting of our process. This might be not very pretty, but my point here is that different patterns of error handling exist. We already learned about [Nodes callback style](../read-files/README.md) where the first param might be an error or if you think about Promises you probably already know about the [`catch()` handler](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch). Rust has different styles of handling errors as well and I want to introduce you to some of them. 63 | 64 | Before we jump into Rust we should test our program: 65 | 66 | ```bash 67 | $ npm -s start 68 | Wrote file in hello-world.txt with content: Hello world! 69 | ``` 70 | 71 | Charming. 72 | 73 | ## Rust 74 | 75 | Our simplified _"read files"_ example looked like this: 76 | 77 | ```rust 78 | use std::error::Error; 79 | use std::fs::read_to_string; 80 | 81 | fn main() { 82 | match read_to_string("hello.txt") { 83 | Err(err) => panic!("Couldn't read: {}", err.description()), 84 | Ok(data) => println!("Content is: {}", data), 85 | }; 86 | } 87 | ``` 88 | 89 | Let us read and print the content of both files now: 90 | 91 | ```diff 92 | use std::error::Error; 93 | use std::fs::read_to_string; 94 | 95 | fn main() { 96 | - match read_to_string("hello.txt") { 97 | + let hello = match read_to_string("hello.txt") { 98 | Err(err) => panic!("Couldn't read: {}", err.description()), 99 | - Ok(data) => println!("Content is: {}", data), 100 | + Ok(data) => data, 101 | }; 102 | 103 | + let world = match read_to_string("world.txt") { 104 | + Err(err) => panic!("Couldn't read: {}", err.description()), 105 | + Ok(data) => data, 106 | + }; 107 | 108 | + println!("Content is: {} and {}", hello, world); 109 | } 110 | ``` 111 | 112 | You shouldn't see anything new here (- besides using two values in `println!()` maybe). Let us remove the duplicated error handling logic now by creating a new function: 113 | 114 | ```diff 115 | use std::error::Error; 116 | use std::fs::read_to_string; 117 | 118 | +fn read_file(path: &str) -> String { 119 | + let data = match read_to_string(path) { 120 | + Err(err) => panic!("Couldn't read: {}", err.description()), 121 | + Ok(data) => data, 122 | + }; 123 | + return data; 124 | +} 125 | 126 | fn main() { 127 | - let hello = match read_to_string("hello.txt") { 128 | - Err(err) => panic!("Couldn't read: {}", err.description()), 129 | - Ok(data) => data, 130 | - }; 131 | - 132 | - let world = match read_to_string("world.txt") { 133 | - Err(err) => panic!("Couldn't read: {}", err.description()), 134 | - Ok(data) => data, 135 | - }; 136 | + let hello = read_file("hello.txt"); 137 | + let world = read_file("world.txt"); 138 | println!("Content is: {} and {}", hello, world); 139 | } 140 | ``` 141 | 142 | As you can see `read_file` accepts a param called `path` which is a `&str` and it will return (`->`) a value from the type `String`. In contrast to TypeScript we _always_ need to specify the return type. 143 | 144 | The remaining code should be relative self-explanatory. Only returning the actual value is new, but it looks just like JavaScript and uses the `return` keyword: `return data;`. 145 | 146 | _But_... Rust allows us to write our code like this, too: 147 | 148 | ```diff 149 | fn read_file(path: &str) -> String { 150 | let data = match read_to_string(path) { 151 | Err(err) => panic!("Couldn't read: {}", err.description()), 152 | Ok(data) => data, 153 | }; 154 | - return data; 155 | + data 156 | } 157 | ``` 158 | 159 | What is that? `data` is an _implicit return_ while `return data;` is an _explicit return_. This is possible, because Rust is primarily an _expression-based_ language. In that case `data` is an _expression_ while `return data;` is a _statement_. An expression automatically returns a value and a statement does not. The `;` basically turns our expression into a statement. Therefore the `;` is _not_ meaningless as it is 99% of the time in JavaScript. It is really a little bit similar to arrow functions in JavaScript which can return values automatically (e.g. `[ 1 ].map(n => n + 1) // [ 2 ]`). Using expressions like that allows nice things. For example Rust has _no_ ternary operator (e.g. `let foo = x ? y : z;` in JavaScript), because we can do the same with our normal `if`/`else` keywords and expressions (e.g. `let foo = if x { y } else { z };` in Rust). 160 | 161 | I definitely need to get used to writing _implicit returns_ like that even if I use arrow functions very often in JavaScript. The single `data` on the last line looks a little bit weird to me. But to be honest... writing _implicit returns_ is the [prefered style](https://users.rust-lang.org/t/implicit-or-explicit-return-in-non-closures-what-is-more-idiomatic/6357/4) in that case. It is recommended to use the `return` keyword only for _early returns_. 162 | 163 | In fact we can simplify the code even further: 164 | 165 | ```diff 166 | fn read_file(path: &str) -> String { 167 | - let data = match read_to_string(path) { 168 | - Err(err) => panic!("Couldn't read: {}", err.description()), 169 | - Ok(data) => data, 170 | - }; 171 | - data 172 | + match read_to_string(path) { 173 | + Err(err) => panic!("Couldn't read: {}", err.description()), 174 | + Ok(data) => data, 175 | + } 176 | } 177 | ``` 178 | 179 | We should check that everything works: 180 | 181 | ```bash 182 | $ cargo -q run 183 | Content is: Hello and world 184 | ``` 185 | 186 | Fine. Now we can look into different ways of handling errors in Rust before we'll add the actual part of writing files. 187 | 188 | The patterns we used for now was using `panic!` which allows adding a custom error message and exits our program. This is okay, but a little bit verbose. The shortest way for error handling is calling `unwrap()` on functions returning a `Result`. (Remember from the [last example](../read-files/README.md) that the `Result` type has an `Err` case and an `Ok` case which we both _need_ to handle. This can be done with pattern matching like we did for now.) `unwrap` _just_ exits the program on `Err` or returns the result on `Ok`, but you cannot pass a custom error message. It looks like that. 189 | 190 | ```diff 191 | fn read_file(path: &str) -> String { 192 | - match read_to_string(path) { 193 | - Err(err) => panic!("Couldn't read: {}", err.description()), 194 | - Ok(data) => data, 195 | - } 196 | + read_to_string(path).unwrap() 197 | } 198 | ``` 199 | 200 | As you can see our example becomes very readable. We could also remove `use std::error::Error;` now. That's why you can see this style very often in tutorials. But you rarely use `unwrap` in real code, because it lacks a custom error message. 201 | 202 | You can achieve that with `expect` which works like `unwrap`, but accepts custom error messages. 203 | 204 | ```diff 205 | fn read_file(path: &str) -> String { 206 | - read_to_string(path).unwrap() 207 | + read_to_string(path).expect("Couldn't read.") 208 | } 209 | ``` 210 | 211 | A mix of both styles could look like this: 212 | 213 | ```diff 214 | fn read_file(path: &str) -> String { 215 | - read_to_string(path).expect("Couldn't read.") 216 | + read_to_string(path).unwrap_or_else(|err| panic!("Couldn't read: {}", err.description())) 217 | } 218 | ``` 219 | 220 | The `|err| panic!()` you see here is a [closure](https://doc.rust-lang.org/book/ch13-01-closures.html). You can think of them like arrow functions in JavaScript: `(err) => console.log()`. (Like JavaScript with `(err) => { console.log() }` you can use `{}`, if you need multiple lines, but they are optional for one-liners: `|err| { panic!() }`.) 221 | 222 | Nice. 223 | 224 | To showcase the next error handling pattern we will use the more verbose example from [the previous chapter](../read-files/README.md). I just extract it into our `read_file` function. The complete example would like this: 225 | 226 | ```rust 227 | use std::error::Error; 228 | use std::fs::File; 229 | use std::io::Read; 230 | use std::str::from_utf8; 231 | 232 | fn read_file(path: &str) -> String { 233 | let mut file = match File::open(path) { 234 | Err(err) => panic!("Couldn't open: {}", err.description()), 235 | Ok(value) => value, 236 | }; 237 | 238 | let stat = match file.metadata() { 239 | Err(err) => panic!("Couldn't get stat: {}", err.description()), 240 | Ok(value) => value, 241 | }; 242 | 243 | let mut buffer = vec![0; stat.len() as usize]; 244 | 245 | match file.read(&mut buffer) { 246 | Err(err) => panic!("Couldn't read: {}", err.description()), 247 | Ok(_) => (), 248 | }; 249 | 250 | match from_utf8(&buffer) { 251 | Err(err) => panic!("Couldn't convert buffer to string: {}", err.description()), 252 | Ok(value) => value.to_string(), 253 | } 254 | } 255 | 256 | fn main() { 257 | let hello = read_file("hello.txt"); 258 | let world = read_file("world.txt"); 259 | println!("Content is: {} and {}", hello, world); 260 | } 261 | ``` 262 | 263 | The only new part is the `to_string()` in the last line of the `read_file` function which convers our `&str` into a `String`. 264 | 265 | Now assume we write a special `fs` crate which offers a similar API as Node to ease the transition for Node developers to use Rust. `read_file` shouldn't exit the program on errors. It is just a lib - the program needs to decide whether it should exit or not. How do we do that? We need to return an `Result` on our own. The `Result` will return a `String` in the `Ok` case and an error in the `Err` case. 266 | 267 | The problem is the type of our error. `File::open` and the other file related methods can return a `std::io::Error` struct in an `Err` case. (Don't confuse `std::io::Error`, which is a struct for I/O related errors with our trait `std::error::Error` which was used so far.) But `from_utf8` can actually return a `std::str::Utf8Error`. Without `std::str::Utf8Error` our function could look like this: 268 | 269 | ```rust 270 | fn read_file(path: &str) -> Result { 271 | // our code 272 | } 273 | ``` 274 | 275 | But we need to be able to return _both_ errors. Sadly we can't write `Result` like we can in TypeScript which would read as _"the `Err` case is either `std::io::Error` or `std::str::Utf8Error`"_. There are multiple ways to solve this: 276 | 277 | 1. Create a custom error type which takes care of this. But this can [be very verbose](https://twitter.com/PipoPeperoni/status/1084700498308022272). 278 | 2. Use [a third party lib like `error-chain`](https://github.com/rust-lang-nursery/error-chain) to get rid of the verbosity. 279 | 3. Use `Box` a more generic type we can cast every error to. Sadly we won't be able to statically determine the error type anymore. 280 | 281 | I have the feeling that `1.` and `2.` are a little bit out of scope for this tutorial. In a real world library you would probably go with `2.`. I _really_ hope that Rust will someday handle this scenario more easily without introducing third party libs. 282 | 283 | For the sake of this tutorial we go with solution `3.`. So what is `Box`? [`Box` is a built in struct](https://doc.rust-lang.org/std/boxed/struct.Box.html). It is basically a way to store a pointer to some value and with `Box` we can basically store and return _any_ error. 284 | 285 | This will change the return signature of `read_file` and because Rust forces us to handle all errors, the error handling will move up to our `main` function. On every error we'll use an _early return_ to return the `err` as our `Err` case. This is written as `Err(err.into())` - the `.into()` will take care of converting the error into a `Box`. Similar we don't just return `data`, but `Ok(data)`. 286 | 287 | ```diff 288 | use std::error::Error; 289 | use std::fs::File; 290 | use std::io::Read; 291 | use std::str::from_utf8; 292 | 293 | -fn read_file(path: &str) -> String { 294 | +fn read_file(path: &str) -> Result> { 295 | let mut file = match File::open(path) { 296 | - Err(err) => panic!("Couldn't open: {}", err.description()), 297 | + Err(err) => return Err(err.into()), 298 | Ok(value) => value, 299 | }; 300 | 301 | let stat = match file.metadata() { 302 | - Err(err) => panic!("Couldn't get stat: {}", err.description()), 303 | + Err(err) => return Err(err.into()), 304 | Ok(value) => value, 305 | }; 306 | 307 | let mut buffer = vec![0; stat.len() as usize]; 308 | 309 | match file.read(&mut buffer) { 310 | - Err(err) => panic!("Couldn't read: {}", err.description()), 311 | + Err(err) => return Err(err.into()), 312 | Ok(_) => (), 313 | }; 314 | 315 | match from_utf8(&buffer) { 316 | - Err(err) => panic!("Couldn't convert buffer to string: {}", err.description()), 317 | - Ok(value) => value.to_string(), 318 | + Err(err) => return Err(err.into()), 319 | + Ok(value) => Ok(value.to_string()), 320 | } 321 | } 322 | 323 | fn main() { 324 | - let hello = read_file("hello.txt"); 325 | - let world = read_file("world.txt"); 326 | + let hello = read_file("hello.txt").unwrap(); 327 | + let world = read_file("world.txt").unwrap(); 328 | println!("Content is: {} and {}", hello, world); 329 | } 330 | ``` 331 | 332 | This is a very useful pattern, but also quite verbose. Thankfully Rust has the `?` operator which will unwrap the value or return an error. This will reduce our code dramatically. 333 | 334 | ```diff 335 | use std::error::Error; 336 | use std::fs::File; 337 | use std::io::Read; 338 | use std::str::from_utf8; 339 | 340 | fn read_file(path: &str) -> Result> { 341 | - let mut file = match File::open(path) { 342 | - Err(err) => return Err(err.into()), 343 | - Ok(value) => value, 344 | - }; 345 | - 346 | - let stat = match file.metadata() { 347 | - Err(err) => return Err(err.into()), 348 | - Ok(value) => value, 349 | - }; 350 | - 351 | - let mut buffer = vec![0; stat.len() as usize]; 352 | - 353 | - match file.read(&mut buffer) { 354 | - Err(err) => return Err(err.into()), 355 | - Ok(_) => (), 356 | - }; 357 | - 358 | - match from_utf8(&buffer) { 359 | - Err(err) => return Err(err.into()), 360 | - Ok(value) => Ok(value.to_string()), 361 | - } 362 | + let mut file = File::open(path)?; 363 | + let stat = file.metadata()?; 364 | + 365 | + let mut buffer = vec![0; stat.len() as usize]; 366 | + file.read(&mut buffer)?; 367 | + let value = from_utf8(&buffer)?.to_string(); 368 | + 369 | + Ok(value) 370 | } 371 | 372 | fn main() { 373 | let hello = read_file("hello.txt").unwrap(); 374 | let world = read_file("world.txt").unwrap(); 375 | println!("Content is: {} and {}", hello, world); 376 | } 377 | ``` 378 | 379 | Beautiful! Now you know the most common error handling patterns. We can now start to add our file writing logic. First we need to create `String` called `hello_world` which will represent our file content. We can create an empty `String` and push values into it (very much like an `[].push` in JavaScript). 380 | 381 | ```diff 382 | fn main() { 383 | let hello = read_file("hello.txt").unwrap(); 384 | let world = read_file("world.txt").unwrap(); 385 | - println!("Content is: {} and {}", hello, world); 386 | 387 | + let mut hello_world = String::new(); 388 | + hello_world.push_str(&hello); 389 | + hello_world.push_str(" "); 390 | + hello_world.push_str(&world); 391 | + hello_world.push_str("!"); 392 | + println!("Content is: {}", hello_world); 393 | } 394 | ``` 395 | 396 | `hello_world` needs to be `mut`, because we change its value by pushing new values into it. Pushing is done with the `push_str` method which accepts `&str`'s, but _not_ `String`'s. That's why we pass `&hello` instead of `hello`, because the `&` coerces a `String` into a `&str`. 397 | 398 | This example can be simplified a little bit, because we can append `&str`'s with the `+` operator to a `String`. 399 | 400 | ```diff 401 | fn main() { 402 | let hello = read_file("hello.txt").expect("Couldn't read 'hello.txt'."); 403 | let world = read_file("world.txt").expect("Couldn't read 'world.txt'."); 404 | 405 | - let mut hello_world = String::new(); 406 | - hello_world.push_str(&hello); 407 | - hello_world.push_str(" "); 408 | - hello_world.push_str(&world); 409 | - hello_world.push_str("!"); 410 | + let hello_world = hello + " " + &world + "!"; 411 | println!("Content is: {}", hello_world); 412 | } 413 | ``` 414 | 415 | But... there is also a macro for that! `format!` which makes it more readable in my opinion - especially if your string would be more complex. You use it like `println!`, but it just returns a `String`. 416 | 417 | ```diff 418 | fn main() { 419 | let hello = read_file("hello.txt").expect("Couldn't read 'hello.txt'."); 420 | let world = read_file("world.txt").expect("Couldn't read 'world.txt'."); 421 | 422 | - let hello_world = hello + " " + &world + "!"; 423 | + let hello_world = format!("{} {}!", hello, world); 424 | println!("Content is: {}", hello_world); 425 | } 426 | ``` 427 | 428 | Now we have our file content and can write it into a file. 429 | 430 | ```diff 431 | use std::error::Error; 432 | use std::fs::File; 433 | -use std::io::Read; 434 | +use std::io::{Read,Write}; 435 | use std::str::from_utf8; 436 | 437 | fn read_file(path: &str) -> Result> { 438 | let mut file = File::open(path)?; 439 | let stat = file.metadata()?; 440 | 441 | let mut buffer = vec![0; stat.len() as usize]; 442 | file.read(&mut buffer)?; 443 | let value = from_utf8(&buffer)?.to_string(); 444 | 445 | Ok(value) 446 | } 447 | 448 | +fn write_file(path: &str, data: &str) -> Result<(), Box> { 449 | + File::create(path)?.write_all(data.as_bytes())?; 450 | + Ok(()) 451 | +} 452 | 453 | fn main() { 454 | let hello = read_file("hello.txt").unwrap(); 455 | let world = read_file("world.txt").unwrap(); 456 | 457 | let hello_world = format!("{} {}!", hello, world); 458 | - println!("Content is: {}", hello_world); 459 | + write_file("hello-world.txt", &hello_world).unwrap(); 460 | + println!("Wrote file 'hello-world.txt' with content: {}", hello_world); 461 | } 462 | 463 | ``` 464 | 465 | We create a new function with `fn` called `write_file`. `write_file` accepts two params `path` and `data` which are both from the type `&str`. It returns a `Result` which is either a `Box` for the `Err` case or "empty" (`()`) for the `Ok` case. The file is created with `File:create` (this _opens_ a file in write-only mode) which is available, because we added `Write` to `use std::io::{}`. We actually write our content into the file with `write_all` which accepts bytes (`&[u8]`). A `&str` can be converted into a `&[u8]` with the `as_bytes` method. After that we return our "empty" `Ok` case with `Ok(())`. 466 | 467 | Let's try our program: 468 | 469 | ```bash 470 | $ cargo -q run 471 | Wrote file 'hello-world.txt' with content: Hello world! 472 | ``` 473 | 474 | Awesome! In this example you learned different error handling patterns, different ways to concatenate a string and how to write a file. 475 | 476 | --- 477 | 478 | ← [prev _"Read files"_](../read-files/README.md) | [next _"HTTP requests"_](../http-requests/README.md) → 479 | -------------------------------------------------------------------------------- /write-files/node/dist/index.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | //# sourceMappingURL=index.d.ts.map -------------------------------------------------------------------------------- /write-files/node/dist/index.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /write-files/node/dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var fs_1 = require("fs"); 4 | var hello; 5 | try { 6 | hello = fs_1.readFileSync('hello.txt', 'utf8'); 7 | } 8 | catch (err) { 9 | throw "Couldn't read 'hello.txt'."; 10 | } 11 | var world; 12 | try { 13 | world = fs_1.readFileSync('world.txt', 'utf8'); 14 | } 15 | catch (err) { 16 | throw "Couldn't read 'world.txt'."; 17 | } 18 | var helloWorld = hello + " " + world + "!"; 19 | try { 20 | fs_1.writeFileSync('hello-world.txt', helloWorld); 21 | } 22 | catch (err) { 23 | throw "Couldn't write 'hello-world.txt'."; 24 | } 25 | console.log("Wrote file 'hello-world.txt' with content: " + helloWorld); 26 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /write-files/node/dist/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAAA,yBAAiD;AAEjD,IAAI,KAAa,CAAC;AAClB,IAAI;IACF,KAAK,GAAG,iBAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;CAC3C;AAAC,OAAO,GAAG,EAAE;IACZ,MAAM,4BAA4B,CAAC;CACpC;AAED,IAAI,KAAa,CAAC;AAClB,IAAI;IACF,KAAK,GAAG,iBAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;CAC3C;AAAC,OAAO,GAAG,EAAE;IACZ,MAAM,4BAA4B,CAAC;CACpC;AAED,IAAI,UAAU,GAAM,KAAK,SAAI,KAAK,MAAG,CAAC;AAEtC,IAAI;IACF,kBAAa,CAAC,iBAAiB,EAAE,UAAU,CAAC,CAAC;CAC9C;AAAC,OAAO,GAAG,EAAE;IACZ,MAAM,mCAAmC,CAAC;CAC3C;AAED,OAAO,CAAC,GAAG,CAAC,gDAA8C,UAAY,CAAC,CAAC"} -------------------------------------------------------------------------------- /write-files/node/hello-world.txt: -------------------------------------------------------------------------------- 1 | Hello world! -------------------------------------------------------------------------------- /write-files/node/hello.txt: -------------------------------------------------------------------------------- 1 | Hello -------------------------------------------------------------------------------- /write-files/node/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "write-files", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/node": { 8 | "version": "10.12.18", 9 | "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz", 10 | "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==", 11 | "dev": true 12 | }, 13 | "typescript": { 14 | "version": "3.2.2", 15 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.2.tgz", 16 | "integrity": "sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==", 17 | "dev": true 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /write-files/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "write-files", 3 | "version": "1.0.0", 4 | "private": true, 5 | "main": "dist/index.js", 6 | "typings": "dist/index.d.ts", 7 | "scripts": { 8 | "start": "npm run build && node dist", 9 | "build": "tsc --build src" 10 | }, 11 | "devDependencies": { 12 | "@types/node": "^10.12.18", 13 | "typescript": "^3.2.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /write-files/node/src/index.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync, writeFileSync } from 'fs'; 2 | 3 | let hello: string; 4 | try { 5 | hello = readFileSync('hello.txt', 'utf8'); 6 | } catch (err) { 7 | throw `Couldn't read 'hello.txt'.`; 8 | } 9 | 10 | let world: string; 11 | try { 12 | world = readFileSync('world.txt', 'utf8'); 13 | } catch (err) { 14 | throw `Couldn't read 'world.txt'.`; 15 | } 16 | 17 | let helloWorld = `${hello} ${world}!`; 18 | 19 | try { 20 | writeFileSync('hello-world.txt', helloWorld); 21 | } catch (err) { 22 | throw `Couldn't write 'hello-world.txt'.`; 23 | } 24 | 25 | console.log(`Wrote file 'hello-world.txt' with content: ${helloWorld}`); 26 | -------------------------------------------------------------------------------- /write-files/node/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "outDir": "../dist", 5 | "declaration": true, 6 | "sourceMap": true, 7 | "declarationMap": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /write-files/node/world.txt: -------------------------------------------------------------------------------- 1 | world -------------------------------------------------------------------------------- /write-files/rust/Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "write-files" 3 | version = "1.0.0" 4 | 5 | -------------------------------------------------------------------------------- /write-files/rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "write-files" 3 | version = "1.0.0" 4 | publish = false 5 | edition = "2018" -------------------------------------------------------------------------------- /write-files/rust/hello-world.txt: -------------------------------------------------------------------------------- 1 | Hello world! -------------------------------------------------------------------------------- /write-files/rust/hello.txt: -------------------------------------------------------------------------------- 1 | Hello -------------------------------------------------------------------------------- /write-files/rust/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::fs::File; 3 | //use std::io::Read; 4 | use std::io::{Read,Write}; 5 | use std::str::from_utf8; 6 | 7 | fn read_file(path: &str) -> Result> { 8 | let mut file = File::open(path)?; 9 | let stat = file.metadata()?; 10 | 11 | let mut buffer = vec![0; stat.len() as usize]; 12 | file.read(&mut buffer)?; 13 | let value = from_utf8(&buffer)?.to_string(); 14 | 15 | Ok(value) 16 | } 17 | 18 | fn write_file(path: &str, data: &str) -> Result<(), Box> { 19 | File::create(path)?.write_all(data.as_bytes())?; 20 | Ok(()) 21 | } 22 | 23 | fn main() { 24 | let hello = read_file("hello.txt").unwrap(); 25 | let world = read_file("world.txt").unwrap(); 26 | 27 | let hello_world = format!("{} {}!", hello, world); 28 | // println!("Content is: {}", hello_world); 29 | write_file("hello-world.txt", &hello_world).unwrap(); 30 | println!("Wrote file 'hello-world.txt' with content: {}", hello_world); 31 | } 32 | -------------------------------------------------------------------------------- /write-files/rust/world.txt: -------------------------------------------------------------------------------- 1 | world --------------------------------------------------------------------------------