BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Presentations JavaScript: Empowered by Rust

JavaScript: Empowered by Rust

Bookmarks
48:48

Summary

Chris Biscardi explores what Rust is being used for on the front-end and introduces the language from the perspective of a JavaScript developer.

Bio

Chris Biscardi is an independent full stack product engineer focusing on JavaScript and Rust. He teaches Rust at rustadventure.dev and helps companies adopt emerging technology.

About the conference

Software is changing the world. QCon empowers software development by facilitating the spread of knowledge and innovation in the developer community. A practitioner-driven conference, QCon is designed for technical team leads, architects, engineering directors, and project managers who influence innovation in their teams.

Transcript

Biscardi: I am Chris Biscardi. I educate people on Rust these days. I write a lot of Rust to do Rust contracting. I have 10, 15 years of frontend development, work on Gatsby, webpack stuff, all sorts of things. This is a talk about Rust and JavaScript. This is not a talk about me telling you to go rewrite everything in Rust. I'm not going to tell you to do that. If anybody tells you to do that without looking at your project, don't believe them. This is about writing Rust in places where it can be helpful for your JavaScript projects. It is not about throwing all of your JavaScript out and moving to a different language.

Today's Tools (Partly/Fully Written in Rust)

When we get into it, why would you even think about doing this at all? Today's tools, we're moving slightly past it these days, about a year or two into being past webpack now. webpack came out 2014, something like that. It was great for a time. Who has had a webpack issue that's cost them more than an hour? People are looking at, what do we build after that? People have built meta-frameworks on top of webpack. People have used all sorts of things to try to make webpack better to use, easier to use, faster, whatever. People are starting to do that. Here's one example, Turbopack from the people at Vercel. It is one of the people that worked on, maintained, created webpack. They are rewriting in Rust. Why? It's fast. It's not necessarily true all the time, which we'll get to. The thing that I want to say about this slide is that Turbopack is written in Rust, but also the things it's comparing to on this page are also Rust. SWC we'll get to later, a Babel replacement written in Rust. You see more of Rust popping up in all of these tools, whether it's Rspack, which is all Rust, or it's Turbopack, which is maybe all Rust, maybe not, whether it's other tools, but all these tools are popping up. They're all choosing to use Rust in some capacity. I want to convince you to not be scared of it.

One of the things that I think people get hung up on when it comes to Rust is everybody talks about speed. Everybody talks about memory safety. Everybody talks about why you should use it over C. I assume if you're in this track, you all write JavaScript. None of you care about any of that. We see this over and over. It's like, look at how fast we are, we used Rust, but people are starting to build higher level tooling on top of these tools that use Rust, just like Gatsby, just like Next, just like all these other things. Even the tools that are not switching completely over to Rust are rewriting components of themselves in Rust. We've got Parcel. Parcel is like a webpack competitor, Vite competitor. It is starting to rewrite components of this system, the things that it feels matter, in Rust. Of course, they also are lightning fast, just like everybody else. If you read some of this slide, you'll find out that they're also using more Rust tools like SWC. They're using Lightning CSS. Aside from being hundreds of times faster than whatever, this is having a real impact on the people who are building these tools. They're all choosing to go in these directions for a reason. I've mentioned SWC a couple times. Babel has been very popular for a long period of time. Maybe a little less popular recently. SWC is a Rust replacement for Babel. It does the same thing. It just does it faster, again. It's also faster on four cores, so parallelism, concurrency, multi-threadedness are also a theme that come up in these. They're basically all marketing pages. Lightning CSS is the CSS Parser, transformer, minifier thing inside of Parcel that they're using. This is also used in other projects, which I'll mention.

They are also extremely fast. All of them have this slide, or a variation of this slide, because apparently speed sells these days. I don't think that speed or memory safety or whatever, which none of you care about, are the selling points of Rust. I think that's obvious when you look at, like what Lightning CSS is built on top of. There are these crates that the Firefox browser uses to do CSS parsing when you load a page inside of Firefox. It would be nice if your tools were capable of parsing everything the browser could. If you use those crates, then they are. There's no difference between what you have in some custom parser and what is actually going to work in the browser, so on and so forth. Now Lightning CSS is getting into other places, not just bundlers. I've talked a lot about bundlers up until this point. Tailwind just made their Rust based parser, the default parser. If you use Tailwind, in the next version, you will be using Rust, even if you didn't choose to, which is part of my point. Nx, another build tool, another minor repo tool also has some Rust in it. More importantly, this is one of the tools that is not completely rewriting in Rust. I'm not trying to convince you to rewrite everything in Rust. You don't have to. You can rewrite little pieces, introduce new features, anything that's in a hot loop, anything that's heavy computation, anything like that, you can do that in Rust. You don't have to do everything in Rust.

