We're sorry but this page doesn't work properly without JavaScript enabled. Please enable it to continue.
Feedback

The Rust Borrow Checker - A Deep Dive

00:00

Formal Metadata

Title
The Rust Borrow Checker - A Deep Dive
Title of Series
Number of Parts
8
Author
License
CC Attribution 3.0 Unported:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal purpose as long as the work is attributed to the author in the manner specified by the author or licensor.
Identifiers
Publisher
Release Date
Language

Content Metadata

Subject Area
Genre
Abstract
The Rust compiler's borrow checker is critical for ensuring safe Rust code. Even more critical, however, is how the borrow checker provides useful, automated guidance on how to write safe code when the check fails. Early in your Rust journey, it may feel like you are fighting the borrow checker. Come to this talk to learn how you can transition from fighting the borrow checker to using its guidance to write safer and more powerful code at any experience level. Walk away not only understanding the what and the how of the borrow checker - but why it works the way it does - and why it is so critical to both the technical functionality and philosophy of Rust.
Virtual realityElectronic mailing listCodeCompilerFunction (mathematics)Vector spaceCompilerFunctional (mathematics)Loop (music)Line (geometry)Letterpress printingText editorWorkstation <Musikinstrument>Staff (military)IntegerCompilerStreaming mediaSlide ruleCodeMoment (mathematics)Right angleMessage passingContent (media)Figurate numberSurfaceTouchscreenBitContext awarenessOrder (biology)MereologyLevel (video gaming)Multiplication signTwitterTouch typingMathematical analysisFrustrationOnline helpMusical ensemblePresentation of a groupNumberComputer animation
Electronic mailing listCompilation albumMathematical analysisParsingSource codeVirtual realityMathematical optimizationCode generationCompilerQuery languageElectronic program guideInformationParsingComputer programToken ringNumberAbstract syntax treeAbstractionNetwork topologyMacro (computer science)CodeExpandierender GraphElectronic visual displayCore dumpLoop (music)Representation (politics)Data structureTouchscreenSurfaceCodeNumberCompilerToken ringMathematical analysisComputer programmingCompilation albumParsingParsingAbstract syntax treeElectronic program guideSoftware developerInformationFunctional (mathematics)Level (video gaming)Content (media)Loop (music)Source codeStatement (computer science)Macro (computer science)Mathematical optimizationIntermediate languageData structureRaw image formatOnline helpLinear codeCompilerMatching (graph theory)ArmAbstract syntaxMappingData storage deviceBinary codeSheaf (mathematics)Focus (optics)Control flowLine (geometry)Latent heatProcess (computing)Electric generatorElectronic mailing listMultiplication signIterationLetterpress printingModule (mathematics)Point (geometry)Context awarenessComputer animation
Virtual realityCompilerElectronic program guideSource codeRepresentation (politics)Block (periodic table)Data structureControl flow graphStatement (computer science)InformationCompilation albumMathematical analysisParsingMathematical optimizationCode generationProgrammer (hardware)CodeBinary codeModul <Datentyp>UsabilityMachine codeVirtual machineCASE <Informatik>Correspondence (mathematics)Semiconductor memoryFrame problemOperator (mathematics)Line (geometry)Machine codeCodeMathematical optimizationFocus (optics)Level (video gaming)Electric generatorCompilation albumPoint (geometry)Source codeGame controllerGraph (mathematics)Condition numberComputer configurationSlide ruleMatching (graph theory)ResultantRadical (chemistry)Block (periodic table)Computer programmingElectronic program guideStatement (computer science)Intermediate languageModul <Datentyp>Computer fileCompilerBinary codeChainData structureNumberCompilerError messageLoop (music)Software developerInformationSequenceOrder (biology)Direction (geometry)BuildingData storage deviceDifferent (Kate Ryan album)Execution unitVirtual machineMoment (mathematics)Computer animation
Electronic mailing listVirtual realityString (computer science)Point (geometry)BitNeuroinformatikCompilerNumberSource codeString (computer science)CodeError messageVariable (mathematics)Type theoryReading (process)Different (Kate Ryan album)Moment (mathematics)Computer animation
Error messageElectronic mailing listInformationString (computer science)Virtual realityCodeError messageResultantLine (geometry)TrailType theoryString (computer science)Point (geometry)Semiconductor memoryCorrespondence (mathematics)CompilerFrame problemLetterpress printingComputer animation
Error messageInformationElectronic mailing listContent (media)Function (mathematics)Error messageCompilerRepresentation (politics)CodeMessage passingInformationSource codeIntermediate languageLine (geometry)JSON
Electronic mailing listString (computer science)Virtual realityVideo trackingInferenceElectronic program guideSource codeVariable (mathematics)Drop (liquid)Line (geometry)CodeMessage passingCompilerMultiplication signTrailWordSound effectTerm (mathematics)Point (geometry)Computer programmingError messageInferenceVariable (mathematics)String (computer science)Computer animation
Error messageElectronic mailing listDrop (liquid)Staff (military)Text editorWorkstation <Musikinstrument>Drop (liquid)Cartesian coordinate systemError messageCodeMereologyPhysical systemFormal languageSemiconductor memoryMetropolitan area networkSelf-organizationMultiplication signMusical ensembleInteractive televisionStreaming mediaLibrary (computing)MathematicsPresentation of a groupPerspective (visual)VotingTwitterReading (process)Computer programmingWeb 2.0Dependent and independent variablesFront and back endsImplementationGroup actionPressureSlide ruleSoftwareProgrammer (hardware)Digital photographyOnline helpSign (mathematics)Power (physics)IterationAreaFocus (optics)Capability Maturity ModelCASE <Informatik>Bit rateDecision theoryProcess (computing)Cellular automatonRight angleYouTubePrototypeMechanism designAbstractionEndliche ModelltheorieVariable (mathematics)ComputerArithmetic meanParameter (computer programming)Cue sportsStack (abstract data type)LogicType theoryLine (geometry)Bit error rateLink (knot theory)Buffer overflowGoogolFormal grammarStandard deviation1 (number)Software testingWorkstation <Musikinstrument>DatabaseDisk read-and-write headConnectivity (graph theory)Level (video gaming)CompilerBitPoint (geometry)Sound effectOrder (biology)SpeicherbereinigungComputer scienceProgramming languageBroadcasting (networking)System programmingFunctional (mathematics)Core dumpCompilation albumSoftware developerElectronic program guideInformationNeuroinformatikZoom lensGoodness of fitSinc functionComputer animation
Transcript: English(auto-generated)
All right, hello everyone around the world. Thank you so much for having me today here at Rusty Days. It is late afternoon here in Seattle, Washington, but I hope whatever time it is in your part of the world, you are doing well. When I work with someone who is just learning Rust,
they sometimes question whether the Rust borrow checker is their friend or their foe. One second, I am getting delayed audio for some reason.
All right, we're having some technical difficulties here. Let me fix this, one moment.
All right, folks, for some reason, I was getting some delayed audio there in my headphones. So what I'm going to do is just take the headphones off and continue the presentation.
So I apologize for this. Let's go ahead and go back to the beginning. When I work with someone who is just learning Rust, they sometimes question whether the Rust borrow checker is their friend or their foe. If you take a look at the Rust subreddit, it's common to see posts like this.
Newbie question regarding the borrow checker. Help fighting the borrow checker. And does it ever get easier fighting with the borrow checker? So it's common to feel a lot of frustration when you're first starting with Rust. However, as I watch people get more experience in Rust,
they tend to come around to the borrow checker and realize what it is protecting them from doing. So in answer to the question, is the borrow checker a friend or a foe? I say the borrow checker becomes your friend through gaining experience with it.
And along with getting experience, it's also very helpful to understand how it works and why it does the things it does. We're going to dive very deep into that. But before we do, let's briefly cover who I am. I'm Nell Shamrell-Harrington. I am a senior staff research engineer at Mozilla.
If my voice sounds familiar to you, I am the lead editor of This Week in Rust and host of the This Week in Rust podcast on the RustStation station. If you ever want to get in touch with me, honestly, Twitter is the best. Go ahead and tweet me at at Nell Shamrell. And let's get back to the presentation.
Just to set expectations, we're going to take an overview of the Rust compiler in order to understand the context in which the borrow checker works, it's important to understand a little bit about how the Rust compiler works. And then we will dive very deep into the borrow checker after.
So let's start with that overview of the Rust compiler. Let's take a look at this code example. This Rust function declares a vector composed of the integers one, two, three, four, and five. And after it declares this vector, it then uses a for loop to iterate
through each integer in the vector and print it out on a new line. Now, if I were to run this using cargo run, I would see these five numbers, one through five, printed out on the screen. Now, this seems pretty simple. Cargo builds and runs this piece of code for us,
but there's a lot that happens underneath the surface in the compiler when cargo is building it. There are five general stages to compiling a piece of code. It starts with lexical analysis of the code, then parsing of the code, semantic analysis of the code.
This is where the borrow checker comes in. And one moment, I'm getting a message. Looks like we're having some problems with the slides. Let me go back a little bit.
I'm not sure why the slides are not updating, but we are going to figure that out right here.
And we're gonna get this figured out for you folks. Give us just a moment.
What I'm going to do is I'm going to stop presenting for a moment, and then I will restart presenting just to see if that fixes the issue with the slides not updating. One moment, please.
And let's do this again. I think this should fix the issue. One moment.
Let me see if the slides are updating on the stream. I apologize, folks. This is what happens in live presentations. Sometimes there's some technical difficulties, but I promise there's some great content coming from here.
All right, I'm getting the message that it seems to be okay right now. So let's go ahead and go back to this. So as I said before, if you're looking at the Rust subreddit on Reddit, it's common to see questions like newbie question regarding the borrow checker. Help fighting the borrow checker. And does it ever get easier fighting
with the borrow checker? However, as you watch people gain more experience in Rust, they tend to come around to the borrow checker and realize what it protects them from doing. So in answer to the question, is the borrow checker a friend or a foe? I say the borrow checker will become your friend
through experience. And along with gaining experience with it, it's very helpful to understand how it works and why it does the things it does. We're going to dive very deep into that. But before we do, let's briefly cover who I am. I already went through this. I'm gonna go ahead and skip it,
but I will put my details back up at the end of the presentation. And let's get back to our overview of the Rust compiler. So here's the code that I want you to be looking at. And if we look at this Rust function, we see that we declare a vector composed of the integers one, two, three, four, and five.
And after it declares this vector, we use a for loop to iterate through each integer in the vector and print it out on a new line. If I run this with cargo run, as expected, I will see the numbers one through five printed out on the screen.
Now this seems pretty simple. Cargo builds and runs this piece of code for us, but there is a lot that happens underneath the surface in the compiler when cargo is building. There are five general stages to compiling a piece of code. It starts with lexical analysis of the code,
then parsing of the code, semantic analysis of the code, this is where the borrow checker comes in, optimization of the code, and finally code generation, where the compiler creates that executable binary of our code.
When we look at the stages laid out in a list like this, it might seem like they will run linearly. And in some compilers they do. However, if you've delved at all into the Rust compiler internals, you might be thinking, wait a minute, isn't the Rust compiler at least partially query-based
rather than linear-based? And the answer to this is yes, it is partially query-based at this time, but that is out of the scope of this particular talk. For the sake of clarity, I'll speak to the internals of the compiler as if they were functioning linearly. However, if you want to delve more
into how the Rust compiler is query-based and what that means, check out the guide to Rust C development for more information. This guide has been a big help to me as I have learned myself how to hack on the Rust compiler. So check it out and get all the information you want about the Rust compiler and more.
Going back to the stages of compilation, let's start with the first one here, lexical analysis. During lexical analysis, a program within the compiler called a lexer takes the raw Rust source code called a lexeme in this context,
and analyzes it, and then splits the code into tokens to make it easier for the compiler to parse. And speaking of parsing, we go on to the next stage of compilation called conveniently parsing. And in the parsing stage, what happens is a program within the compiler
called a parser takes those tokens created in the previous stage and analyzes them, and then translates them into an abstract syntax tree or AST. Having the tokens in this data structure makes it quicker and easier for the compiler
to do the rest of its work. And now, after it's done the parsing, the Rust compiler, before it moves on to that next stage of compilation, will take the abstract syntax tree generated by the parser and first expand any macros included in the code.
If we look back at our original source code, println, our line here, is actually a macro. So at this point, this line would be expanded to something that looks like this. This is what the full expanded println macro looks like. And how it would be represented
in the abstract syntax tree. The next thing the compiler does is it also de-sugar some of the syntactic sugar that makes writing Rust so delightful. For example, in Rust, the for loop is a piece of syntactic sugar for an iterator.
If we were to de-sugar this section of code, it would consist of both a match statement and a loop. The functionality of this code is identical to the for loop in our original source code, but de-sugaring it like this makes it easier for the compiler to understand and optimize it.
Something I like to say is sugared code is for the human who's writing and reading the code. De-sugar code is for the compiler. And at this time, the compiler also resolves any imports in the code. So if you are bringing in an external crate or using internal crates or modules, those would be resolved here as well.
And finally, after the compiler does all these things, it converts that abstract syntax tree into the higher level intermediate representation or HIR. Some people say here, some people say HIR. I generally prefer HIR.
So let's pause here and take a closer look at how that HIR is constructed. It helps to understand the data structures that make up the HIR. The first data structure is a node. This corresponds to a specific piece of code. This is identified by an HIR ID.
And that node belongs to a definition. A definition is an item within the crate that we are compiling. Definitions are primarily top level items within the crate. This is identified by a DEF ID. And that definition, defined by the DEF ID,
is owned by the crate data structure. This is the crate we are compiling with Rust. This data structure stores the contents of the crate we are compiling and also contains a number of maps and other things that help organize the content for easier access throughout the compilation process.
This crate is identified with a crate num. So looking back at our original source code, let's focus on this section here, that for loop. And remember that this loop desugars into a match statement and a loop.
If we look at the node in the HIR that represents this match statement, we would see something similar to this. Now this is a little hard to read for humans, so let's break it down. This arm structure represents a single arm of the match statement that our for loop desugared into.
And then we have the HIR ID for this piece of code. This identifies a node that corresponds to that code within the HIR. And that node is owned by a definition. The definition is some top level item in the crate. And that definition data structure is owned by a crate data structure.
So our for loop is a node within a definition within our crates. And that's how we can identify where this node corresponds to in our original code. What also helps us do that is what is called a span.
The span stores the file path, line numbers, and column numbers of the original source code. This will be very important in the future as we optimize and desugar the code. If we encounter a problem with the code, if the compiler encounters a problem after it is desugared and optimized,
we still want to be able to show the user where in their original code that they wrote that the error was generated. If we were to have a problem with the desugar code and show them those lines, it probably wouldn't mean that much to them because it's different from the code they originally wrote. And at this point, the compiler then takes this HIR
and lowers it again into the mid-level intermediate representation, also known as the MIR. The MIR is constructed as a control graph. The units within this control graph are called basic blocks,
which are identified with values like BB0 and BB1. Within these blocks, each has a sequence of statements that execute in order. The very last statement is known as a terminator. This is where we get the go-to to go to another basic building block. And this is how the program proceeds
throughout its structure. Now, this is a pretty simple example. There's only one direction the basic blocks can go. But if our code had an if-else statement like this, where we have a condition, if that condition is true, we do one thing. If it's not true, we do something else.
The control graph would look like this. The terminator of BB0 would have the option to either proceed to BB1 or to BB2. In this case, there is more than one path within the program that it can take when it encounters a terminator within a basic block.
If you are curious about more about the MIR, there are definitely more data structures involved in it. Again, check out that guide to rusty development for more information. Let's go back to our desugar code. And let's take a focus on this match statement,
which is assigned to a variable called results. If we looked at the MIR for this piece of code, it would look similar to this. And I have simplified it for the sake of appearing on a slide. Up here in the top left, you can see we have our basic block, which in this case is identified as BB2.
And then we have what is called a local. A local in the MIR represents a place in memory, or more specifically, a place on the stack frame. In this case, underscore five corresponds to the value of the variable result. And like in the nodes in the HIR,
we have a span, the piece of the original Rust source code that each node in the MIR corresponds to. Again, if we encounter an error when we're operating within this MIR, we can still easily refer to what lines in the original source code caused the error.
And that brings us to this third stage. And this is big in the Rust compiler, semantic analysis. This is where the compiler tries to figure out what the program is trying to do in a way the compiler can understand it and then translate it into machine code.
And at this point, after it's lowered the HIR into the MIR, the compiler will run several checks on the MIR, including the borrow checker. Now, we're going to come back and dive deep into the borrow checker in just a few moments. But for now, let's focus on these last two stages
of compilation, optimization and code generation. These stages are where the code is transformed into an executable binary. In the Rust compiler, we use LLVM to do this for us. LLVM is a commonly used collection of modular
and reusable compiler and tool chain technologies. The Rust compiler uses it to further optimize the code and generate the machine code to run it as an executable. Before it uses LLVM, the Rust compiler takes the MIR we created earlier
and lowers it into the LLVM intermediate representation or LLVM IR. And the LLVM IR is pretty unreadable to humans, but it looks something like this. As we can see, the LLVM IR is still constructed
as basic blocks, like in the MIR. And after this, the compiler then uses LLVM and takes in that LLVM IR that we created and then it runs more optimizations on it and emits machine code.
And it then links the machine code files together to produce the final binary. And this means when we run our code with cargo run, we see those numbers printed out. So yay, that gives you a bit of an idea
of how the compiler works to take your Rust source code and make it something a computer can execute. And at this point, I do want to go back and take a deeper look at the borrow checker. And for that, let's use a different piece of code.
If you are looking at this code, if you're reading it and thinking this will error out, you are right. And we'll see how and why that happens in just a moment. First, we declare the variable x and give it the type of string. Then we set the value of x to the string high rusty days.
Then we see that the value of y is equal to the value in x. And then we attempt to print both variables. If we were to try to build this piece of code as it is now with cargo build,
it would give us an error. And this error is the result of the borrow checker. We are trying to use a value or we are trying to borrow a value after it has already been moved. Let's go through how the borrow checker identified this error.
The borrow checker does several things, including tracking initializations and moves. How this plays out in our code is when we start with this first line, where we declare the variable x to have a type of string, x is not actually initialized at this point.
It won't be considered initialized until it is assigned a value. And if we look at the MIR for this line of code, we can see that x is represented by the local underscore one and local underscore one is assigned the type of string. So this is the MIR that corresponds to this line of code
and this is what the compiler is analyzing. Now let's look at the next line where we create the high rusty days string and assign it to be the value of x. Now that x has a value, it is considered initialized at this point.
So x is initialized and the borrow checker is aware of that. We create, to do this, we create a new place in memory, local underscore two, where we store the high rusty days string and then we move the value stored in underscore two to underscore one.
Remember, underscore one corresponds to the value of the x variable. So we have created the string in memory and moved it to be the value of x. Now let's look at this line where we attempt to create the variable y and assign it the value of x.
This line is where the value of x is moved to y. It's not duplicated, it's moved. If we look at the MIR created for this line of code, we see that y is assigned to the local underscore three. Remember, a local represents a place in memory
or a place on the stack frame. And underscore three is given the type of string. Then the value at underscore one, remember that represents the value of x, is moved into the value of underscore three, which represents the value of y.
This means when we get here in our code, when we try to print x, we try to print the value of x. X is not initialized at this line, so we cannot print it. Once that value is moved, x is no longer considered initialized.
And that's what generates this particular error. We're attempting to use the value of a variable after it has been moved. And something I'd like to specifically call out is how the compiler shows where the error was generated from the original source code.
This is that span that is attached to all the nodes of the HIR and the MIR. So even though we had lowered this code into MIR, our mid-level intermediate representation, we still tracked what items in the MIR corresponded to what places in the original Rust code.
And this is very helpful to the end user. What is also helpful is this. The Rust compiler not only tells you what the error is and where it is, it gives you a command to get even more information about the error so you can fix it. And let's go ahead and run this command.
We run this command, we see not only an explanation of the error, but also a piece of example code that would generate it. Then the message gives you even more information not just about what the problem is or where it originated, but how to fix it.
This suggests using a reference to borrow a value rather than attempting to move the value, which is what we saw done in the MIR representation of our code. So let's do what this says. Let's take this advice and change this line so that Y is assigned to a reference to the value of X rather than moving the value of X into Y.
And let's go ahead and run this again. And once the compiler builds the code and executes it, we see the message high rusty days printed out twice. Rather than fighting the borrow checker, we used it to make our code even better.
Along with tracking initializations and moves, the borrow checker also deals with lifetime inference. And let's go over exactly what that means. Rust uses the word lifetime in two distinct ways.
The first way is to refer to the lifetime of a value. That's the span of time before the value of the variable gets freed. Another word for referring to the lifetime of a value is referring to the variable scope. And let's see how this plays out in our code.
And let's start with this first, let's start with the second line where we assign the value of X to this high rusty day string. At this point, X is live. It's initialized, its lifetime begins here. When we get to here and move the value of X into Y, this is the end of X's lifetime.
And that means when we get down here and we try to use X again with our uncorrected code, X is dead. Its lifetime is no longer in effect and it is no longer initialized. This is why this program generated the error that we saw. The other way Rust uses the term lifetime
is to refer to the lifetime of a reference to a value. This is the span of code or span of time in which the reference can be used. Let's look at that corrected code where we created a reference.
So here we assign the value of Y to be a reference to the value of X. If we look at the MIR for this line of code, we remember that the local underscore one refers to X and the local underscore three refers to Y. And we see that underscore three or Y
is assigned a reference to the value of underscore one or X. This is how a reference, creating a reference looks in the MIR. And this is what the compiler is analyzing with the borrow checker. Looking back at our code, let's alter this slightly.
And let's try to drop the value of the variable X before we try to print out the value of Y. So we're dropping the value of X here. If we do this and then try to build our code with cargo,
we will get another borrow checker error. We cannot drop X, we cannot move out of X because it is borrowed and that borrow is used later. The borrow checker tells us that X, because it is referenced by Y, needs to stay alive for at least as long
as Y needs to stay alive. That means X's lifetime must be greater than or equal to Y's lifetime. Looking at this in the code, again, this is X's lifetime, this is the lifetime of X. And this is what needs to be the lifetime of Y,
which is a reference to X. Notice that even though these two lifetimes overlap, X's lifetime ends before Y's lifetime is supposed to end. That means that once we drop the value of X,
again, X's lifetime is over and Y can no longer reference the value of X after this line. So at this point, in this last line, X would be dead, its lifetime is no longer in effect and Y would not be able to reference it. Again, in order for this code to compile, the lifetime of X must last at least as long
as the lifetime of Y. And how these two ways, the overarching way, the scope and the lifetime of a reference relate to each other, is that if you make a reference to a value, the lifetime of that reference cannot outlive the scope of the value.
This is something that gave me a lot of trouble early in my Rust days. And once I understood how it worked, it made it much easier to do my Rust. Now, as I move toward concluding this presentation, I want to make sure that you know that there is so much more to the Rust compiler and the borrow checker.
Again, if you want to know more, check out the guide to Rust C development for more information. It is a fantastic resource. It has been so helpful to me as I've taken my own steps, not only into using Rust, but hacking on the Rust compiler itself.
Going back to this question from the beginning, is the borrow checker a friend or a foe? My answer is it's a friend, though a very strict friend. But the best thing about this friend is it will not only tell you when you do something wrong,
it will also tell you how to fix it. And I find that to be one of the best qualities I can find in a friend, as well as one of the best qualities I can find in a compiler. Thank you. There is my information again, if you want to reach out to me, and I'm ready to take some questions.
I see that we are waiting for questions.
Certainly hope this is helpful to you. It's been helpful to me just creating this presentation.
All right, just so you know, if you're watching at home,
there is a little bit of a lag. So if you see me just standing here, I'm curious, because I'm waiting for the broadcast to catch up with the lag, and I'm looking forward to your questions.
Sure anyone out there doesn't have questions?
I know I had a lot of questions about how the compiler works when I got started. More questions about the borrow checker. You can ask questions about this week in Rust if you like. Twitch user CRD477 says, thanks for the talk. You're welcome. Rust ownership model seems to be pretty unique
in the PL world. I'm assuming PL means programming language. Has anything like it been tried in previous languages? Well, the answer is that is I don't know. I don't know of any ownership models in the programming language that I've used at least that come anywhere close to Rust.
In my experience, Rust is pretty unique, at least as it comes to mainstream programming languages. So if anyone knows the programming languages that use an ownership model similar to Rust, please go ahead and send it to me. I'm curious. All right.
Next one is from Twitch. Uwedrache, U-W-E-D-R-A-C-H-E asks, is X's lifetime extended by being borrowed by Y? And the answer to that is no. X's lifetime will end when we drop it, even if it's borrowed by Y.
The nice thing about the borrow checker is, as you saw, that prevents us from trying to compile the code when X's lifetime runs out before Y's. Next question from Damian on YouTube is, did you have any bad habits from other language
that made borrow checker unhappy? If so, what were such habits? Yes, I came from the Ruby world. Like surprisingly, a lot of people in the Rust world. So I was very used to duck typing. I was very used to not really paying attention because I had the garbage collector
to when my variables went into scope or out of scope. So breaking those habits was hard. I was definitely, it definitely took a few months, but it now feels like the Rust code I write is so much safer than the Ruby code I wrote or the C-sharp code I wrote before that, JavaScript, et cetera. So I really like Rust.
Let's see here. Exa Cage from YouTube says, would you recommend some techniques like RCs or ECS? I'm not entirely sure what you're referring to like that. RC might mean ref cell, but I'm not sure what ECS stands for.
So if you don't mind, if you clarify that, I'm happy to come back to the question. And all right. So someone from Twitch, Mr. Ed Makes says, in your last example, where the lifetimes of X and Y overlap, do you know why it was decided to be an error
as opposed to having Y take ownership? And this is something that has been brought up a lot. I actually, in the issue of This Week in Rust that is going out later today, someone writes, Boats, who's a Rust community member, writes an article about what would it be like if we had a smaller Rust?
And I believe it's in that article that he covers, what if we could have, if lifetimes of X and Y overlap, or why couldn't Y take ownership? And the answer to this is I'm not sure. I know it was a decision made early in the Rust process. I can probably find the RFC that explains it,
and I will try to do that after this talk and tweet it out. Next question is from Parker McMullen on YouTube. And they say, do you ever see a future where the borrow checker moves from helpful compiler hints to being more abstracted and inferring what the programmer meant to do while still staying optimized?
I could see it if we made a good case for it, if we did not have to give up any of the safety mechanisms that are in Rust right now. The reason so many people love Rust, especially when they come from the C++ world, is they feel very safe because they know the compiler will tell them when something is incorrect.
As for the compiler abstracting and inferring what the programmer meant to do, that'd be very interesting. I'd love to see a prototype of that. I don't know if, I don't think the Rust language will move to that anytime soon, but if someone can make it work well and make a good case for it, and the rest of the community agrees with it, or at least the rest of the core team,
I could see us trying that. So if anyone's interested, if anyone wants to build a prototype, please do. I would love to see it. Next question, Twitch. Czechofeeb, I'm not sure if I said that right, but said, has there been any research into whether the borrow checker has produced measurably more reliable code?
The answer to that is I believe there has been formal research into it, and I don't know if it counts as formal research, but Microsoft wrote an article, I think Google's written articles too, about why they are rewriting their C++ code in Rust. A lot of times I see in the Rust channels
and in Slacks that I'm in that people ask, well, couldn't what Rust does just be done by C++ programmers who are committed to good standards and writing safe code and really experienced? And the answer to that is the thing about programmers, as you all know, is we're all human. We all make mistakes. I tell people about the time
I wrote some not optimized Ruby code and I ended up DDoSing myself because the database queries it was trying to do weren't optimized. So yes, I believe there has been some research. The best response that I have is we have
big tech companies who have been using C++ for a very long time who are seeing the value of Rust. All right, Jeff from YouTube asks, how does things change when you add lifetimes? And the answer to that is lifetimes are, even though I've been doing Rust for a few years,
and I'm assuming you mean lifetimes put within the arguments of the function, specifically noted lifetimes. And that, I mean, it would, if you denote that the lifetime of X had to be as long as the lifetime of Y, there might be a way to do that with specifying lifetimes.
And that would mean that X would not be uninitialized, I believe, when you try to use Y. So that is another potential way to solve the problem we saw in our code, would be to add a specific lifetime into it. Next question comes from YouTube, Bartello Mie, I sincerely apologize
if I'm butchering the names here. How much would borrow checker and borrowing be affected by chalk and Polonius in the near or not so near future? That is a great question. And I think that's something that's still being figured out. When I started putting this talk together, I worked with Nico Matsoukis at Mozilla,
who's on the Rust core team. And I told him, I'm putting together a talk on the borrow checker. And he said, oh, let me send you your talk, my talk about Polonius. So Polonius would change things, chalk would potentially, I'm not as familiar with chalk as I am with Polonius, because Polonius at least takes a different approach
to tracking borrows, lens, et cetera. So I will tweet out a link to Nico's slide deck, which will go into more detail about Polonius. Next question from Chekhoviv, do people working with borrow checker find this has changed how they think about the systems they build? Any anecdotal thoughts on this as someone
who's been using it for a while longer than the rest of us? Yes, so I did take computer science courses in college, but I was not a computer science major. So I learned most of my programming from doing it.
And I started with web programming. And with web programming, yes, memory is a consideration, but when you're not doing backend system programming, you don't consider it as much. Now that said, before I was at Mozilla, I was at Chef Software, as you can see in the photo, that's accompanying my details on the slide. And that was the first time I was dealing
with backend systems code. And I ran into a lot of trouble trying to do it in languages like C++ or something. And part of that was inexperience. But when I found Rust, it forced me to think through ahead of time of how my program would interact with memory and how the scopes of my variables and such would work through.
And it's very much, I think of Rust as, someone has said this, I don't remember who, is compiler-driven development. So I write some code, I try to build it, and I see what the compiler tells me, and then I try to correct my code, and I go back and forth and iterate on that. So yes, it has made me think a lot about the systems that I build.
Next question is Michael Ward. Which of the upcoming changes to Rust are you and or Mozilla most excited to see? I can tell you one I'm working on personally, which is adding the ability to use a stream with async, or adding the stream function of async code into the standard library.
There are code, there are external crates, like Futures RS that allow you to use a stream. So you can kind of, it's like an iterator, but for async code. But we want to move that into the standard library. So async is an area that has a lot of my focus right now, and it works well the way it is, but I can't wait to innovate on that.
I'm working on drafting an RFC now, regarding moving the stream trait into the standard library. So async streams, I would say, is what I am personally most excited about right now. Michael Lazowski asks, how do you rate the Rust community comparing to Ruby? Well, most of my experience with the Ruby community
was five or six years ago, and there's a lot of people who migrated from the Ruby community to Rust, like Steve Klopnik, Sean Griffin, et cetera. So it feels like a lot of the community leaders are the same. I would say what I love,
Ruby community is very good, but I love the most about the Rust community is that we are a community that considers empathy and personal maturity to be as important as technical excellence. And Ruby community has some of that, but other technical communities I've been into, that is definitely not the case.
And it is wonderful because it helped me learn Rust and to see how other people learn Rust, how they grow and the power of the things they create, and a lot of that is fostered by the Rust community. So I rate the Rust community very, very high. Let's see here. Next question. Ethan Braley, how much do extra checks Rust does
affect compilation performance? I'm not sure, actually. So let me get back to you on that one. ECS means entity component system. Ah, answer that as I don't know. I'm not as familiar with those systems, I think, but I'm very curious.
Michael Ward on YouTube asks, are there any known borrow check improvements that will vastly improve ergonomics like non-lexical lifetimes did? I am sure there are some in the works. I don't know of any specific ones off the top of my head right now. S. Burnham on Twitch asks,
since Rust doesn't have a RPL, do you find new Rust stations often use the compiler as a testing mechanism, i.e. attempting to compile, fix, compile again? Is there a better way to experiment in Rust? Well, I can tell you when I was in your station, that is exactly what I did. I would write a small piece of code,
see what the compiler did with it, compiler error it out, I would change it, and I would make the compiler be able to use that small piece of code before moving on to the next one. That, honestly, for me, has been the best way to learn. The best thing about the Rust compiler, and someone will be doing a talk about this at RustConf in August,
is that the compiler itself is a tutor. You know, it's an automatic tutor, and the error messages are very helpful, because again, they not only tell you what was wrong, they tell you how to fix it. So I would say as a new Rust station, yeah, go ahead and compile, fix, compile again. For me, that was the best way I could learn. And I know it's frustrating.
I promise it gets better as you come to understand why Rust does the things that it does. Guilherme Souza on YouTube says, when my code starts to be inundated with explicit lifetimes I usually stop and rethink my types and logic. I take it as a hint from the language
that my code is bad. Do you share this experience? I would say I do. When I'm putting in a ton of explicit lifetimes, I kind of feel like I'm flailing against the borrow checker rather than really using it to understand why it does what it does. So yes, a lot of explicit lifetimes
in a small amount of code, that tells me there's probably a different way that I can structure it. And as you say, stop and rethink your types and logic. What I would also suggest doing is pause, obviously read the compiler error messages. And if you're trying to do a lot of explicit lifetimes to correct that error message,
Google the error message itself. There's a lot of stuff on Stack Overflow. I believe it's a Rust community member, Burnt Sushi. I love that name. Has posted so many really helpful answers on it. And the best thing about Burnt Sushi's answers is they very clearly understand how the Rust compiler works
and they give answers along those lines. So that's the other thing I would do is, yeah, it's a sign that something, you're making your code more complicated than it needs to be. And use the compiler for help with that and also use the web resources. Let's see here. Jeff Barsezki says,
will your async stream support back pressure? That is something that we are talking about right now. I'm still working on the RFC in conjunction with the async working group. So as the RFC is now, remember this is still in early stages, it has not been formally proposed yet. I don't think we'll support it
in the initial implementation, but it is possible that could change before we open the RFC and even after the RFC is open based on the response of the community to it. So stay tuned. We are definitely talking about that. And let's see here. Mike Bursezki says, have you read Computer Systems, The Programmer's Perspective?
If not, I would think it would be interesting for you. I have not read that and that sounds fantastic. So I will give that a read and if anyone else wants to form a book club or something over Twitter or over Zoom to read it, let me know. All right, last question we have right now. Voztek Polak says,
what do you think about Verona from Microsoft? I'm not familiar with Verona from Microsoft. So I will have to check that out and get back to you. Any other questions before we close? All right, so it looks like that's all the questions. So I am going to, let's see here,
pick the most, pick a question. I picked the most interesting question. These are all interesting. This is hard to get that code for a man book. Let's see here.
I'm going to go with Guli Haram Souza. Let me put that name, so I'm probably butchering it.
I apologize profusely in my chat with the organizers of Rusty Days. All right, well enjoy your code from Manning and thank you everyone so much for tuning into my presentation
and I am hoping to eventually see you all in person again.