RustConf 2021 - Supercharging Your Code With Five Little-Known Attributes by Jackson Lewis

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hi folks and thank you for listening in my name is jackson lewis and today i want to talk about cool ways to use rust attributes that you might not have heard of before i'm going to assume that you have an intermediate understanding of rust for this talk because today is going to be a deep dive into some of the more obscure aspects of the language that being said i've done my best to make this accessible to everyone let's get right into it shall we so as albert einstein once said rust attributes are pretty lit i agree completely with dr einstein as the man who conceptualized space-time he would surely understand that part of being a programmer is acting like a time traveler you must protect your code from your future self who will forget all the nuanced details of what you wrote and ruin everything luckily we rust stations have tools to help mitigate this issue code comments idiomatic guidelines and strong typing but in my opinion the attribute is one of the greatest tools we have as rust programmers one that can save on boilerplate reduce the chances of making mistakes and bruise and boost productivity all at the same time so for those of you who don't know an attribute is like a tag that you can put on a function struct or statement that tells the compiler to do something special if you've ever derived a trade for a struct then you've used an attribute from the standard library or you might have used the test attribute before to write a unit test but that's not all you can also define your own attributes and import them from third-party creates for example seer day is a popular third-party attribute that will automatically derive functions to manage the serialization and deserialization of your structs you can see it being used here and there are other popular libraries besides cyrdae like tracing asynctrade and so on but i'm not here to talk about those because those popular libraries are only the tip of the iceberg if you browse through crates.io you will find hundreds of attribute libraries that get completely neglected and even in the standard library there are attributes that no one seems to use so today i want to talk about five of these attributes that are hidden in both the standard library and package registry and how they can give your code superpowers so to keep this talk interesting interesting i'll be explaining these attributes through a hypothetical story okay what we have right here is the option enum one of the most foundational types in rust one day a controversial feature request proposed is adding a third variant to the option enum called possible while the maintainers insist that it's rather redundant the creator of this feature request says that it will revolutionize the language but there's a problem currently people can write code that matches on an option like so we have a branch that matches on the sum variant and a branch that matches on the non-variant this code isn't a is an exhaustive match right here since it matches on all the variants of the enum however with the introduction of the new possible variant this match on option is will no longer be exhaustive and this code will fail to compile as you can see exhaustive matches are fragile to changes in enum variants or struct fields because of this crate maintainers can often feel pressure to push a new major version every single time they make modifications to public enums or structs in the worry that the changes will break downstream crates but is there a way to prevent this from happening as a matter of fact there is so let's rewind time a bit and go back to our hypothetical story if the rust team had planned from the very beginning to expand option in the future and didn't want to break existing code when they did expand it what might they have done the answer is the non-exhaustive attribute non-exhaustive tells the compiler that more variants or fields will be added to the enum or struct in the future so any crate which uses it needs a wild card when pattern matching even if they've already matched all the existing variants or fields it should be noted that adding non-exhaustive to your enum or struct is a breaking change to your api because it invalidates any match statements that do not have a wild card branch so if you want to make your e-number struck non-exhaustive it's best to do it when you first make it okay let's go back to our code from earlier if non-exhaustive had been added to the option enum our pattern matching code would wouldn't just look like this we would need to add this wildcard statement too uh yeah even though we've exhaustively matched all the variants we need to add that wildcard or the compiler will throw an error because the wildcard will cover future changes to this enum as you can see even after we add the new variant this code will not break since the new variant will be covered by the wildcard branch that we added and that's the power of non-exhaustive okay so shortly after this controversial rewrite you take a break from open source to work for a local pizza restaurant they are very forward thinking so their website back end has been written entirely in rust however they also have a legacy code base written in c which is slowly being replaced with rust code they have a function in this rest code called pizza validator that checks if a customer's pizza order is valid unfortunately it has two major problems one it's poorly optimized and two it allows customers to order pineapple on their pizza gross however for one reason or another the company doesn't want you to make any modifications to the existing function instead they want you to write a new function that will gradually be phased in to replace the old one so a short while later you've written a new and improved pizza validation function that outlaws pineapples and you've gone around the code for the new site and replaced many of the existing calls to pizza validator with the new function but there's a problem a few days later in order for pineapple pizza comes in which you know shouldn't be happening you identify the problem in a function called add to order it turns out that someone else recently rewrote this code and in the process used the old validation function but to be fair there was little indication that using it was wrong so let's rewind time again what should have we done what should have have been done here to prevent this well it would be nice if we could mark this old function as being improper to use without outright removing it and it turns out that the standard library gives us a way to do this all we need to do is put the deprecated attribute at the top of the function and the compiler will emit a warning when someone tries to use it you can even provide a custom message to go with this warning so now if we try to use the old pizza validation function in the new function code we're going to get a warning from the compiler and in this case you don't just want a warning you want to make sure that using a deprecated function will throw an error for certain parts of your code base so if you put a deny deprecated attribute here or at a higher level in the crate the compiler will make this an error instead of a warning and that's the power of deprecated a few days after this incident a report comes in about the legacy code base a segmentation fault is occurring due to a mistake that a programmer made while using an unsafe function called two box this legacy code base has several unsaved functions that require the usage of raw pointers in order to work with the old c library and two boxes meant to convert these raw pointers back into a safe box pointer as you can see whoever wrote this function left safety documentation in the comments which is the right thing to do however there are still two problems with this code the first problem is that two box can still be used without the programmer looking at the safety documentation and the second problem is that the this safety documentation fails to mention that passing in a null pointer will also cause a segmentation fault and it is unsound to pass in a pointer that points to memory which was not allocated by the global rest allocator but you know i can understand that because when you're writing a safety comment it's possible to accidentally overlook some edge cases now what can we do to fix this well we could start by expanding the safety section and putting a debug assertion to check if the pointer is null but still proving that the function is safe is still dependent on the function itself and not on its color wouldn't it be better if the caller was responsible for proofing that they're using the function safely well this is where pre comes in pre which is short for precondition is a very underutilized third-party macro crate that requires assertions to be placed at both the function definition and the call site first we need to add the preconditions to the function definition to two box as seen here so in this case the pointer must not be null the pointer must be unique and not be shared with another object and the pointer must point to memory allocated by the global allocator this you know looks very similar to what we wrote in the safety documentation okay now let's go to the code that was segfaulting and see how pre can help programmers avoid mistakes with unsafe functions so at a glance it looks like this code is run when the user wants to create a new pizza with random toppings as you can see the code first creates a pizza struct by calling into the legacy cc library which appears to be wrapped with a safe binding on the rest side so we don't need to use unsafe here the pizza struct does have a default method on the rest side of things but it looks like we're using a function from c to allocate it for whatever reason then it passes the pointer into the randomize function which will randomize the toppings for the pizza though this also calls across the cffi it appears to be wrapped here so we don't need to use unsafe and then finally it gets a unique box pointer to the pizza back using two box since two box is an unsafe function we need an unsafe block here however since we've added preconditions to the to box function we also need to assure that we're using this function correctly or this code will no longer compile and adding these assurances will help us spot the reason why this code is segfaulting so first we need to assure that this pointer won't be null by putting the assure attribute which is part of the pre-library on this two box call this will run an assertion in debug mode at the call site with the provided condition in this case that pizza pointer is not null note that we also have to add a reason as to why this precondition is satisfied which makes users more aware of whether or not the provided condition will hold as we add in the other assurances along with their reasoning we catch the problem two box is only safe when we're using a pointer which was allocated with the global rest allocator so the first line of code where we allocated the struct from c was the offender after we rewrite the code to allocate the pizza on the rest side we can provide a proper reason in the uh sure attribute for why these conditions are satisfied as you can see pre is a great way for users of unsafe functions to be cognisant of when needs of what needs to hold true for the function to be safe and that's the power of pre that being said i highly recommend that you limit this attribute to functions that you define internally so downstream users don't need to add pre as a dependency to their crate just to call the function the next week you're asked to perform a refactor on the pizza struct when you go to make some changes to it the definit the definition looks off something about it is seriously bothering you but you can't place your finger on it oh that's right it's these publicity modifiers right here the fields are public so that you can read from um from them directly instead of using a getter function that's useful but any programmer could also modify these fields if they owned this pizza variable immutably furthermore since all the struct fields are public the struct can be initialized elsewhere using raw struct initialization syntax and all of this bypasses the existing functions which are meant to handle state changes and initialization of the pizza struct but the good news is there's an attribute to fix this it's so useful in fact i wish it was in the standard library it's called read-only all you have to do is add read-only as a dependency to your crate and then put the read-only make attribute on the struct of your choosing any function that would have had access to these fields even if they weren't public can still write to them for example this mozzarella method can still write to the struct directly but outside the file you can only read from the public fields and you can't initialize the struct with struct initialization syntax so this function right here will compile just fine since we only read from the cheese field even though we're taking the struct as a mutable reference however this function will not work since we try to modify the field directly and then we also try to build a new struct using initialization syntax this will create two separate compile errors so but to solve our particular problem we don't need to make all the fields read only we just need to keep a few fields from being changed the good news is that read only supports this too by putting the read-only attribute on individual fields the other fields can still be written to based on the normal publicity modifiers and that's the power of read-only okay finally there is one major problem that still needs to be resolved with the pizza restaurant's website the legacy system and the new system use a separate database for storing customer orders since the new system should read from both databases a trait called order has been created which provides a common interface over the two order formats which will let the new system deserialize operate on and then serialize these two schemas interchangeably um but unfortunately that doesn't work because cerade can't serialize and deserialize trade objects due to object safety rules with serialized and deserialized traits as you can see here if we try to create a box pointer to a dynamic trade object the compiler will tell us that order is not object safe and this is because uh seer day um cyrado's serialized and deserialized traits are requirements for this trait and those are not objects safe themselves so let's go back to the order trade this time with no requirements for serialize or deserialize because that clearly didn't work as planned in order to serialize and deserialize an arbitrary object of this we're going to need the third party type tag attribute type tag lets you serialize a trade object as an enum or each variant is an implementer of that trait and the content is the serialized version of that implementer here you can see that we serialize the order trait as a tagged enum using the arguments we passed into the attribute to specify the names of the tagged fields in this case type and content and then as long as each implementation of order implements serialize and deserialize themselves we can use type tag to make them serializable as trait objects all we need to do is put the attribute above each implementation and this works even if the implementations are in different crates which i think is really cool now the code which previously failed will compile without any problems and that's the power of type tag alright that just about wraps it up i hope you learned at least a few new attributes and how you can apply them to real world scenarios here is a list of cool attributes that i didn't have the time to talk about i highly recommend you check them out on your own time thank you so much for listening and have a great rest of your rest golf you
Info
Channel: Rust
Views: 4,774
Rating: undefined out of 5
Keywords: rust-lang, rust, rustlang
Id: 8d7DqeYXq7A
Channel Id: undefined
Length: 15min 14sec (914 seconds)
Published: Wed Sep 15 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.