Why Rust?

We looked at a couple marketing pages here. According to them, why should you use Rust? It's fast, parallelized architecture, browser grade. Is Rust fast? Because it's on every single page. All of them. Every single person that uses Rust inside of one of these projects is like, it's fast. That's why we're using it. Is it? My answer to that is, yes, if you write a fast program. You can write a fast JavaScript program. When you're doing performance work, the fastest thing to do is to not do anything, and you cannot do anything in any language. If you can avoid work, everything gets faster. Is Rust fast? I don't really think of languages as being faster or slower than each other. I think the algorithms, the amount of work, the kind of work that you're doing tends to matter more. Which brings us to, it's parallelized architecture. This has some truth to it, because of the ownership model, because of the borrow checker. It's easier to write concurrent code. It's easier to write parallelized code. We'll talk about iterators later. If you wanted to parallelize something that was using an iterator, you can use a crate called rayon. That allows you to do par_iter, which is short for parallel iterator. Then you get parallelization. It does have some benefits. There is something to that. It's not magic sauce. It's not going to magically make whatever program you write parallel. There's something to it. The other thing that's on these pages is these are browser grade. They're using code that is browser grade or Rust is browser grade. What does that really mean? Because like, you don't care. If Lightning CSS is going to transform your CSS, it's either going to or it's not. If it's JavaScript or Rust, you probably don't care. Browser grade means that it's using the same crates that are used by Firefox. This becomes an option if you choose Rust. The parser for CSS that you use can be the same parser that is used to load pages in Firefox. The WebGPU implementation, wgpu, you can now use in your projects. If you're going to go try to build Figma or something, you can use the crate that Firefox uses to implement WebGPU in the browser. You can stick that in your program, and now you have a cross-platform, GPU accelerated Wasm-based interface that you can play with via JavaScript. That's what it means to be browser grade, in my opinion. If you're not convinced at this point, I don't blame you, those weren't very convincing. I don't think that these marketing pages and saying that they are fast, or saying that they're more parallel than something else are really that interesting.

Why Rust? These are taken from people on the internet who write Rust. Types. Are you all TypeScript fans? The way I'll phrase this then is that, using TypeScript, you've probably seen a lot of impedance mismatches with the way that JavaScript works. Types are great, but they are better when they are built into the language from the beginning. If you've ever been frustrated by an any type, if you've ever been frustrated by losing your type safety through some third-party dependency, or something like that, it's a stronger level of types, is the way I'll phrase it. Rust is low-level and high-level. What this means is that you can write operating systems, which everybody is scared of. You can write web UI. It'll look a lot like your JavaScript that you're writing. You can go from very low-level stuff. You can opt to care about things like that, or you can write things that look a lot more like JavaScript itself, with types that were built in from the start. Wasm is another reason that people choose Rust. If you want to build that Figma competitor, if you have a lot of computation to do in a browser, Wasm can be a valid option for you. Rust is one of the top languages to use if you are going to target Wasm. There are scary words that people use like algebraic data types. Nobody knows what that means. This is the structs and enums. Basically, you can define structs, object types, and you can define enums that also hold data. It's really nice.

No garbage collector, that means it embeds well. That means it embeds well in JavaScript, Python, Ruby, Elixir. It fits inside of whatever you're already building. You don't have to rewrite everything in it. Whatever you have in your JavaScript world doesn't need to compete with Rust for resources or garbage collection or what have you. Fast and safe. People call things fast all the time. They call things safe all the time. As JavaScript people, we write in a memory safe language already, this doesn't mean a lot when you say this to a JavaScript crowd. Error handling is great. Rust doesn't have undefined, doesn't have null, the errors are values, you can match on them. You can deal with them. Destroy your program panics. You do not catch them. You do not deal with them. They're not going to pop up randomly without telling you for most cases. Cargo was really powerful. Rust had the huge benefit of just coming after everybody else. Rust was not built in 1990. It looks like a language that wasn't built in 1990. That means that cargo, the package manager that we use for Rust, got to learn from Ruby's bundler, and everybody else, like npm and the 5 million other package managers we have. rust-analyzer. One of the things you all like about TypeScript I'm sure is the editor support, the autocomplete. That's what rust-analyzer is, is a very powerful tool for writing code, refactoring code, showing you errors, doing autocomplete. If you have a type in your editor with Rust, they will tell you all of the functions that you can call on that type, and it is not guessing, it knows. Scoped resource management, may not be super interesting to you. Basically, if you open a file, you have to remember to close the file. In Rust, you don't have to remember to close the file, because if you open a file inside of a function, and you end that function, Rust will drop that value and close the file. It's really nice to not have to remember to do that, because I forget that thing all the time.

