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

Bending the Curve

00:00

Formal Metadata

Title
Bending the Curve
Subtitle
How Rust Helped Us Write Better Ruby
Title of Series
Part Number
47
Number of Parts
94
Author
License
CC Attribution - ShareAlike 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 and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this
Identifiers
Publisher
Release Date
Language

Content Metadata

Subject Area
Genre
Abstract
Ruby is a productive language that developers love. When Ruby isn't fast enough, they often fall back to writing C extensions. But C extensions are scary for a number of reasons; it's easy to leak memory, or segfault the entire process. When we started to look at writing parts of the Skylight agent using native code, Rust was still pretty new, but its promise of low-level control with high-level safety intrigued us. We'll cover the reasons we went with Rust, how we structured our code, and how you can do it too. If you're looking to expand your horizons, Rust may be the language for you.
BitMetropolitan area networkComputer animationLecture/Conference
Message passingProduct (business)Multiplication signComputer virusLecture/ConferenceComputer animation
ResultantBit rateUniverse (mathematics)TheorySet (mathematics)Hand fanMultiplication signBlogReal-time operating systemProduct (business)InformationElectronic mailing listHistogramCASE <Informatik>2 (number)Bimodal distributionOpen sourceDependent and independent variablesResponse time (technology)NumberAverageTwitterInheritance (object-oriented programming)MeasurementFundamental theorem of algebraCartesian coordinate systemRevision controlComputer animationLecture/Conference
Cartesian coordinate systemRight angleTrailParameter (computer programming)Domain-specific languageMobile appSemantics (computer science)Programmer (hardware)SoftwareRevision controlGame controllerComputer animationLecture/Conference
Arithmetic meanOrder of magnitudeInformationRight angleLikelihood functionParameter (computer programming)Endliche ModelltheorieKnowledge baseMachine codeWritingMultiplication signTerm (mathematics)MedianTwitterPairwise comparisonAdditionEmailCodeDecision theoryMobile appSemiconductor memoryLevel (video gaming)Computer animation
Field (computer science)Mobile appFormal languageRight anglePhysical lawState of matterSocial classAreaQuicksortVarianceNumberSummierbarkeitMultiplication signReading (process)TheoryArithmetic meanVideo gameUniverse (mathematics)Variable (mathematics)Parameter (computer programming)MereologyEndliche ModelltheorieLikelihood functionSign (mathematics)Physical systemService (economics)Cartesian coordinate systemProcess (computing)Term (mathematics)CodeWritingBuildingLinker (computing)Server (computing)FreewareRegular expressionCASE <Informatik>Programming languageSystem programmingAssembly languageClient (computing)AbstractionAdditionRun time (program lifecycle phase)Graph (mathematics)Key (cryptography)GodComputer programmingLevel (video gaming)DebuggerEmbedded systemFrequencyNP-hardSemiconductor memoryBitAxiom of choiceError messageComputer animationLecture/Conference
Letterpress printingFunctional (mathematics)SpeicherbereinigungComputer programmingBitMemory managementRight anglePoint (geometry)CodeLine (geometry)Link (knot theory)String (computer science)Vector spaceData structureFormal languageSocial classType theoryProfil (magazine)QuicksortParallel portLevel (video gaming)Computer configurationAnalogyTrailObject (grammar)Physical systemBuildingWeb browserSemiconductor memoryDefault (computer science)Goodness of fitFreewareNumberSoftware bugFile systemGeneralized linear modelLibrary (computing)Multiplication signArrow of timeCompilerEvent horizonDifferent (Kate Ryan album)SummierbarkeitProjective planeLattice (order)ResultantWord1 (number)TheoryContent (media)Physical lawState of matterReading (process)VarianceUtility softwareRule of inferenceProduct (business)Video gameSound effectParameter (computer programming)
Programming languagePoint (geometry)Letterpress printingMathematicsBookmark (World Wide Web)Library (computing)BitPhysical systemOrder (biology)Functional (mathematics)System callCompilerGreatest elementRule of inferenceMultiplication signSymbol tableComputer programmingReal numberOverhead (computing)Link (knot theory)Object (grammar)Run time (program lifecycle phase)Web pageElectronic signaturePointer (computer programming)Level (video gaming)SpeicherbereinigungRead-only memoryAbstractionNumberMereologyEndliche ModelltheorieSummierbarkeitForm (programming)Kernel (computing)Source codeCellular automatonTheoryRight angleElement (mathematics)WordFood energyReading (process)Position operatorArithmetic meanComputer animationLecture/Conference
Slide ruleQuicksortWritingGoodness of fitAlgebraic closureFunctional (mathematics)Heat transferSeries (mathematics)Sheaf (mathematics)Regular expressionRule of inferenceInterior (topology)MereologyMultiplication signBitError messageHookingRead-only memoryPerspective (visual)AliasingThread (computing)Arithmetic meanLibrary (computing)SynchronizationOperator (mathematics)Demo (music)Formal languageSubject indexingCategory of beingInformation overloadRun time (program lifecycle phase)State of matterMobile appData structureType theoryStrategy gameRootPhysical lawInsertion lossWordEndliche ModelltheorieOrder (biology)Medical imagingResultantTheoryRight angleLevel (video gaming)
CodeAnalytic setMemory managementFunctional (mathematics)Semantics (computer science)Social classLibrary (computing)Descriptive statisticsTouchscreenPerspective (visual)Traffic reportingConfiguration spaceMereologyInformationParsingNumbering schemeFiber bundleMappingHash functionTerm (mathematics)Data structureMultiplication signProgramming languageBuffer solutionBuildingInjektivitätDegrees of freedom (physics and chemistry)AreaNumberDisk read-and-write headRight angleTheoryComputer animation
Projective planeWordArithmetic meanBranch (computer science)Traffic reportingConfiguration spaceComputer animation
Library (computing)Revision controlDefault (computer science)Point (geometry)Video gameLetterpress printingCodeReal numberFile formatTraffic reportingRight angleForm (programming)Computer animation
Transcript: English(auto-generated)
So I wanted to talk to you guys a little bit today just about Skylight, although we
really did shine out of this talk and turned it into a rough talk. Since a little bit about Tilda, we started the company back in 2012, and we're really proud and proud. We work on a product called Skylight, and DHH's message yesterday about being
a small team, being a prepper, and needing to put tools into your backpack that are going to let you compete with the big guys actually really resonated with us, because we only have five engineers on staff, and we're building products that compete with companies that are IPO-ing, yes, like very much, much bigger competition. And fundamentally what that means is that we need higher leverage tools.
So when we built Skylight, the thing that we really wanted to solve, the thing that really put a fire under us was that all of the tools that we were using to measure the performance of our applications were reporting averages. And as DHH said, this is a visit that DHH loved to make up for my Twitter feed yesterday.
So DHH wrote this really great blog post in 2007, it's like ancient times, where he said, our average response time for Basecamp right now is 87 milliseconds, which sounds fantastic. And it leads you to believe that all is well, and we wouldn't need to spend any more time optimizing performance. But that's wrong. Average number is completely skewed by tons of super fast responses to feed requests
and other cash requests. If you have a thousand requests that return in five milliseconds, then you can have 200 requests taking two seconds and still get a respectable 170 milliseconds average. Useless. And I actually think it's worse than useless. I think it's actively misleading. So instead what we need are histograms. We're like, great, we have received this wisdom from DHH, so let's go build a product
around it. And so that's what we did. We built a product where instead of giving the average, you can see a histogram. And this is super important because looking at a histogram makes something just so obvious, like you can see cash hits and cash misses. We call this a bimodal distribution. You can also see the 95th percentile, which gives you a much better sense
of what the average worst case that your customer is experiencing. And we did that all using very high leverage open source tools. So our back end, in particular, is built on a product called PatuStorm. I don't know if you can read below. It says distributed, resilient, real-time. I wish we had known when we picked it that that was a kick-to list.
Carl had a rope to kick. Anyway, you may have seen our talk, which when I grabbed this slide, I apparently grabbed all the animations that come with it. But we gave a talk on our art.
So this year we want to talk about a different high-level tool, which is Rust. So our timeline for Skylight, I think it really motivates us, is how do we give our users, if we're just dumping a bunch of data on them, how do we give them real answers? So we sit through all the data, and they don't have to. And as it turns out, doing this answers-not-data approach requires a lot of data.
We have to collect so much information. And when we wrote the first version of our agent, which runs inside of your application, we wrote it in Ruby, which is really great if you know Ruby. But we quickly realized that if we were going to collect the amount of information that we needed to build the product that we wanted to build, Ruby just had some fundamental performance problems that weren't going to be acceptable.
Well, we really needed something, but we really needed a tool that was going to give us low-level control, like a C or a C++. But we were afraid, because Yvette and I were afraid of mediocre programmers. And we know that we're getting our software to run inside of your application. And the idea of us writing something with a segfault that would crash your apps is just totally terrifying for our application.
So we needed something that would give us the high-level safety guarantees of Ruby, but with this low-level control and this low-level access. And Rust was the answer. Rust came out. It was still 3.1.0. But we decided to make a big bet on Ruby. Well, that's when we went away that year. We've got semantic version of what's over here.
So we decided to rewrite our agent in Ruby in Rust from Ruby. And we called that our featherweight agent. And this, I have to say, has been one of the best decisions that we've made. It was very nerve-wracking to bet on this pre-1.0 program, which is low-level, making all these quite intense promises.
But it's been really great, because Skylight, the agent now, in addition to collecting so much more information than our competitors, just sips resources in comparison. Because we're writing this code that's essentially operating like C, in addition to better performance and being so lightweight in terms of resources, it also lets us build features that we would never be able to build
if we didn't have that low-level access. Being able to write something in native code lets us go a level deeper into MRI itself. So for example, this year we launched a new feature that actually shows allocations. So you can actually see how much memory is being allocated in your Rails app in production, which is huge when you're trying to track down memory issues. And we can do that at a really granular level as we drill down.
Yeah, like to the minute. And this is also letting us ship a new feature that we're announcing today called Trends. So Trends is a new weekly email that you'll be able to subscribe to by launching it this week. You'll get a weekly email showing you not just negative percentile, but also your medians. And this is a really great way to protect changes over time.
So the thing that I would ask you to do, and I'm just going to get into the meat of the whole matter, but the thing that I would ask you to do is, with a language that offers you low-level control, but high-level safety and expressiveness, if you're a small, professional team, what can you do? What new opportunities and new features in your apps does that involve? So without further ado, here is Kuda with his Rust field.
Hey! So those are the value dollars to the Rust. Oh my God.
We came up dollars to the Rust dollars. There's like a key in your heart.
Yeah. Oh yeah.
Let's do this. All right, I can hold it for you. Okay, so today I'm going to talk about Rust. This is the Rust logo.
I'm going to plot a program that I do just on this expressiveness and speed graph. And I actually kind of find this to be a somewhat pointless exercise, and I use JavaScript as sort of an example of this,
which is that when I started writing JavaScript at 05, JavaScript was pretty bad on the expressiveness axis and even worse on the speed axis. There was one thing that people underestimated about JavaScript, and what they underestimated was the fact that JavaScript was everywhere. So JavaScript was, on the client side, basically all you got to write,
so you wrote a lot of JavaScript on the client side. On the server side it was very tiny, so maybe the red one there is Rails or something. Yellow is PHP. So JavaScript was everywhere on the client side. It wasn't very popular on the server side, so people kind of dismissed it, but they sort of missed the ubiquity of JavaScript on the client side.
And what happens when there's an advantage like ubiquity is that people go and they say, okay, well, JavaScript is low on speed and expressiveness scale. Well, let's write V8. We write V8, boom, now it's way faster. Oh, well, people don't think it's expressive enough? No problem. We'll go and we'll make it more expressive. And so this idea of programming languages as living in fixed places on an xy axis of speed expressiveness
is kind of not the way I think about programming languages. I think about programming languages and tools in terms of enabling what they enable. So obviously everyone is here because of Rails, and for a lot of people, Rails enabled you as a person who may not have necessarily,
at least for me, I'll speak for myself, someone who didn't really know what I was doing when I started. It enabled me to build pretty ambitious stuff that I wasn't able to build before. We were my emperor for a similar reason. And the thing that was great about Node, I think, is that Node allowed a bunch of people who only knew how to write on the front end, but like 99% of people write on the front end using JavaScript to let them write backend stuff. And a lot of people like to say,
oh, do you really want those jQuery jockeys right on your server side? And that's like a pretty good joke that you can tell. But the industry is actually controlled largely by a lot of people who you may not have necessarily wanted building that thing, but ended up getting pressed into service. I think, like for me, that's definitely my story. I got pressed into service and did a lot of things I wasn't ready to do.
And so for me, looking for a technology that enabled me to do my job is so important. So what Rust is really good at, Rust is not ubiquitous by any stretch, but Rust actually enables people like you and me, Tom, to be systems programmers. And usually when I say that, I say, oh, yes, Rust will enable you to be a systems programmer. I get a look like this.
Like, I'm not a systems programmer. What are you talking about? Somebody else can be a systems programmer. Although, as Tom pointed out, sometimes you don't have a choice. But then usually when I get past it and say, no, it's good. You may want me to end up in a situation like that. People say, oh, my god, that sounds really scary. Systems programming sounds super hard.
And obviously, I played with C a few times, and that was crazy. And also I said all of it all the time, so it was super dangerous. So then you're like, no, Rust is great. It lets you, you know, it's easier, and it has some of the high level affordances. It's also not as dangerous as you see. It's like safe, actually.
And then people say, oh, so systems programming seems cool, but what is that? What is systems programming? So what does that mean? What is the definition? So there's actually a lot of definitions, but I'll just give you my personal take on it. So there's a few different things that systems programming means to me. One of them is you get to write code without a GC. And there's a lot of reasons why you might care.
Like if you're a high frequency trader, you care about GC pauses. If you're asked, you don't want to embed GC language inside of a GC language. So there's a lot of reasons you might want to program without a GC. Also, programming directly against the metal. I don't mean like the way you know what people say, I'm programming on the metal. I mean literally like writing against the lowest levels of abstraction that you have access to.
And doing that without additional costs, additional abstraction costs. So you should have to write extra layers of, have extra layers of cost just to program, talk to the kernel, right? Also, in terms of a run time, most programming languages have a pretty heavyweight run time or involved run time. Usually when people say systems programming,
what they mean is that there's like either no run time at all, or the run time is very lightweight and you just pay as you go. As you need it, you use it. Also, this is like a thing that came out of C++, but I think it's important. You should be able to write abstractions and those abstractions should not cost. You should be able to write functions, you should be able to make structures, you should be able to organize your code in a good way and not have those abstractions at additional cost as you go.
And finally, I think the thing that systems programming most is, is everybody's written code, like you write a new code, and normally the good answer to Rust is like, well, it doesn't end up mattering. Who cares? It's like I'll just write my Rails app and it'll be fast enough and it doesn't end up mattering. But occasionally you end up writing code where it does matter.
This happens basically every app where there's a performance critical area and it just doesn't end up being true anymore or that it doesn't matter and the amount of time that it takes you to get to a reasonable performance is actually more writing in a language like Ruby than writing in a language that's optimized for good performance. So that sort of, for me, systems programming is when it turns out that the story that we tell ourselves
about performance is not mattering, for the case where that doesn't end up being true. That doesn't mean a bunch of stuff, though. It doesn't mean malloc and free. It doesn't mean you have to write assembly languages. It doesn't mean you're writing code that only talks to Unix. It doesn't mean you're writing handcrafted makefiles. It doesn't mean you have to care much about what the linker's doing. And it doesn't mean that your entire application is written in a systems programming language.
It may mean that you're writing using a little bit of systems language for errors where it matters, but mostly your app is still written in something like Ruby. So a good example of this, like Tom said, is Skyline. So Skyline basically used Rust because we needed to embed a programming language and put out a programming language and two GCs is not good. We needed high performance and we needed low memory.
So for all those reasons, we ended up going with Rust. Firefox, sorry, Mozilla, actually was using building Rust for kind of a different reason, which is that they're building a new browser engine called Servo, and for them, they needed to build something that was fast, safe, and parallel. So they really wanted to be exploring different parallel options. And interestingly, the Servo team is like seven people,
so you want to talk about a proper team, or five people building Skyline, that's seven people building a browser. And they're using Rust because it allows them to explore ways of writing low level code so that when you normally write C++, you normally write Rust and C++, it lets them explore that kind of performance profile about being able to do parallel stuff.
So that ends up being pretty important. So those are all reasons why you might use Rust. I'll talk a little more at the end about Rust and Ruby, but before I talk about Rust and Ruby, I want to talk a little bit about how it works. So I'm going to talk about Rust, but before I talk about Rust, I want to talk about sort of how garbage collector works. So here is some Ruby code.
You can see I have a very simple structure. I have a class that has a point that has an X and Y. It's a Ruby audience, so you should know what this code does. It's very simple. And I have a link function. The link function basically makes a new point, a couple new points, makes a line, and calculates the line. So what happens is I go into this link function. I make this point. The point needs to be created. It goes into the heap somewhere.
I make another point. No problem. It goes into the heap somewhere. I make a line. It goes into the heap. Of course, that line points at the two points, right? I go into the link, and then at some point in the future, so that those objects stay there chilling out. At some point in the future, something stops the world,
and it goes in and looks into what's going on, discovers that nobody else sees those objects anymore, and it cleans them up. And that's actually a pretty good story for safety, because basically by definition, a garbage collector ensures that you can't use an object. Sorry, the object is cleaned up not only after it's no longer being used. So the whole concept of a use-after-free bug
is impossible by definition. You have a garbage collector. Use-after-free is impossible. Free only happens after use is done. That's the point of a garbage collector. But that means that you need a garbage collector. And there's another way of dealing with memory that is how the methodology for dealing with memory in C or C++,
and that methodology is called ownership. And the idea of ownership is basically that whoever allocated the object is responsible for deallocating it. So if I make the object, I have to deallocate it. And the reason why this is good, obviously, is that it means you don't need a garbage collector. The reason that it's bad is that now you have to keep track of all that. You have to make sure that you do the right thing.
And if you do the wrong thing and I make the object and Tom tries to free it, basically game over. Now I try to use it later and I get a second hold of it. So the idea of ownership is the methodology for doing systems programming, but historically, actually doing it was very hard. So let me try to give you a simple analogy. So let's say there's me and there's Carl in thinking man pose
and there's a bookshop. I basically go to the bookshop. The bookshop says, okay, here, you can have a book. It's my book. Now it's my book. I bought it. So now I bought the book from the bookshop. Now I'm allowed to destroy it or burn it. Not the best analogy.
So a good use of the blame animation. So because I own the book, I am allowed to destroy the book. Since it's my responsibility, I don't have to ask anybody else for permission. Now, once I own the book, once it's my book, I can also give it to Carl. And now that I've given it to Carl, Carl is allowed to dispose of the book.
But I can no longer dispose of it. I've given it to Carl. It's now Carl's book. So one way that you might think about this is that ownership, both in the real world and in the programming world, is basically talking about the right to destroy something. So let me show you, basically, the equivalent code. Hopefully people can read this code. Probably not, but sorry.
N24 by 768. So you can see on the top that there's a book. It's simple if you're familiar with any type languages. It's a book. It has a title. It's a string. It has a bunch of chapters. It's a vector of strings. Then we have a function called name. And I'll just walk through what happens here. So what happens here is the first thing is we say, give me a book.
Read the book out of a file system. And then now the book exists, and it's owned by this function. So ownership in Rust is usually rooted in a function. So the function that's in it will be called home to the book. Now I go and I print the book, and that's great. The book gets printed to the screen, and now I leave the function name. So now the function name, as an owner, doesn't exist anymore. And because the function name had the right to destroy,
basically Rust will automatically go and clean it up. So you didn't have to do any work. You should know a lot of manual memory management here. What happened was that because the function name owned the book and the function name doesn't exist anymore, the book gets destroyed. And I think that's a pretty good starting point if you want to try to do automatic memory management. But of course, if only the function that ever created something
is allowed to use it, that doesn't make for very interesting programs. So let's look at a little bit more involved, a little more involved example. And this is an example where the name function makes the book, and then it calls the print book function to print the book. And so what happens here is that we make the book as before.
Now the book is owned by the main function. Now we call the print book function. And the thing to note here is that the print book function just takes the book, capital B book, no extra signals or anything like that. So because of the fact that it takes the book by default in Rust, that means that you're going to transfer ownership. And now what that means is that the main function no longer owns the book. Instead, the print book function owns the book.
It does the print line as before. And when it leaves, now it is responsible for disposing it. The function and the book gets destroyed, as you might expect. Now you might be thinking, okay, so that's cool, but I've written a lot on Ruby, and there's nothing really stopping me and Ruby from after the following print book going and using the book again.
So if you follow this methodology over here, what's going to happen is that the main function is going to try to access a book that was destroyed. And we said before that we kind of need a garbage collector to deal with that kind of problem. So how does Rust deal with that problem? So let's go back and let's look at this example where we do the same thing as we did before, but after transferring the ownership to the print book function,
we try to use it here. We try to print the number of chapters. So as before, we make the book. We put it to be its own by this function. The main function, we print book. We transfer it as before. The book got destroyed. Now we try to go back and print the book. Well, actually, no. What's going to happen is we didn't actually get this far. The compiler actually discovered that we did this ahead of time
and says, you can't do that. And what it says is you use the moved value. The value is transferred. The ownership was transferred into print book, and you're trying to use it. And you get a little note that says, note, the book was moved here. So in the real output, there would be a little arrow pointing at the print book function. So you say, OK, I see that I transferred the ownership. I'm not allowed to use it anymore.
And sort of like I said before, I think this is fine for simple examples, but you can sort of intuit that this is not the whole story. This is not, you can't really write interesting programs and every single time you want to do something with the value, you have to give ownership to the function that wants to do something with it. That's not very intuitive. And to deal with that in the real world,
in the real world of transferring ownership, we deal with that by saying you're allowed to lend something. So I go to the library. The library doesn't have to give me ownership of a book. It can lend the ownership to me. And then I'm giving a promise to the library that I will return at a certain point. So the way we deal with the promise of ownership in the real world is by borrowing. And that's also the way that we deal with the promise of ownership in Rust.
So let me give you an example. So I have this book that I got from the library. And I say to Carl, hey, I will give you this book, but you need to return it to me by 5 p.m. on Friday. And as long as Carl returns it by 5 p.m. on Friday, everything is great. Now, the problem is that in the real world, there's nothing enforcing that rule. So I can say to Carl, hey, I'll give you the book back at 5 p.m.,
and then if he doesn't give you the book back by 5 p.m., it's basically now I'm in trouble. If somebody else wants it from me, then I'm going to be in trouble. But of course, the programming language, we can do better than that. So let's look at another example with borrowing. And you're going to notice that this is basically the same program that we wrote before, except this time when we call print book, we use the ampersand symbol before the book,
and we put an ampersand symbol before the capital B book on the bottom. And the only thing that we're saying here that's different from before is that instead of transferring ownership from the main function to the print book function, we are lending the book to the print book function, and it's required to give it back to me at the end, and that happens automatically. So let's look at what happens. So I start off with, as before, the book is owned by the main function.
But now, because I call print book with an ampersand, it gets lent to the print book function. The print book function prints the thing to the terminal, and then when it returns, it gives the book back to the main function. And now, because of the fact that it gave the book back to the main function,
when I go to print the line, the main function still owns it. It can happily do that, and everything goes along as expected. So that works pretty nicely. But there's one additional step that you need to understand how the whole system works, which is the fact that you can lend something that you borrowed to somebody else. We call that some leasing. The idea behind some leasing is that the first person who borrowed isn't the last person who can borrow.
So in the real world, you can imagine I go to the library, the library lends me a book, and the library says, hey, I need you to return this book by 5 p.m. on Friday. Great. So I remember that I need to return the book by 5 p.m. on Friday, and then Carl says to me, hey, I want the book. I want to borrow the book. So I can give Carl the book, but I can say to Carl, hey,
I need you to give me this book back by 30 p.m. on Friday, because I know I can give it back to the library by 5. So you can borrow the book, and you need to return it back to me. Then Carl returns the book back to me. I return it to the library. Everything's great. Again, in the real world, it starts to get complicated. In the real world, when you start dealing with some leasing, you start dealing with complicated subleasing arrangements and restrictions,
and that's mostly because in the real world, people are very bad at honoring the leases that we ask them to honor. But in the programming world, the compiler can enforce it for us. So let's look at another example with subleasing that's a little more involved. I think the important thing here is that we're able to write
arbitrarily involved abstractions just like you would any programming language as long as you follow these basic ownership rules. So here's the function called mania. We're going to get the book. The book is going to be owned by the main function, and then we're going to call printBook. Again, we're going to lend the book to the printBook function. So it gets the book. It already has barred it,
but it doesn't really know how to print it, so it's going to delegate that to the printTitle function, the printChapters function. So it's going to call printTitle. That's going to lend the book another level down. It's going to printLine, and then it's going to return back up. Then the printBook function is going to go another level. It's going to print the chapters. That's going to go get me the link to the chapters.
It's going to print the line. It's going to return. The printBook function is going to return, and then as soon as the whole main function is done, then it can destroy it. So the really cool thing about all this is that in all these cases, I think the thing that we're doing is pretty normal. It's basically, if you think about it, in most programming, when you call a function,
you're basically lending. You're not expecting the thing that you're calling to take ownership and try to do something with it later. But we have to pay the garbage collection overhead in all programs, all the time, in all cases, because somebody might want to do that. And in Rust, the way it ends up working is that you start off with this assumption of ownership transfer,
and you can lend things as much as you want. That's basically a thing you're free to do. And once you've done that, now we can completely eliminate the cost of garbage collection across the entire system. So there's one last bit here, which is immutability. So, so far we've been talking about read-only things,
and of course you can imagine that if you're only dealing with read-only things, you can lend as much as you want, and then you can look at it, and of course they can't mutate it, so it's totally fine. You can have 50 people looking at something at the same time, and it's fine. But what if I want to allow mutations? Mutations add a little bit of a wrinkle to it. So let's say I go to the library,
and the library says, here, you can have this book, and you can feel free to change it. Let's say you can move the bookmark around, or fold the corners, or something like that. And then Carl says, hey, I want to borrow the book. I might want to say, Carl, you can borrow the book, but I don't want you changing it. And so I can say, hey, return the book on Friday, but don't change it, and he returns the book, I return the book,
everything is great. Again, he returns, this is getting even more complicated, it's very difficult to implement on the rules. In Rust, it's, again, a pretty straightforward change to what we've been doing all along. So the first thing to note is that so far, by default, everything that you do in Rust is quote, unquote, immutable. But when we say immutable, we don't mean that the object is frozen,
there's no runtime checks, nothing like that. It's just, by default, if you have a reference to some kind of object, you're not allowed to mutate it. The compiler will prevent you from changing it. Now let's say I want to add a new feature here, which is that I have the ability to add a bookmark to the book to say where I'm off to. So now, in order for me to be allowed to mutate that bookmark, I need to start off by saying,
give me a mutable book. Don't give me a book, or read only a book, or give me a mutable book. And that's fine. And now when I call the print book function, the print book function, before I just called it with the ampersand, which means I'm lending it to you, this time I'm calling it with the ampersand mute, which means I'm lending it to you, and you're allowed to modify it.
So I call print book with the mute, I send it over. Now, the thing to note here is that when print book calls print title, print book says, I am allowed to mutate it, but printing the title shouldn't be repeated, so I'm not going to let you mutate it. So print title, which is before, it borrows it, does the thing, call print chapters,
does the same things as before, and then at the end of my function, I'm basically going to mutate the bookmark. So this is a kind of hokey example because print book shouldn't be changing the bookmark, but the key point here is that if you look at the main function, it's clear to me that the print book function might be mutating something. So just by looking at that signature, I can say, okay,
I can see that mutation might be happening, and all the other signatures on this page don't take a mutable, borrow something, so we know that they can't mutate anything. And that ends up being pretty important. That ends up helping a lot. So now the rules, I'll just recap by saying there's basically two rules of borrowing.
You can have as many outstanding read-only borrows as you want, so you can, in the real world again, you can't give the same quote to many people at the same time, but on deep programming, you can have as many pointers to the same object as you want, so you can have as many outstanding read-only borrows as you want, but in contrast, a mutable borrow is unique. If you have a mutable borrow that's outstanding, no other borrow is mutable
or read-only can occur at the same time. And again, it's very important to note that this is not enforced at run time. It's not like every single time you try to borrow something, it checks to see if anybody else has anything outstanding. There's no mutable locks at run time. It's all enforced by the compiler, but this gives us some nice properties.
So I want to show you some examples. So I talked about what's a LOD or not a LOD. I want to show you some examples of what that might look like. So we start off here. We have this function called same, and the same function takes two books. It borrows two books, right? And it just says, is the title the same? Is the first title the same? Is the second title or the chapter the same as each other?
And in Rust, double equals does what it does in Ruby. It does deep, deep comparison, right? And we call same with hook one and hook two. Obviously, that's fine, right? I'm allowed to lend something to the same function and these are two totally different books, so that's totally fine. Now, what if I, instead of lending hook one and hook two,
what if I call same with hook one twice? So again, because this is an immutable borrow, because it's a read-only borrow, this is still fine, even though I'm making two copies of that book. So from the perspective of the same function, those are two copies of the same thing, or two different things. The fact that it's read-only means that's safe and we're allowed to do it. So that's still fine.
But now what if I make another function? Well, this function is going to be called copy, and it's going to take a book and a mutable book, and it's going to copy the read-only book's title into the mutable book's title. So here, what we did is we said, copy the title from hook one into mutable book two. So like I said before, that's still totally fine,
because in this situation, we have a book one, we have a book two, there's no aliasing going on, we're following the rules for borrowings, mutable borrows must be unique. That's totally fine. Now, what happens if I'm going to change it? And I say, we're going to only get one book, and then I'm going to say copy from book to the same book, mutable book.
Again, from the perspective of the copy function, it doesn't know that these are the same thing, right? But what it ends up asking is that the pilot looks at this and it says, you're trying to have a read-only borrow and a mutable borrow at the same time. That violates the rules of borrowing. So, error. Cannot borrow a book as a mutable because it is also borrowed as a mutable. And it gives you a note that says,
the previous borrowable book occurs here. Which is useful. And then it says the previous borrow ends here. And basically at the end of the day, borrowing is the secret sauce of Rust. Borrowing basically allows you to do very, very involved, complicated, recursive things that you might think
would be a garbage collector, but by following these very simple rules, you get done when you need to get done. I actually want to skip a little bit here because I want to get to the Ruby part. I'm going to skip over the closure stuff. But the TLDR on closures is that closures follow the same rules as regular, closures follow the same rules as regular ownership.
So if you're using a closure and the closure closes over a variable, it ends up having the same rules. So if you pass a closure to another function, and the function tries to call it multiple times, that would violate ownership rules and ends up giving you an error. You can learn more about that in the Rust book. So what about the currency?
So the interesting thing about the currency is that really the problem with the currency boils down to one thing, which is that shared beautiful state is the root of all evil. And there's basically two strategies that people use to try to deal with this problem. So one of the solutions is called channels. The idea behind channels is that you're not allowed to have two copies of the same thing at the same time. If I want to give a value to some other thread,
I have to pass it to a channel, and then you basically can't get it. Another strategy that's used is functional style, which is that you can't mutate. You can never mutate anything, and you never have shared beautiful state. Rust sort of does a combo. It says you can have shared state, or you can have mutable state, but you can't have shared state that is also mutable at the same time.
And if you think about that, that actually is the exact same idea as the original rules of borrowing that we had before, which is that you can have as many outside of the read-only bars as you want. That's shared state. Or you can have a single mutable borrow that's mutable state, but you can't have both of them at the same time. So you can have alias state, or you can mutate, or you can't have both at the same time.
And if you use more Rust, you'll see that the send trait and the sync trait are basically the ways that internally Rust enforces this. It's not anything special about threads or anything special about any particular data structures in Rust. There's just two traits that represent thread safety, essentially, and any library that's written, like Carl has a bunch of libraries
that deal with asynchronous IO currency, and they're able to implement these rules themselves. Just a couple simple examples here. So we have the spawn function. The spawn function takes a closure here. You can see that if we call the spawn function and we try to access a book from the outside,
it's going to tell us that there's an error and it's going to tell us that the book doesn't work long enough. And the reason for that is, actually, this kind of depends on the closure and stuff, but the basic idea here is that the closure can run at any time, right? So we call spawn with the thread. That closure can run at any time in the future, but we know from before
that the main function owns the book, right? So we can't let the closure run and use the book at any time in the future because we know that as soon as the main function gets exited, it's going to clear the book. So that's an error. It basically says, the book does not work long enough. This is a compile-time error, and we add the word, but we add move to it. That basically says, this closure's going to be moved out. You can feel free to move anything
from the outer scope inside of it, and then that becomes just a regular transfer of ownership into the closure. I'm going to skip the next section. So one thing I didn't talk about, or a series of things I didn't talk about, is sort of high-level productivity, and maybe you got a sense of that from the one slide that I showed in closures, but Rust has a lot of higher-level ideas
that are pretty familiar to higher-level programming, people like people coming from Ruby, Python and JavaScript, so there's things like OO, right? So you can have a type, and then you can implement it. There's things like traits, which I don't have a good example of, but the idea behind traits is sort of similar to Ruby mixins, or Ruby requirements,
which are like scope mixins. So you can write things like app support, or you can implement a method on the one value, or you can implement, you can say, like, one.base.go, or you can implement traits that are used like Go interfaces, right? So there's sort of this very flexible way of dealing with
a kind of mixin type of situation. There's also iterators, which are a way of doing something that looks very Ruby-ish in the sense that you might map, filter, reduce over things, but under the hood, it ends up being compiled and being effectively as fast as it believes that you would have written by hand, and that just sort of imagined all of the M stuff.
There's also enums, which is, if you're familiar with other languages where there's something with a variant, you can have enums, and you can also put methods on those enums, which is awesome. There's also overloaded operators, which is pretty great, you can do things that look interesting, so you can overload indexing operators, but also things like the plus operator. So all that's pretty cool.
I didn't talk about it because it's not like, these are all, like, cool things about Rust, but they're not part of the big story. The big story is that ownership lets you do really low-level things without the fear of segfaults that you have, and I wanted to show just a really quick demo before my time is up of what that might look like in Ruby. So let me, I should mirror my screen.
So I don't have much time, but basically, hello?
Make it bigger. I got this to me. So basically, I wrote a little rack handler. So this is a rack handler, you can see it's Ruby. The thing on top here is kind of the interesting part of this, which is I'm using a built-in thing in Ruby, which Aaron's talking about a lot, and I'm basically just saying, okay, I want to dynamically load
this RailsConf thing that I got from Rust, and I'm going to define three functions, an anchor function, a report function that takes analytics, and an analytics function that returns analytics. I made a little class here called analytics, which just wraps that, so you can see it initializes the calls, you have a five-layer, it's basically calling into Rust. So all these things
that are happening here are calling into Rust. And then I wrote a little rack handler here, which is just an analytics handler, and when you go to slash report, it calls report, gets us right back, and otherwise, it just increments with the request URI. If I go into the Rust code, you'll see that the Rust code is actually a pretty vanilla Rust code. I have a structure here
called analytics. It has three hash maps in it for host schemes and endpoints, and then when you call anchor, it basically goes, okay, increment, parse the host, make sure everything is okay, basically fail, then it's not a valid URI, and then increment each one of these three hash maps with the information pulled out, and then also increment the code.
So we have some Rust code that does what we wanted. Now, the cool thing here, the really interesting thing here, is that you see this, there's this nomencl thing, and then pub, x, r, and c. Other than this, it's pretty vanilla Rust. But by adding these descriptions here, what we're basically saying is,
from the perspective of any other program, when it does not Rust, treat this like C. So you can see here, for example, this interfunction takes an analytics and a buffer, and if I go back into the, if I go back into the config.ru, you'll see that that's, right, interfunction takes a C pointer, pull analytics, and a buffer, right? So the cool thing about Rust is that
even though it has all these high-level C guarantees that syntax isn't exactly like C, the underlying semantics that you tell Rust, like this should be usable from C, are exactly the same as C. So now let me just run, I should actually show you, so Rust has this thing, has a, sorry,
Rust has a package manager, so there's cargo.toml, which is the package manager description. You can see I have a RailsConf demo, I have some authors, then I have, I describe my library, I say it's a dialog, which is important for this demo, and then I say it has a dependency on the URL, and it has a dependency on RubyBridge, which is a library that I wrote for this demo, which is basically just the thing
that gives you the buffer injection. And then if I go cargo build, dash dash release, it actually does nothing, and the reason for that, if I run verbose, is that it sees that I already built that, so if I rm rf the target directory, and then run cargo build again, it kind of works like on the right, it's basically like, oh, I see you already downloaded those things,
so no problem, I'll not download them again, but I'm going to compile them one at a time. You can see that it's compiling the RubyBridge crate and the URL crate, which are basically the dependencies that I listed, and that's automatically, I didn't have to do anything other than say that it depended on it, and then I'll just point out in the Rust code, librs, you can see on the top there,
all I have to do is say, x term for URL, x term for RubyBridge, and that's all the code that you have to do to get cargo to build it, so if you've ever written like C or C++, that's like a billion times simpler to deal with dependencies, it's like writing in a modern program language. So now, now I'm just going to do
bundle exec, rack up, I'm actually inside of a Ubuntu VM, so I have to minus 0, 0, 0, 0. So then, let me open Chrome, I go to,
no. Okay, so you can see, it says success, I've implemented Ubuntu.dev, I can go to like, Google, like whatever,
URL. Ah, oh yes, so I flashed it on the screen, but it's very simple. You go back to the config.ru, you'll see it's, this was a benchmark. You can see that it basically doesn't do anything, it's a call, it basically says, if your report then returns 200 with the report that we get over the F5 from Rust,
otherwise, calling for function, we're going to say success. So, you can see, none of the actual work is done inside of Ruby, it's just calling into Rust through that little, little branch. So, I can say success, I can do like, Ubar, awesome, and then if I go to 42.92 slash report,
you can see that it'll give you a report, and then basically, it's too big. So this is basically the default debug version of that structure. So, actually if you go back and look at that structure, real quick, with RS, you'll see that I derived debug,
and derived debug basically says emit the code that is necessary to print the debugging version, and then if you look at the report, you'll see that the report is literally just saying format that debugging code, and then send it back over as a debug, like a debug debug, exactly like that. So, and this is basically just Ruby printing that same thing, I mean, I called you last time, so, you can also see that
it's definitely working, because you can see Fave Ico, the ICO, is incrementing as like, oh, this is definitely, definitely real life. So, I'm basically out of time here, but the key point here is not really anything about this specific example, but just to show that Rust, B, produces an ISO file, which you can load in Ruby,
Ruby has a built-in, built-in fiddle, Rust is pretty good at, so, without that much difficulty, you can take something that might be computationally intensive, and convert it to Rust, and then call it in Ruby pretty easily, using like, normal tools that you're used to using, basically will look correct. both these examples, sorry, the example and also
the libraries that I use, which are very tiny, are on my GitHub, so, GitHub.com slash Black Hat is like the last that we wrote in the push, and I'm happy, like, anytime the next few days to take questions, I'm happy to answer them. thanks. Thank you. Thank you.