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

Defense Against The Wrong Logic: Proactive Rust Coding

00:00

Formal Metadata

Title
Defense Against The Wrong Logic: Proactive Rust Coding
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
Rust guarantees memory safety, but it doesn’t stop you from doing everything. From unsafe to unwrap and more, there are lots of ways for you to subvert and make your code fail due to our own mistakes: logic bugs and incorrect assumptions. Come along and learn Rust techniques and patterns that will help build safe guards into your code, test your assumptions, and get a better understanding of what Rust and you can guarantee with your code. All so you can sleep sounder at night and not be woken up at 3am while on call. Follow us on Twitter: https://twitter.com/rustlatamconf
Open sourceFacebookLogicCoding theoryLogic programmingMachine codeLogische ProgrammierspracheCompilerOrder (biology)Software bugComputer animationLecture/Conference
TwitterInsertion lossMachine codeoutputFunctional (mathematics)BitCovering spaceInteger32-bitPattern languageType theoryData conversionMultiplication signLibrary (computing)
Right angleFunctional (mathematics)NumberType theoryAliasingLecture/ConferenceComputer animation
SpacetimeExecution unitMetreData conversionLecture/Conference
Machine codeType theoryFunctional (mathematics)NumberRight angleField (computer science)Physical systemCASE <Informatik>32-bitIntegerLecture/Conference
Square numberPhysical systemCircleType theoryFunctional (mathematics)Different (Kate Ryan album)Lecture/Conference
outputCellular automatonType theoryExecution unitCASE <Informatik>Object (grammar)Lecture/Conference
NumberIntegerType theoryResultantPrimitive (album)AliasingArithmetic meanBitBookmark (World Wide Web)Pattern languageGoodness of fitMathematicsState of matterDecimalLibrary (computing)CASE <Informatik>Lecture/Conference
Logic programmingRun time (program lifecycle phase)Software bugoutputComputer programmingLecture/Conference
Type theoryClient (computing)Latent heatFlow separationLibrary (computing)BuildingVariety (linguistics)Functional (mathematics)Group actionSet (mathematics)State of matterOrder (biology)BitPattern languageSoftware repositoryAdaptive behaviorIterationQuery languageSystem callInstance (computer science)Right angleLevel (video gaming)InformationGraph (mathematics)MathematicsMixed realityComputer animation
DatabaseSoftware bugProgrammer (hardware)Level (video gaming)Type theoryMultiplication signProduct (business)System callLecture/Conference
outputMultiplication signWebsiteMachine codeCASE <Informatik>NumberParsingLine (geometry)String (computer science)Lecture/Conference
String (computer science)outputBoundary value problemType theoryLecture/Conference
Dependent and independent variablesMachine codeEmailDependent and independent variablesEmailField (computer science)Error messageTelephone number mappingMachine codeType theoryLecture/Conference
Type theoryCompilerLecture/Conference
Machine codeEmailoutputString (computer science)Serial portDerivation (linguistics)Functional (mathematics)Type theoryData structureInstance (computer science)Lecture/ConferenceComputer animation
String (computer science)Type theoryError messageOrder (biology)Programming languagePoint (geometry)outputLibrary (computing)Boundary value problemMultiplication signComputer programmingParsingParsingLecture/Conference
Computer programmingBitLecture/ConferenceComputer animation
Computer fileConfiguration spaceComputer programmingCache (computing)Instance (computer science)Condition numberAsynchronous Transfer ModeRight angleMachine codeLecture/Conference
Run time (program lifecycle phase)Goodness of fitProduct (business)Computer programmingCrash (computing)CompilerAsynchronous Transfer ModeLecture/Conference
Machine codeoutputTwitterError messageResultantServer (computing)Line (geometry)RobotBitLecture/ConferenceComputer animation
Code refactoringOrder (biology)Multiplication signMachine codeMathematicsLecture/Conference
Library (computing)Expected valueCrash (computing)Error messageCompilerComputer programmingProgrammer (hardware)Machine codeType theoryString (computer science)RippingCore dumpBitComputer animation
CompilerComputer programmingStandard deviationCASE <Informatik>Instance (computer science)Programmer (hardware)Wechselseitiger AusschlussLibrary (computing)
Computer programmingGoodness of fitCore dumpLevel (video gaming)Context awarenessMultiplication signBitLetterpress printingMachine codeComputer animation
outputNeuroinformatikPointer (computer programming)Raw image formatPoint (geometry)Lecture/Conference
Standard deviationLibrary (computing)CompilerMultiplication signPlastikkarteMachine codeResource allocationSemiconductor memorySoftware developerLecture/Conference
Software developerMemory managementMachine codeCompilerMachine codeLecture/ConferenceComputer animation
CompilerDesign by contractInvariant (mathematics)Physical lawCompilation albumRule of inferenceComputer animationLecture/Conference
System callMachine codeLine (geometry)System callBlock (periodic table)Lecture/ConferenceComputer animation
Pointer (computer programming)Reading (process)Raw image formatMachine codeFunctional (mathematics)Lecture/Conference
Functional (mathematics)Machine codeCASE <Informatik>Pointer (computer programming)CompilerLatent heatDesign by contractRight angleString (computer science)Run time (program lifecycle phase)Message passingComputer animation
Physical lawFunctional (mathematics)Design by contractMereologyMachine codeException handlingLibrary (computing)Wrapper (data mining)Interface (computing)Lecture/ConferenceComputer animation
Projective planeOpen sourceMachine codeService (economics)BitMathematicsLecture/Conference
Control flowMultiplication signOrder (biology)Functional (mathematics)Invariant (mathematics)Machine codeRun time (program lifecycle phase)WritingComputer animation
Order (biology)Pointer (computer programming)Functional (mathematics)Principle of maximum entropyState diagramLibrary (computing)Sign (mathematics)Standard deviationLecture/Conference
Multiplication signBitSign (mathematics)Vapor barrierPrinciple of maximum entropyInstance (computer science)Computer animation
CompilerGrass (card game)Compilation albumMachine codeMultiplication signFunctional (mathematics)Lecture/Conference
LogicCoding theoryInterface (computing)Pattern languageType theoryLecture/ConferenceComputer animation
LogicCoding theoryOpen sourceFacebookMusical ensembleLecture/ConferenceComputer animation
Transcript: English(auto-generated)
Alright, so this talk is entitled Defense Against the Wrong Logic Proactive Rust Coding. Pretty much this talk is going to be about how we can use a couple guidelines on some things as well as using the compiler in order to make sure that you can make sure
that you actually can stop yourself from doing logic bugs because Rust prevents you from doing a lot of, you know, it prevents you from doing data races and things like that but it doesn't exactly stop you from doing the wrong thing yourself. So a little bit about myself, my name is Michael Gatozzi, I've been using Rust since
about 1.0. I make jokes on Twitter a lot of the time, that's how a lot of people seem to know me. So if you do take pictures of this stuff, feel free to at me, my mom wants some pictures as well. So that'd be great. So let's go over what we're going to cover today. So there's quite a few things, we're going to cover what's known
as new types as well as what the builder pattern is, dealing with converting input, insert assertions, what to do when you unwrap and expect as well as a little bit of unsafe guidelines when you're running your code. So let's get started about new types. So let's say we have some code here, you know, we want to create a
temperature conversion library between Fahrenheit and Celsius. So you might say, okay, I'll have a type Fahrenheit, I'll make it a 32-bit integer and I'll have a type Celsius and I'll make it a 32-bit integer and I have this function, is it hot? It'll take just the Fahrenheit temperature and it will return bool, yes or no, right? And
so we'll say, okay, we'll say that we'll have a temperature, we'll make it a Celsius, we'll give it a 32 as a number and we'll put it into this function, is it hot? So will this compile? Yes. Unfortunately, you can't, you can do that. And the
other thing is, you said that they're both 32-bit integers and that you can put that in there and that's not really good. We don't want our, we don't want to put Fahrenheit inside of Celsius or Celsius inside of Fahrenheit and when you deal with these kind of conversions between units, you have a lot of
problems. In fact, some of the space, like, spaceships have, like, blown up in flight because they were dealing with meters instead of feet or feet instead of meters. And that's not good. So we can prevent that. We can do this in a way using what's known as a new type. You define a struct and then you give it some internal value without a name for the field. So in this case, we
said, okay, we have Celsius, it has an internal field called 32-bit field and we have a struct called Fahrenheit and Fahrenheit has as well a 32-bit integer inside of it. So let's try that code again. Let's look at it in a different way. So
we've taken this new type and called it Celsius, right? It has 32 as the number inside of it and we've assigned it to the value temp. And then we try to put it into this function, is it hot? And if it is, it will say yes, otherwise it will say no, right? Will this compile? The answer is no. It will not compile. And the reason
being is that we've defined a struct, a type in our system called Celsius and our function is taking a struct called Fahrenheit. And Russ is going to look at it and go, these aren't the same thing. What are you doing? Don't put this in here. And so we can then utilize this type system as a way to make sure that, you
know, we only put, you know, the square and the square hole or the circle and the circular hole. We're not able to put things that are not supposed to be in the right spot or in the wrong spot into different places. But, you know, it's
kind of useful to be able to convert things. We want to be able to say, well, Celsius is a unit of temperature and so is Fahrenheit. Wouldn't it be nice if we could, like, convert that? And we can. There's the into trait. And we can define this. We can say, okay, we can implement into for Celsius, into for
Celsius for Fahrenheit. So it takes a Celsius's input and here's how we would convert it, right? And so we can do this in such a way that we can now convert between things safely. The way to look at it is that types and
structs and things, they represent ideas or objects, right? In this case, we're trying to represent a unit of heat. But traits define behaviors. Behaviors allow us to tell how these types work. And as a result,
we can do things that we couldn't do if we just use a type alias and dealing with primitives. Like a 32 bit integer is just a number, but a new type is a number with meaning. And so we can, therefore, define ways to give that meaning and those values that we're dealing with behaviors.
So in this case, here's how we define Celsius. You might notice it says dot zero. That's how you pull out the inner value of a new type as well. So we're saying, okay, we want the value inside of Celsius will add nine to it divided by five and add 32. Now there is one kind of iffy
thing about this and that is that we're kind of using as I 32. Uh, if you were, you know, actually making a really good temperature library, you probably wouldn't want to do that. This, uh, this would truncate any, uh, results that are not, um, or that are decimals. But this is just an example, um, as, uh,
to kind of give you an idea of like what you can do with this. So let's look at that again. You know, we'll define the temperature. Um, and then we can call dot into and then this will compile this. This actually works now because what we're saying is that, uh, it is hot is asking for a Fahrenheit and we're saying, okay,
temperature is a Celsius and we're calling a dot into, right? Rust knows that it's, it needs a Fahrenheit, but it also knows that all you have is a Celsius, but it has a way to do it. It knows that you've implemented a way to change Celsius into Fahrenheit. And so you can call that into, and this compiles and it works.
So I want to talk a little bit about the builder pattern. This is probably one of my favorite things inside of rust. You'll see these extensively in a lot of places. Um, so what's the builder, what's the builder pattern used for? We want to use it to avoid invalid States. Uh,
what I mean by this is, uh, someone's using your library and you've defined a pathway to make sure that they cannot do things that will not work. Um, they can't do something that would cause the program to, you know, take input that would, you know, it would compile,
it would run, but it wouldn't exactly work the way they expect. You basically have a logic bug or a runtime error. And so what we do is that we use types to represent these States. What we're saying is that like, okay, like, you know, here's, here's one type that represents this. Here's one type that represents that. And then we find a way to change it.
And so by this we use functions to change these States. And so what you're doing is you're doing a defined pathway for people to move from one place to the next. If this sounds like a graph, it is. Um, and so here's an example. So let's say we're building a library to connect to get hub. We want to be able to do, we want to interact with the API. We want to,
uh, you know, do a variety of things. Um, and I've, and I've done this in my own library. Um, and so we'll have a client type that lets us do all kinds of requests. Uh, you know, we want to do get requests, there's delete requests, post requests, requests, put requests,
all kinds of separate things that you can do. Now what we do to get to those States is that we'll say, okay, we'll call get or delete or post or put. And that brings us to the next type, right? But this type, when you implement, you know, you'll say like imple delete or imple get, right? These only have a specific amount of functions that they can use, right?
You can't just all of a sudden post and then to like a user repo, like that's not allowed. Um, but we do have get and delete, which allow us to get a user's repo. And so these functions allow us to transition from one to the next. And it's a little bit easier if you see it kind of in action.
Okay. So this is kind of what it would end up looking like, right? We have a client, we want to get some user, um, we'll want to get their repo and then we'll like send the request. And so the builder pattern allows you to build up a set of functions in order to kind of do these changes. Uh, oftentimes you'll see it with like iterator adapters. Um,
you might call like iter map, um, and then collect, right? Um, there are ways to kind of build up, uh, ideas or things that you want to run. Um, and it's fairly you, it's used throughout, uh, the Russ ecosystem. Uh, you'll see it a lot with like diesel, for instance, you build up a query. Um,
and they do a lot, diesel does a lot of like really interesting, some other type level hacks, um, to like really make sure that you can only do very specific things. Um, and it ends up allowing you to like make sure that you don't do like database queries that do not work, right? Things that you might not catch, uh, until, you know, it's 5m in the morning production is down and you're
getting a phone call, waking you up going, Hey, everything's broken. Um, subtle bugs that, you know, you could catch at compile time if you knew what you're doing, but you know, it's really hard as a programmer user to catch. Uh, and so, uh, you'll, you'll see this throughout. Um, and yeah.
And so the next thing I want to talk about is about converting input early. Um, so let's take an example. Let's say that we are making a request to some website, you know, uh, we'll say, okay,
I want to make this HTTP request, you know, and I get the value back and it's just the HTTP requests and HTTP has been around for quite some time. It's a lot of just strings. Uh, and you could say, okay, well, I want to get the status code of this and you could go,
you could iterate through the lines, find the status code, parse it, grab the number and then check, was it like a 200 or was it a four Oh four or whatever the case might be. Uh, but that's really prone to errors.
You're kind of, if you just pass the string around, there's no guarantees that it hasn't changed. There's no guarantees that, um, you didn't parse it correctly or there's no guarantees that you parsed it correctly. Um, you're dealing with a lot of uncertainty. The boundaries of input is just a bunch of strings that you're trying to deal
with as a type is not exactly the best. It would be better if you had something more like this where you made a request and you got a response. You have a field headers where you have all the headers parsed out correctly. You have some status code, uh, you have a body made of Jason.
These are types that you can again deal with. They are types that again have behaviors and traits that you can work with. Um, there are things that you can match against a status code. It could be an enum that you can match against every possible thing that happened. You could say, Oh, I got a four Oh four.
I want to throw an error or Oh, I got a 200. I want to make sure that like we continue on with whatever we're doing. Types allow us to check a lot of things and the faster that you get things into types, the better. Um, traits, traits and types are very powerful things that allow you to do a lot,
a lot of interesting things. And you're able to check your work with the compiler. The compiler ends up being your friend rather than being this antagonist. It's able to make sure it's got your back in a way. So with that, I also want to mention that Saturday is a fantastic crate.
If you want to deal with transforming string based input into actual types, it makes it super, super easy. Um, I use Saturday Jason a lot, for instance. Um, you know, you define a structure like, okay, here's what it looks like. It kind of looks like the Jason in a way. And then you say, okay,
derive serialize or do arrives, uh, de serialize. And then it just works. You, uh, call the function saying, okay, here's my string, here's the input, turn it into this struct and then that's it. Um, and so there's a lot of these kinds of crates that deal with the fact that we're going to have a lot of string as input at the boundaries of your
program. Anytime you take input from a user, um, and the faster you turn it into a type, the faster you can deal with it inside of your own program and have a lot of benefits for doing so. Uh, there's also parsing libraries like nom or, um, pest. You can use those as well in order to like write your own parser. If you're trying to like write your own language or something like that, uh,
you can use these things in order to parse input. But I think the key point I want to make sure is that you use types. Types are powerful and the less you're dealing with a string based API, the easier it is to catch errors that you might have. Okay.
So I want to talk a little bit about assertions. Uh, assertions are ways to say I need to stop my program because some value or something that's supposed to be here or supposed to be like this is not, and it would make more sense for me to crash my program than to continue
on. Um, um, it tends to be the case in like larger programs. Like if we're not able to load up, uh, you know, for instance, a, uh, configuration file or, uh, the cash that you're using gets invalidated in a really bad way that it
subtly breaks everything. Uh, assertions are ways to make sure that you can kind of just say, you know what, let's just stop and not continue running the program. Okay. So there's a couple, there's also a cert not equal and debug assert not equal. Uh, just forgot to put it on the slides. Um, but assert says that, you know,
okay, I will take some condition or something that evaluates to Boolean yes or no. Uh, assert equals just says the thing on the left side is equal to the thing on the right side and debug assert does the same. Um, but they're slightly different in how they work. Uh, uh, assert will be in your code even in release mode. And this is like,
this is what you want to use when you're saying, okay, as I'm running my program, even in production, I want to crash it. If, if it's really bad. Um, and so you can use these assertions as kind of like a double checker for you at like run time. Uh, these extra things that you can kind of look at and go, okay, if it fails,
I want to know that it's failed. Um, whereas debug assert is very useful while you're writing your program, uh, when you're not running it in production. And the reason being is that it compiles away. So in release mode, it's not there. So, uh, it's good if you're like kind of just, you know, you want to have a couple of things in there as you're like working.
You're like, okay, I think this should be equal to that. But if it's not, you know, it's not good. So like crash my program. And then I'll be able to go and try and fix it. So you can use assertions, um, for things that the run time or that the compiler can't necessarily catch with types. Um,
and it's really just more of just something that has to do with input or how your code parses or deals with things. Okay. So I want to talk a little bit about unwrap and expect, uh, I have a lot of opinions on this one. Uh, should you unwrap is a question that gets asked a lot. Um,
and my answer to that is it depends. Uh, I've had a Twitter bot that ran for like six months that I wrote that was like a hundred, 200 lines of code. And the only reason it stopped working was because the server rebooted,
not because, uh, the code was bad or anything. Um, and also like when you're prototyping and you're just working on things and you're just like trying to get something working really quickly, it's great. You know, you just, you're saying, I don't care if the result is an error and I don't care if there's
nothing here. I'm pretty sure there's going to be something just like, let's unwrap and deal with it. Um, this is, uh, and that's fine. But when you start getting larger, it starts ending up being a problem. Uh, at work I spent about two weeks or about a week of time just trying to remove
all that. And it was the hardest refactoring I've ever had to do. And then the PR never ended up getting merged because it was just too big in order to, uh, kind of go, okay, we understand all these changes that are being made. Once it's in your code base, it's kind of hard to take out. Um,
so I have a few guidelines regarding this. Uh, you kind of want to use error handling as soon as possible. Uh, libraries like failure, um, are really good for this. They wrap up all the errors in a type that you can just kind of return to at
the top. Um, and the faster that you end up using error handling, um, the easier it is to just keep it that way. If all of your programs using it from the beginning, it's not hard to add it in versus if it's not using it from the beginning, it's a lot harder to add it in. Um, if you can and if you do need to use expect or unwrap or unwrap,
try to use expect. Um, nothing's worse than having the code crash and you're not really sure where because the stack trace just says that it failed inside of lib core and that's not useful. You don't know where the program crashed. Um,
and so with expect, you can give it like a little bit of a string like, Oh, we expect that we expected two plus two to equal four, but it was five for some reason. Um, and then you can kind of search for that in your code base. You can use like rip rap or just grab and you can find that and you'll be able to kind of go, okay, like I know where it crashed. At least we can go from here. Um,
and so if you do use expect and unwrap, you should only use it if you know it will never panic. Um, what you're telling the compiler is you're saying, I know exactly why this is fine. I'm saying, don't worry about it. Uh,
this will always be a value or this will never be an error. The compiler doesn't know that the compiler can check types. It can do borrowed checking, but it doesn't know everything. It's only so smart and we as the programmers can know certain things about it. Like we can look at that program and go, Oh yeah, that is, that's fine. Um,
so if you do do that, you're telling the compiler this, that it is fine. In some cases it's totally perfectly fine. Um, oftentimes I will use it with like the standard library mutex to just unwrap it when I get a lock because I'm never going to crash the program such a way that's going to like poison the lock. Um, for instance,
uh, so if you do do that though, try and leave a comment. Or as, as to why it's okay, it might be obvious for you. Um, but for someone else coming to read your code base maybe or even yourself six months later, um, if you're in that spot, you might not know why or you might not have the context or you might not
be someone who does know these kinds of things and you want to be able to kind of tell people this is okay. This is not a bad thing. Like here's why. So I want to talk a little bit about unsafe. Um, it's a, uh,
not the best name. I think a lot of the times people kind of look at unsafe and they go, Oh no, we should never, ever, ever write unsafe ever. We should ban it from everything. Uh, you can't write a good rust program without unsafe at some level it's being used. Um, like try,
try doing like a no STD and a no core program and try writing it to like print out hello world. And let me tell you, it is very, very, very hard to do, uh, without, and you have to use unsafe. Um,
at some point you are dealing with a computer and a computer needs input and output. Um, and as much as if we want to kind of pretend that it's not there, it is. Um, and you kind of had to deal with these like raw pointers and things like that. Uh,
but the nice thing is that a lot of the compiler and S standard library deal with this for you. A lot of smart people have spent some time writing it to make sure that we have some really safe code that's really performant that, uh, allows us to deal with all this stuff. Vec is a really great, uh, example. Uh, you don't have to worry about a growable Ray.
You just throw values into it and it works. Um, I like to call it the memory safe, the memory safe allocator for rust developers in the sense that, uh, you don't have to worry about memory allocation. Vec just does it for you. Um, so with unsafe, what you're trying to tell the compiler is that
this is fine. This is okay. Code. You can't, I can't prove that to you, but you should listen to me and just trust me. And I think that's kind of what we should have called unsafe is just trust me. Um, because it's not really unsafe so much as it is just you have to be
careful. You're, you're upholding, uh, invariance laws like a contract. You have this contract with the compiler and the compiler is expecting you to uphold that contract. You don't want to break the compiler's trust because what happens up happening is that you ended up having like undefined behavior, seg faults,
all kinds of not fun things. The things that rust is trying to prevent but say for us can only do so much. Safe for us, uh, knows its rules and it's says, okay, I know these work, but unsafe for us to saying, I'm gonna let you do some other stuff,
but I need you to also make sure that whatever you're doing upholds these rules too. Cause I can't, I can't do that. Uh, so some guidelines with that, um, you want to minimize the scope of unsafe calls. You want to kind of keep it to one line if you can or maybe like a block,
uh, the less, the less kind of, uh, unsafe that you use, the better it's going to be for you. Um, and the reason being is that, um, it's a lot easier to think about. It's a lot easier to navigate about in your code. Um, if you have just a whole lot of the code is unsafe,
it's tagged on safe, but really only a portion of it should have been, it's kind of hard to think about, Oh, did, especially if someone else comes in much later and helps work on your code base. Oh, is that unsafe? Why is that? And they have to think about all these things. And if you just say, no, this pointer read, uh, this raw pointer read is unsafe and you just leave it there,
then it's a lot easier for someone coming in looking at the code going, Oh yes, just this one spot. This is the one spot that it's unsafe. And here's why. If you are doing functions, uh, you're writing code and someone has,
who's calling your function needs to uphold a very specific contract themselves, right? Um, like, Oh, don't, don't pass a raw pointer in or don't pass a null pointer in or whatever the case might be. Or we assume that this is a null terminated string, right?
These things that the compiler can't check, obviously they're at runtime, then you should mark the function itself unsafe. And the reason being is that you want other people when they're calling that function to know, yes, this is fine. Uh, or I need to, I need to uphold certain laws. They're also part of this contract.
But what would be even better is that instead of exposing unsafe interfaces, you actually provide safe interfaces on top of them. Um, Nix is a wonderful crate as an example for this. Uh, it's a wrapper around libc and not only does it deal with all the not so fun
stuff with error handling in libc, but it also provides a rust like interface on top. So instead of dealing with like I'm writing C code that just happens to be in rust, you're dealing with, I'm running rust code that happens to be calling C functions and that's a much nicer experience. Um,
so if you do use unsafe where you call it again, comments, you don't know where you'll be in six months when you come back to your code. You don't know if some, if it's an open source project, somebody coming into your code base is looking at it and going, Oh, I don't know. I don't understand. Um,
you, you yourself know and you're kind of doing yourself and future people a bit of a service by just documenting it for them saying here's why it's okay. And uh, if that changes, then it's probably unsafe. And then you also want to make sure that you document invariants for unsafe
functions. Uh, and by that I mean, what are the things that people who use this unsafe function are required to do? A lot of the time it's easy to go, Oh, I don't need to write documentation. It's fine. People who are calling this will understand. Sure. For regular functions maybe. Um, but with unsafe functions, uh, you know,
it's going to be pretty subtle as to what needs to happen or in order for it to work in such a way that it doesn't break people's code at runtime. A lot of the time it's, you can't really notice that until it's too late. And so by documenting and kind of putting a big warning sign on it, people can go, Oh, okay, I understand what I have to do.
If you look at the standard library docs, especially under a STD D mem and STD pointer or PTR, you'll see that there's quite a few functions in there that are unsafe. Um, and they're really great examples of this. They're fairly well documented and they tell you all the things you should do
in order to make sure that it's safe as well as like the couple of the gotchas if you do it incorrectly. Uh, mem transmute is a very particularly, uh, big red warning sign of don't use this. It's like a, uh, it's the barrier between you and a really bad time is just a little bit of
dental floss. It is not much. Um, and that's not to say that you shouldn't use it. Sometimes it's very, very useful. Unsafe is a very useful thing to have and sometimes they're just things that you know yourself is going to be fine. Like, okay, you could use, uh, from UTF eight unchecked for instance. Well,
if you're directly writing all the UTF eight bytes into an array and it's statically there, well that's fine. You know it's there. So you can just use that. You can use it unchecked. Like why are you doing a compile time check for something that you already know works? Um, so just, just be careful, write documentation for it. Um, and uh,
but also know that it's not a bad thing. A lot of times people kind of look at unsafe as this like, Oh, I should never ever write it ever. And you should, I think if you haven't spent some time running some unsafe functions, I think you should. But if your first instinct to is when you're writing grass code to go, Oh, the compiler's in my way.
Let me just do this thing that I did or whatever in an unsafe function, you probably want to try and find a more rust like way. Usually when the compiler is telling you don't do that, it just means that you're not really kind of acting within rusts design. So that's kind of all I have for today. Um,
we covered a lot of, uh, a lot of things. Uh, we covered, uh, new types, we covered, uh, the builder pattern. We covered, um, kind of making sure that you turn stringly typed interfaces into types, uh, a little bit of unsafe and as well as about unwrap and expect. Um, so yeah, thank you very much.