Zero cost abstractions, this is not going to be super meaningful. Basically, what this means is, Rust can do a lot of stuff at compile time. If you want to use high-level abstractions, if you want to use generics in your code, we want that to be a compile time cost. We don't want to slow down our program at runtime. That's what zero cost abstractions mean. You can use high-level abstractions without paying a runtime cost. If anybody's ever built like a webpack plugin or something, suddenly your whole build can slow down if you mess something up. That's the idea here. Pattern matching, we'll get to. That's related to enums and other things. It's like fancy switch statements. Type inference is really good. Rust requires that you type all of your function definitions, all the arguments in, all the return values out. Everything else is up in the air. It'll just infer everything else in your program if you let it. There are some exceptions. If things are super generic, and Rust can't tell which thing you actually want, that usually means there are multiple options, and you have to be specific. We'll get to that. The compiler error messages, the best thing I can say about them is that they are worth reading. Cargo has built-in testing, formatting, linting, benchmarking, documentation. Included in documentation is things like documentation tests. When you build your documentation, it will test to see whether those documentation examples actually work. Which is really nice, because it really is not great to go to some library, some crate, and be like, I'm going to use this. You take the code, you put it in your project, and you're not sure why it doesn't work.

Why Rust, in my opinion? Rust is a tool that can make you better. It can make you better at what you do. It can give you more time to do what you want to do. It can enable you to write programs that are maintainable that you can come back to in three to six months, that aren't going to break when you come back to them. I'm sure as JavaScript, Rust people had the experience of some transitive dependency updating. Suddenly our Gatsby app, or our Next app, or our Nuxt app, or whatever we're using, doesn't build for some reason, in CI, and that really isn't fun. I think that Rust really lets you avoid a lot of that. My opinion, Fireflowers. Widely applicable. You can write operating systems, networking code. You can write games. You can write web UI. You can compile to Wasm. You can take Rust, you can embed it inside of a JavaScript project, and let other people npm install your project, and then call functions from that project. Just like any other JavaScript package, they don't even need to know you wrote it in Rust. Testing built-in, documentation built-in, modern package management. Performance that sticks. It can be tough to optimize JavaScript programs. If you have a hot loop, if you have something that really matters, if you are trying to reduce the amount of memory that you're using, it is nice to be able to use the type system to guarantee that those changes are going to stick. You don't want to make a bunch of changes in your JavaScript program, and then tomorrow, one of your teammates puts in a PR. Suddenly you don't have the performance benefits anymore. It embeds really well in other languages, like I said. There are projects for doing data frames in Python that are written in Rust entirely. There are projects for doing database clients, written in Rust, deployed to JavaScript. Really great at that. Error messages that are worth the time to read. Editor support.

Then, this to me, is one of the most important things. The people who work on the Rust compiler. This is a quote from one of them. "Diagnostics should always be clear on what the problem is, and when they don't, that's a bug." What that means is if you are using Rust and you get an error message in your program that you feel does not adequately represent what's actually happening. You can file an issue on the Rust compiler project, and they will go, "That's awful, we should fix that." It's really nice. This results in a lot of error messages getting cleaned up over time. It means that when you see an error message in Rust, it is worth reading, it's worth spending a little bit of time and actually looking at it, which, unfortunately, is not something I can say for undefined is not a function.

Rust + JavaScript

We're talking about Rust and JavaScript. I've been talking a lot about Rust so far, and other people that are using Rust. How can you use Rust and JavaScript? Option one, you write Rust, you embed it in a JavaScript package. People use your package via JavaScript. They import it, use that as normal, everything's great. You publish your Rust binary to npm. You decide, maybe our CLI needs something from Rust that is really important. You publish that Rust binary to npm. You can do that. It works. Third, you can compile Rust to Wasm. This is going to be more on the frontend side. It works in Node, works in the browser. It's more on the frontend side in my opinion. You can also compile Rust to Wasm to run in serverless environments, if that's where you're deploying things. If you're deploying things on Deno Deploy, or some edge network, or something like that, Rust can help you there as well.

Why is it easy to embed Rust? Why should you embed Rust? Rust has no garbage collector, it is not going to randomly stop your application. This is related to the ownership and borrowing features. It basically takes the garbage compiler and it moves it to compile time, like we were talking earlier. It's like zero cost abstractions. You can control when things get allocated, when you open that file, and when you close that file, how long it's in memory. All of that is known at compile time. The Rust compiler can then optimize that at compile time. Then your binary runs really fast. napi-rs is the crate that you would look at if you were going to embed Rust inside of a JavaScript package and let other people use it. We're going to look at it. You can do this with a number of different languages, Python, Ruby, Elixir, JavaScript, whatever you happen to be using, it's probably already there. Downsides of this approach, you have to compile for certain architectures. You have to compile for certain platforms. If you want to run on an M1 MacBook, like the one that I have here, you have to compile for it. If you want to run on Windows, you have to compile for it, and so on and so forth. You end up with effectively N+1 packages. You end up with a series of packages that contain this extra native code, and one package that is the one that everybody is going to install. You can do zero copy communication between Rust and JavaScript, if performance is really important to you, for some reason. You will know if it is because if it isn't, then you don't know what I just said. You can use buffers and typed arrays and stuff. You can just stick some memory over here. When V8 drops it, V8 will drop it and everything will be fine.

Then the other features of the language that you're probably used to in JavaScript, like async, are all supported by this library. This is napi. I know you don't care about Fibonacci. Nobody cares about Fibonacci. This is basically taking the napi library, we're doing what is called a macro on top, macros are literally just code generation. There's some boring binding code that you have to write. That's it. The napi thing is doing that for you. On the top, we have Rust. On the bottom, we have how you would use it in JavaScript. You get to just import it, call the function. It'll call out to the native code. You're probably doing something more interesting than Fibonacci. Then it'll return. This is that N+1 package explosion. It doesn't get that big. It is a maintainer issue. It is not a runtime issue. Your users will not know this. Your users will not care. What ends up happening is you end up with your core package that depends on these optional packages. These optional packages all have CPU and architecture defined in them, so when you build the native code, it goes into these extra packages. When somebody installs it, the optional dependencies will get pulled in based on which architecture they're on. Again, maintenance burden. Users don't have to care.

Two, you can publish Rust binaries to npm. This sounds really simple. You've published binaries before probably. If you haven't, in package.json, there's a bin field, bin path, you're done. It sounds simple. Except we run into that architecture stuff again. It's not as simple as you would want it to be. It's not impossible. It is by no means extremely hard. You have to either manage that binary download yourself, or you have to do what we just looked at with napi and build all of these extra packages. There is something to be said, for the extra maintenance cost. There are libraries that are written to help you handle this binary download. One of them is binary install listed on the screen right now. Yes, you will have a little bit higher of a maintenance burden, because you have to create these extra little steps. You do get to just publish your Rust binary to npm. People can just download it, install it, use it.

Third, and probably one of the more relevant approaches, you can compile Rust to Wasm. The browser and Node will support Wasm. You can compile to a Wasm file, it's a binary format. We don't care about what the binary format is, because we'll never really be looking at it. You compile to a Wasm file, this is how you do it, cargo build --target wasm. You get a Wasm file out instead of a regular binary. Congratulations, you are all Wasm developers now. There are a bunch of tools to help do different things in the Wasm environment, there's wasm-bindgen. This is a tool to bind to existing APIs to help you bind your Rust code to your JavaScript code. wasm-opt is a tool to make things smaller. At some point, we all care about download size, we don't want our Wasm to be 3 megabytes or something. wasm-opt will bring that way down. As we'll see, the Hello World that I have in the slide deck comes down to 12 kilobytes. We'll talk about why too. There's trunk. Trunk is a higher-level tool. web-sys is a collection of wasm-bindgen APIs for all of the browser APIs. That's it. All the browser APIs you use via JavaScript, you have all the bindings already. That's what that is. Something I wanted to mention because it drives home how powerful and how capable Rust is when it comes to Wasm, there are full stack, Signal based, like SolidJS kind of thing, frameworks in Rust, that you can fully compile to Wasm and then deploy on something like Deno Deploy or an edge network. Rust is really good at building Wasm. If you're going to build something in Wasm, and you need to do a lot of computation, you need to do GPU stuff, you need to do something like that, Rust is a really good choice.

This is what Wasm bindings look like. At the top there, we are binding to alert. You would not really do this because web-sys already does this for you, but to drive home the example. Then we have a function called greet, it accepts the name. It uses the alert that we just bound to in the browser, passes it a string, formats it. That's how you use it in JavaScript. You import it, and you call greet. The people who are using this stuff can act like you don't exist if you are writing your stuff in Rust. It doesn't matter to them. It doesn't have an impact, other than the fact that you wrote it in Rust. This is what it looks like. You do the cargo build wasm32, you get a Wasm file. That Wasm file you probably sent through wasm-bindgen. In this case, I'm sending it to a folder called out. I'm targeting the web. You can also target Node or other platforms. You get these files. In this case, this is the default. This is what you get if you literally just copied the code I just showed you, throw into a completely unconfigured cargo project and build it, and you get TypeScript types. You get a JavaScript file, because the browser needs a JavaScript file to download the Wasm, and you get the Wasm file. Again, you can get this down to 12 kilobytes. About 10 kilobytes of this is the allocator, which is a word that probably doesn't mean anything to you. That's totally ok, you're probably going to want it at some point. If you were trying to get really small, I wanted to point out you can cut another 10 kilobytes off this easily. There are tools to tell you what's in your Wasm bundle. Has anybody used webpack analyzer or anything like that, the stats stuff from any of these bundlers? This is effectively that. It will tell you all the functions you're using, where you're using them, how much you can save if you remove them. In this case, if you look at this, anything that says dlmalloc is the allocator. Anything that says formatter, is that format that we had. If you remove both of those things, you are building what's called a no standard Rust library. You can get rid of the entire Rust standard library, and you don't have to use it if it doesn't apply for your application, or what you're trying to do. You can make these things very small. You can do no allocation Rust programs. This becomes very similar to how you would write Rust for an embedded microcontroller. There are people out there working on making all of that really easy, and you just get the benefit.

Intro to Rust

We're going to start covering an intro to Rust. How many of you have actually written Rust before at all, even Hello World? This will be something we spend some time on. On the left, you have Rust. On the right you have JavaScript. If you want to manage your Rust version, it's going to be with rustup. JavaScript, nvm, volta, n, fnm. The thing that I really want to send home with this slide is basically on the left-hand side, everything is cargo: cargo test, cargo build, cargo doc. On the right-hand side, pick whatever you want. You can extend cargo. You can cargo install cargo-watch, for example, and that gives you a watch command with cargo. It means that you can do cargo watch, build, and every time you change a file, it will keep building. You can do this for a number of different things, cargo lambda, for building Rust, AWS lambda functions. Flamegraph for profiling your Rust runtimes. espflash for flashing to an ESP32 microcontroller. Cargo dist and release for releasing not just to crates.io, the package registry, but also to Homebrew and GitHub releases, and whatever you want to do on Windows. Cargo insta, snapshots. If you use Jest, you like snapshotting, cargo insta. Then there's a whole bunch of those.

Let's get a little bit into the language now. This is Hello World. On the right-hand side we have Node. This is exactly what the Rust code is doing. All of you have seen a function definition before, I'm sure that shortening that to fn is not too hard for you. Println is a macro. Macros generate code for us. In this case, the println macro is basically console log for us. At the top, who's heard of a shebang/used one before? Basically, on the right-hand side, we can ./ this JavaScript file and it'll run using Node. On the left-hand side, we're using cargo script, which is a new feature, which is why I'm using nightly. Has anybody used node-nightly, or any nightly distribution of the Node? Nobody does that. It's really easy to do in cargo, you do +nightly. Then you can use the nightly stuff, you can just test something out, it's fine. If we do ./ on this, Rust is a compiled language, it needs to compile to something. In this case, we're compiling to a binary that runs on my M1. It gives you the name of the program that you're compiling, the version, where it's located, the profile you built with. If you write Rust in the future, don't forget the release flag. If you run Rust, you feel like it's slow, 50% of the time, somebody has forgotten to use the release flag. This is dev mode, unoptimized, has a whole bunch of extra stuff, for, as you might imagine, developing.

Variables. I would compare this to const and let, but I really don't want to start that discussion. We've got let for immutable and let mute for mutable. We've got two variables here, x and y, we're setting both of them to 5. Y is mutable, so we can change it, x is not so we can't. That's why it's commented out below. Debug is another macro, it's really useful. It gives you, if you look on the right-hand side, the name of the file, the location in the file that the debug came from, the expression that you passed to debug, and the value of that expression. It's really nice. As you might expect, 5, 5, 5, 10, because we only changed y. If we do try to change x, this is the error messaging I was talking about, worth reading. This error message has a code at the beginning. If you want to go to the web, you can actually look that up, find more examples. In this case, cannot assign twice to a mutable variable x. It has the line where we did our first assignment, the line where we're trying to do our second assignment, and none of the other lines that we don't care about. Then it points to it, it says we can't assign twice. This is our second assignment, we're trying to do it again. In the help text, we've got our first assignment to x, and then some help. It says, maybe you forgot to put mute here. That would fix our problem, if that's what we wanted. If we didn't want to mutate that, we could find a different solution. Rust compiler message is very useful, very readable. I encourage you to read them when they come up. Structs, typed objects is what you want to think of these as. You write TypeScript. These are typed objects.

The derive debug on top is used for the debug macro. Macros are almost always used to generate some boring code that we don't want to write. You could implement debug for this struct if you wanted to. It's boring. You're just going to print out name and height, and everybody knows that, the Rust compiler knows that, we can just derive it. We construct it in the same way that you would construct any other object, person, name, height, debug it out. This is what we get from the JavaScript, just an object. I'll go back for a second, so you can see the JavaScript code here is just an object, and then we're logging it. Rust gives us more output. Why? Because we aren't doing that much. We aren't using a bunch of stuff. Rust knows that. Dead code elimination is something you just get with Rust. You do not have to worry about it, it is there. If you don't use something, Rust will know about it, it will tell you about it. You can choose whether you want to use it or ignore it. In this case, right at the bottom, we've got our person debug out. It's telling us basically we haven't used name, and we haven't used type.

Enums, this is roughly what it would be like in JavaScript. Symbols I know aren't terribly used in the industry. They're just not around very much. Basically, an object with some string, and then fruit.apple. On the Rust hand side, we've got enum, it's got apples, pears, bananas, whatever. We can instantiate it, debug it out. It's one of many variants, is the phrasing. We run the JavaScript side, we get a symbol out, because we are printing out the symbol. Again, we run the Rust side, it tells us that we didn't use a bunch of stuff. Specifically, we didn't use pear, and we didn't use banana. We used apple, and it knows that. The Rust compiler is really good at knowing when you use something or not, telling you and also removing it from your final stuff. Let's say somebody gave us one of these enums, handed it to us, got it from user code, got it from a database, whatever, we don't know what apple is right now. We just have a variable. I'm using match syntax. Match syntax will let me match on this variable. The little box here is rust-analyzer. rust-analyzer will let me fill the match arms and automatically write that code for me. Now I have all of the variants that could possibly be there already written. I don't have to worry about typing them all out. We match on the variable, when it's apple, we do something, when it's pear, we do something, when it's banana, we do something. Other interesting thing, the todo macro. If you have a spot in your Rust application and you are not ready to finish it yet, you can write todo. It may be interesting to know that that will also short circuit the type inference. If you have an extra branch, and you need to return something from that branch, and you type todo, the Rust compiler will just be like, he's going to write that code, or they're going to write that code, or she's going to write that code, they're going to write it later. That's it. It's really nice. If we drop that back in a script file, so we can run the whole thing. Right at the bottom, not yet implemented. That's the todo macro. If we get an apple, and we haven't written the code for the apple yet, our program will crash. It'll go, you said you were going to write this and you didn't. You can include messages in the todo macros. Not yet implemented: apples are for later, give a little bit more context.

Enums can hold data. This is really why I introduced structs, and then why I introduced enums. Who's used Redux or something like it? All those actions, all those objects that have type whatever, and then some data, and then that switch that you have to do, this cleans all that up. If you've got an action, let's say, AddTodo, you can just write it like a struct inside of enum. It's one of the variants, you can construct it later. RemoveTodo takes an ID. CompleteTodo takes an ID. We can construct an AddTodo, let's say, some user submitted a form. It's got an ID. It's got some text. We can match on which action is coming in. If you've used JavaScript before, you've probably used destructuring, that's what we're doing here. We're destructuring the fields out of these variants. ID in text and AddTodo, and then ID in the other two. If you run this, it will again tell you, you didn't use ID. You destructured it out of this thing, you didn't use it. We can use underscores to silence those messages if we're intentionally aren't using something. I've shown you a couple times, I don't want you to think that it's just going to fill up your console every time. If you don't intend to use something, you throw an underscore on it, that warning goes away.

Options, built-in enum. There's a lot of built-in enums. Option is one of them. Bottom right, you can see the definition for an enum, it uses a generic. Don't worry about the generic, it's either none or it's some and it contains a value. There's the two options. If we have maybe something and we have some number, say 200, we can then do the exact same thing we just did, match on that variable, destructure those things out. In this case, we're just returning number if it's a number. If it's none, it's 0. Because Rust is an expression-based language and these values at the edges of these branches return through that match expression, our number becomes that number, and then we debug it out.

You can see the output of that, it's 200 in both cases. That's wordy, though. We have other functions for that. You can unwrap_or 0. If you have defaults, do it like this. Iterators, you've got a for loop, and we've got something that I'll call just an iterator. The for loop uses a range 0..10. In the other case, we've got characters, a to z. In the first one, we just iterate over it. We debug out. You can see the output on the right. It's as you would expect. The alphabet collects into a string. Collect is a super powerful function. It's taking that type that we want. It's saying, can I turn the items of this iterator into that type? Then it's doing it if it can. If it can't, it will give you a compile error message.

How does that work, though? These things are iterators. What that means is they implement the iterator trait. Traits are interfaces. Just treat traits as interfaces. You have to define an item that you're iterating over, an item type, u32, a character, whatever it is. You have to define the next function. That's really important. Everything else can be derived from that next function. We had ranges before. We had ranges of numbers, and we had ranges of characters. There's an implementation of iterator for anything that you can put in a range. You can see a little bit below where I have highlighted, it's got a next function. What does that mean in practice? It means that for loop and this collect are just syntactic sugar for calling next over again until you return none. That's all it is. There's no magic going on here. Traits are interfaces.

Add function takes a usize. Usize are architecture sized unsigned integers, so any positive integer. Config test, this code even though it's written in the same file as your regular code will not compile into your final binary, it will only run when you cargo test. In this case, tests are regular functions. You write the test attribute macro. If it panics, it fails. If it doesn't panic, it doesn't fail. You get all the asserts that you're normally used to. This is what it looks like. You get Async/Await. The key thing here is promises, futures, basically the same thing for now. Await also on the tail end, not in the front. This means you can just keep chaining stuff together, which is actually really nice. rust-analyzer, there's a tokio main, that comes from a third-party crate that has a macro that generates some code. rust-analyzer will let you expand that macro, take a look at what it's doing. This is what it's doing. It's regular Rust code. It is not magic. It's just generating some code that is otherwise tiresome to write. This is your one slide on borrowing. This is it. You have a dog. It's got a coat color. You can share that dog with somebody so they can pet it, and then they give it back to you because it's your dog. You can give it to maybe a dog barber, with what I'll say an exclusive reference. The first one was a shared reference. You're sharing it with somebody. Second one is a mutable or an exclusive reference. You're giving it to somebody, then they can do whatever they want to it. You can see these functions on the right-hand side. We can set the coat color to whatever we want. In this case, orange. They give it back to us though. It's a reference. We give it to them temporarily, we get it back. We can also just give them the dog. Somebody wants to adopt the dog, we painted it orange, so that it would be more adoptable on Halloween. Somebody came in was like, I love that orange dog. We gave it to them. They own it now. We can no longer pet the dog. We don't have access to the dog, somebody else does. In this case, it's the function, the function owns it. If we do try to pet the dog, this is the error message you get. It's not going to be very intuitive. Basically, when we gave the dog away, we moved the dog into that function. We can no longer access the value inside of that variable. That's all that is. Borrow checking, not super scary. Don't have to worry about pointers. It's just giving and taking references.

How is Rust Useful, and What Is It?

How is Rust useful? Standalone build tools. Embed in your npm packages. Wasm in the browser or Node. You can do serverless functions. You can do things with industrial strength grades like wgpu, CSS Parser, whatever. What is Rust? Rust is a language empowering everyone to build reliable and efficient software. I really believe in that empowerment message. This is on the Rust-Lang website, rust-lang.org. This is how Rust is marketed.

 

See more presentations with transcripts

 

Recorded at:

Mar 04, 2024

BT