Beginner's Guide to Rust Operator Overloading πŸ¦€ Rust Programming Tutorial for Developers

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey guys my name is Trevor Sullivan and welcome back to my video channel thanks so much for joining me for yet another video in our rust programming tutorial Series in this particular video we're going to be exploring another feature of the rust language which is known as operator overloading now before we get into how to implement operator overloading let's actually talk about what operator overloading is well as you probably know pretty much every programming language out there allows you to use common mathematical operators like the plus sign the minus sign the division sign which is basically just a forward slash you also have the modulo operator which allows you to get the leftovers after a division operation which is typically the percent sign and you can actually customize the implementations of these operators yourself which is known as operator overloading now normally when you're operating on the Primitive types in Rust like a floating 0.32-bit value or maybe an unsigned 8-bit integer or maybe a signed 64-bit integer the addition subtraction multiplication division and modulo operators are going to have a pretty well understood effect right but what happens if you implement a custom data structure into your rust application like a person or a dog or some kind of animal and you want to implement some kind of addition operation between those custom object types for example when you have let's say two different people right you have a person that has a first name and a last name and a birth date maybe a social security number and let's say that you have two instances of that person struct well what happens when you take the person number one and you add it to person number two well there's a variety of different functions that you might think could result from that but let's just say for the sake of example that these two people get married so the result of the addition operation isn't some kind of numeric number or value it's actually going to be a custom construct known as a marriage that ultimately points to those two individual instances of people those are the two individuals that chose to get married and so the resulting object from the addition operation could be a marriage data structure it's really totally up to you exactly what you want to results from the different implementations of these operators but for now just be aware that you can actually take the core operators that work on primitive types by default and you can actually Implement those behaviors for your own custom data structures in Rust so under the rust by example book here there is a brief article that talks about operator overloading this is a really simplistic example that doesn't really show any kind of real world Concepts it's just kind of a Fubar example that allows you to see the custom implementation here but we're actually going to take a couple of real world approaches one of which I just mentioned being a marriage between two individuals and we're going to go ahead and Implement that in Rust code and then we'll take a look at another example so make sure that you watch to the end of this video to see what that other example is we're going to overload the addition operator again for another real world example so if you head over to the standard crate here and go to the Ops module here so if you're in the standard crate documentation here you can just head over to modules and then of course there's a bunch of different modules here some of which we've looked at and then head down to Ops here and if we take a look at the Ops module here the way that we can actually Implement these custom behaviors or overload these operators in Rust is to implement certain traits here now we've already taken a look at traits and super traits and we've seen how to implement traits but these traits are kind of special because they actually allow you to modify the default behavior of these operators for your custom types so this is really cool functionality we've got the add trait here that allows us to specify what happens when we add items together we also have things like the division trait we have things like the subtraction so down here we've got sub and we've also got multiplication or just mul for short and then I think we have one for modulo operations here as well well but I'm not seeing it let me just do a quick search for the percent sign here so it's actually called the remainder operator in this particular case and so that's the one that you would Implement if you wanted the leftovers from a division operation so what we're going to be doing is taking a look primarily at this add method right here or I should say add trait and if we take a look at the ad trait and look at the definition of it it allows us to specify the type that we get as an output so in the case of having two custom data structures as person objects then when we add those together we would get a marriage struct as a result so a marriage struct could have pointers to the two individuals as well as having a date that the two individuals got married as well as perhaps a location you know that could be a country it could be a state it could be a city whatever you want to use for the location there could be other types of things that we want to represent on a marriage as well but a marriage is going to be a custom data structure that ultimately points to the two individuals that got married and in other examples you might have some other kind of output type so this is your opportunity to declare what the output type of the operation is going to be and then the only method that is required by the added trait here is the add function here and this is just going to take in itself the current instance that we're operating on as well as the right hand side or rhs input here and that's the second instance of that type that we want to add now something else that's really cool is that you can actually implement this as a generic type as well so if you want to add a different type to a type number one let's say we wanted to add a dog to a person so rather than having a marriage result from that we could have an ownership relationship where when you add a dog to a person that dog could become a pet or an uh the the person could become an owner of that particular dog as a kind of person to pet relationship instead so again it's completely up to you how you want to do that but you can actually add a different type to whatever type it is that you're calling the add method on and again depending on what type it is that you're adding to that type then that could kind of change the actual implementation of this particular trait so let's go ahead and take a look at some code and we'll we'll write this completely from scratch so there's no smoke and mirrors you'll actually see how to implement this kind of functionality completely from scratch in your program and before we get into the actual code I just wanted to remind you to support my channel I'm an independent content creator so any kind of support that you can provide to my channel is extremely helpful so head out and subscribe also check out the different playlists that I have featured on my channel here I've got one called rust programming tutorial and I've got some other ones on things like virtualization on Linux with lxd open source software in general Powershell automation Amazon web services training and a whole bunch of other interesting stuff coming down the pipeline as well so make sure that you keep an eye on these playlists also if you leave a like on the video leave a comment and just let me know what you thought of this video by the end if you learned something new I really hope you did then leave a like on the video and we'll continue bringing you some fresh content all right so I am connected here in vs code to one of my remote Linux virtual machines and I've already got the rust runtime installed so we're just going to create a new project here and just to show you I'm currently on Rust 1.71 and that should work perfectly fine I think I could technically upgrade it to 1.72 or to Nightly but for now we'll just stick with the current version so what I'm going to do is just create a new project folder here so we'll say cargo new and we'll say op underscore overload and now we'll go ahead and open up that project in my editor here so let's open up the op overload directory here and this should have a very basic scaffolded project for a rust binary so in cargo.tommel here we don't have any dependencies this is the name of our project make sure that the name of your project doesn't conflict with any rust keywords or module names or things like that because you can potentially run into issues so if I call this like survey for example that's probably not a good idea because there's already an existing survey crate out there all right so what we're going to do is just go into our main Dot RS file here will eliminate the hello world statement that we have and for starters what we're going to do is just Define a couple of data structures so up here at the top of my program we're going to define a person as a data structure we'll give it a first name field with a string data type and a last name as a string data type as well and then we're also going to define a marriage data structure because we want to be able to take two person instances and add them together with the add trait and then we're going to implement that functionality to actually result in a marriage object so what we're going to do is say struct marriage and then we need to have two different pointers one to the husband one to the wife we also need a location and a date field to represent what a marriage looks like from a data standpoint right so for starters we're going to have a husband field here and we're going to set that to a person we're going to have a wife and set that to a person we're also going to have a let's see location and we'll just make that a string type directly on the marriage and then if we want to have a date we're actually going to need to bring in Chrono so if you go back and watch my video on Chrono that'll kind of show you how to work with some dates and times so for now we'll just say cargo add Chrono into our project and then we're going to set the marriage date field to the Chrono and then we'll go into the naive module here actually I think we can just do naive date right here on the root of the Chrono module and that'll allow us to specify the date that the marriage occurred so now we want to do is basically construct two different person instances each with their first and last names and then we want to be able to add those people together so for starters in our main function down here we're just going to kind of proceed as if we're not doing operator overloading and we're going to see the problem that you'll result in if you try to add these structs together so let's do a person one and we'll set this to a new person struct with a first name of trevor.to string and then we'll set the last name to Sullivan dot to string and then I need a semicolon there and then we'll just duplicate that line down and we'll say let person number two equal let's say Nancy and then Jones as the last name so let's say that these two people are going to get married and so we want to be able to say let marriage equal person one plus person two now as you can see this in theory would work perfectly fine if these two operands right here were numeric types if we had like zero and one as integer values we could add zero to one and we could get one as a result or we could add 5 and 10 together and get 15 as a result but what exactly does it mean when you add two people together well we get an error from the rust compiler here saying that we can't add a person to a person because as far as rust knows there is no operator that allows us to add to two people together so we have to basically tell rust how exactly we want to implement the functionality of adding a person to another person so that is where the trait comes into play standard Ops add and this is part of the Prelude and we haven't really talked about preludes before but a Prelude is basically just a set of default Imports that you get from a particular crate so in the standard crate over here we actually have a Prelude and this includes certain types out of the box and some of those are from the standard Ops module right here but we can also be a little bit more explicit in our declaration here by just saying use standard Ops and then add and that will allow us to explicitly import this particular trait if it's not already imported by the Prelude so what we're going to do now that we've imported this trait is we're going to go ahead and implement it and you can or this warning because it's basically just telling us that we've imported this right here but we haven't actually referenced the ad trait anywhere in our program yet so this is just a warning it's not going to prevent you from compiling of course we will get the error from our program saying that we can't add two people together but for now we're going to go ahead and take the compiler's recommendation and implement the add trait for person so down here we're going to say impul add for the person data type here and you're going to have to implement both the type parameter which declares the type of object that's going to result from this operation so for starters we're going to say type and it already knows from the metadata on the trait that the type output equals something and then we have to specify that the resulting object type that we get from adding two people together is a marriage so what we're going to do now is implement the add function right here so you can see automatically vs codes rust analyzer extension knows that we have to implement this add function right here and so we're going to assume that we're going to always start with the husband and then we'll add the wife as the right hand side argument here that's just going to be kind of kite by convention here there's nothing enforcing that but if you implemented a custom husband type and a custom wife type then you could start with the husband and Implement add-on husband instead of just the generic person type but for the sake of example this is going to work perfectly fine we'll just use a single type as person because I don't want to complicate things too much all right so what we want to do now is make sure that we satisfy this add function here by returning the self output type so basically the person output type for the add trait is going to be a marriage so this add function right here in order to satisfy this type declaration here we have to make sure that we return a marriage object so anytime that we call add on a person here we're going to construct a new marriage so we'll say new marriage equals a marriage instance here and then we need to provide all of the inputs to the marriage data structure so we have location we have date we have husband and wife so I'm just going to set husband to self so basically whatever person we call the add method on is going to be considered the husband and then we'll set wife to the right hand side argument which is the second person instance that we're passing in to the add function so then what we're going to do is set the location for now I'm just going to hard code a location like Arizona and say dot tostring to turn that into a heap allocated string and then we're going to set the date so the date of the marriage is going to be Chrono and then it will go into naive module and we'll use the Now function which I think is in there so let me figure out where that is coming from and it looks like it's not in there so let me figure out where that's from so I'll head over here to the rust Chrono docs and check out where that Now function comes from we'll just do a search for now and it looks like it's under local so Chrono offset local is where that's going to be totally forgot that's where it is so we'll say Chrono local and then we'll try to go into the what is it offset I think and so let's see let me just pause this really quick so it's actually under the Chrono offset module then we have the local type and there is a Now function on here that we can call so if we do it now we can grab the current date and time with the offset let's go into Chrono offset module local and then there should be now now when we call now on local here it's going to give us a date time but we just want the date so we'll call Dot date naive in order to retrieve just the date and discard the time because generally speaking we want to know the day that somebody got married we don't necessarily care about the specific time right of course maybe you do and so in that case you could just change the data type to a naive date time instead of just the naive date all right so now we've got this new marriage constructed here now we need to go ahead and just return that back from the add function to satisfy this return type so what we'll do is just say it return new marriage and that should automatically satisfy the code that we've already written down here so as you can see we're no longer getting this error from the compiler saying that it doesn't know how to add a person to a person because we are able to accomplish this so if we go ahead and say print line and then we'll plug in and say x got married to Y on X date all right so on Z date I should say so we have X Y and Z as three inputs here so now we're going to reference the marriage dot husband dot first name and then we'll do marriage dot wife DOT first name and then we'll do marriage dot date as the final argument here and of course I need an exclamation station point there because this is a macro so this is how we can basically plug in some variables from this marriage or some field values I should say from this marriage data structure we're going into the marriage we're grabbing the husband which is a unique person we're grabbing the wife which is also a unique person and then we're just grabbing the first name fields and then finally we're just grabbing the date field which is this Chrono naive date object so let's go ahead and run this and as it as you can see we get Trevor got married to Nancy on 2023.09 19. and of course if you wanted to customize the date with something different rather than hard coding whatever now or today is then you could specify that as some kind of argument to this as well or maybe just go ahead and modify it after the fact if you make it mutable here you could go into marriage and say marriage.date equals some other date and time or just date rather all right so that is a simple example of how we can add a person to another person now let's take a look at another example and we're going to use a grocery bill as our example so if you go to the grocery store you know you load up your cart with a bunch of items maybe some cottage cheese cans of peaches maybe some carrots maybe some corn cabbage that kind of thing let's say that we want to create a grocery bill right so you walk up to the checkout with your cart of items and you basically want to create a bill and you want to add all of those items to that bill and then you've got a certain tax rate and then you can get your total with the tax rate applied right so in order to implement this we need to kind of represent each item that we purchase from the grocery store and we also need to be able to represent the bill of the total list of items that we have and then we also need to be able to represent the tax rate for that particular bill of course in some cases you might have a unique tax rate for each item depending on what category it's in that kind of thing but we're just going to keep things simple and say that our entire bill has a singular tax rate so in order to implement this particular example we're going to start off by creating a data structure here to represent each item so we'll say a grocery item and then we're going to create another structure for grocery bill and for grocery item here this is kind of our most broken down type and so this is going to be a name of string and then the price for that item is going to be let's say a floating Point 32-bit value so maybe two dollars and 23 cents for a bag of carrots right so now under grocery bill we need to have something like items and this item list would basically be a VEC of type grocery item so this VEC here is going to allow us to have one or more grocery items or zero or more technically in our entire Bill and then let's also do something like tax rate and we'll set our tax rate to a floating Point 32-bit value so if your tax rate is maybe on food like 2.3 percent or 4.7 percent we can represent that as a floating point value so now this is a little bit different because in this case we have a different relationship so instead of having two distinct persons and a marriage that simply points to those two persons now we have individual grocery items but we have a grocery bill type that Aggregates all of those items together and something else that we'll want to do is to implement a calculate total function on the grocery bill so we can basically add all of the items together multiply it by the tax rate and that would be the total for the bill so down here we'll just say impul grocery bill because we want to implement a method on the grocery bill and then we'll do FN calculate total and will take self as an input argument here so that we can call this on the instance of the grocery bill and then basically we just want to return a floating Point 32-bit value that represents the total payment for that particular bill right so we'll go ahead and implement this a little bit later so we'll just do to do right here and now what we can do is start by implementing the functionality to add a grocery item to the grocery bill so essentially what we want to be able to do down here let's say slash grocery example and up here we'll say marriage example and so down under the grocery example we want it to instantiate a new bill right so you walk up to the self-checkout machine as I often do I like self-checkout I don't know about you but I'm a a very firm believer in self-checkout so it's faster for me I can scan the things when I need to and if I need any help I can always talk to somebody right so what we're going to do Under This is say new bill equals a grocery bill and for starters we're going to initialize the grocery bill with a VEC of grocery items and we're just going to say new and then what we're going to do is specify our tax rate and let's just say that the tax rate on groceries is 2.7 now right here I also need a double colon here because when we are instantiating a new VEC and calling the new method on it we need to use this syntax I believe this is Turbo fish syntax here where we specify the generic type that we want as the vectipe I might not even need that since I specify the data type in the data structure and sure enough it is able to infer that this is a Vex of grocery items because that is what we declared up in our grocery bill type right here all right so now we've created this new grocery bill and what we want what we want to do is be able to instantiate certain grocery items and then add those items to the bill so what we'll do is say let new or new item or let's say let carrots to be a little bit more clear in our code here we'll set it to a grocery item we'll give the item a name of bag of carrots one pound dot two string and then for the price we'll just set it to 2.2 US Dollars and of course you could use whatever unit of measure you want to for the currency as well as the product itself probably prefer kilograms instead of pounds but in the US we mainly use pounds so I'm just going to stick with that and then let's say how about uh cheese cottage cheese so we'll set this equal to a grocery item and the name will be cottage cheese 12 ounces and Dot two string and then we'll set the price for that to about 3.4 dollars so what we want to be able to do is say new bill plus here it's plus cheese and that should ultimately add these grocery items to the grocery bill right so the implementation of the add trait is going to be a little bit different when we took a look at how to implement the ad trait with a person and a person right the two operands are our people person one and person two those are both the same data type now in this case we're actually going to take a grocery bill and we want to add a grocery item which is a different structure type right so what we want to do is make sure that when we implement the add method or the add trait I should say for the grocery bill we want to specify the generic type that we're actually adding in this case is going to be a grocery item so we'll say impul add for grocery bill but this time instead of implementing the add trait by itself we're actually going to say import add and then specify that the right hand side type is not a grocery bill we don't want to add a grocery bill to a grocery bill we want to add a grocery item to a grocery bill so we need to specify this generic type grocery item as the type that we're going to call add on so well I shouldn't say that it's actually the type that we're passing in to add because the right hand side of the plus operator is what we're passing in we're actually calling the add method on the gross rebuild type so what we're going to do is implement the type and the type that we're going to get back from this operation when we add an item to a grocery bill we want to get the grocery bill with the newly added item back as the results so the output from this operation is going to be a grocery bill now for our function here function add we want to return a grocery bill back so we're of course going to use self as the output type here and we're going to take the grocery item as input and we're going to add it to the VEC of items in the grocery bill now when we implement the ad trait here something that's important to note is that self which is the grocery bill that's a pointer to the grocery bill itself the instance of it that is going to be an immutable reference so we actually need to take a mutable reference to self because we need to be able to mutate the VEC that is contained by this grocery bill so one of the first things that we need to do is say let mute Bill equals self and so this will allow us to get a mutable reference to the current grocery bill so that we can add items to the VEC that the bill contains so now what we're going to do is take this grocery item that's being passed on the right hand side of the plus operator and we're going to push that onto the existing VEC so if the VEC is empty which is the default value that we started with right down here then we're just going to push that new item onto the VEC and the Vex should now have one item so what we're going to do is come up here and say Bill Dot items so that refers to the VEC of grocery items and then we'll call the push method here on the VEC and all we need to do is pass in the right hand side and then we'll go ahead and return the updated bill so we're just going to return bill as the type here because we need to return the grocery bill back from the add function here to satisfy this particular trait so this right here is the implementation of our add method to take a grocery item as input on the right hand side and be able to push that onto the VEC of items for the grocery bill which is this field right here so we need to get a mutable reference we add or push the new grocery item onto the VEC and then finally we just return the updated bill so now what we need to do is to capture the result of this operation right here so in order of operations right here what's going to happen is we're going to take the grocery bill in the new bill variable here and that's going to need to be mutable because we need to be able to alter the state of the VEC and then we want to add carrots which is a grocery item to the new bill so this right here is going to be one operation and then the next time that we call the operation again we're going to have a bill resulting from this left-hand side operation right here so then we're going to take this new bill that we get and we're going to call plus again on the bill and we're going to add the cheese which is another grocery item to that bill and you can do that as many times as you want to fill up that VEC and then at the end we'll calculate the total including tax by multiplying the tax value on it so we'll go ahead and say Bill equals new bill actually we'll say new bill equals new bill plus carrots plus cheese and then after we update the new bill we're going to do new Bill Dot calculate total and so now we need to do is actually implement the code for calculate total here to take the value of all of the items multiply that by the tax rate and then add the tax to the final value so what we're going to do here is just Implement a simple function and we'll take all of the items from the VEC so we'll say let total let's say items total equal self dot items so that's the VEC then we're going to grab an iterator from the vac and I've got another video that talks about iterators in a fair amount of depth and one of the functions that we cover in there is the fold function and so I'm going to start with an accumulator value of 0 as a floating Point 32-bit number because we're going to add each price from each of these items which is a floating Point 32-bit value to the accumulator and then at the very end of the fold function that accumulator value is returned back to this variable right here called items total so that'll give us the total of the items before tax so in here we need to specify a closure that takes the current accumulator value and the current item that we're iterating across and all we're going to do is say return a which is the current accumulator value that starts at zero and then add the current item dot price to that accumulator and then the final result is going to be items total right here then what we want to do is say let tax value equal the items total and multiply that by the percentage of our tax rate which in this case is going to be 5.5 now in this particular example instead of specifying a percentage as this 2.7 value here so that I don't have to convert 2.7 as a number to a percentage I'm actually just going to hard code the percentage right here so I'm going to say 0.027 which is 2.7 percent represented as a floating point value so then what we'll do is just multiply items total by self dot tax rate and that should give us the final tax value so then all we have to do is take the total of the items plus the value of the tax and return that as a floating Point 32-bit value so we'll just say return items total plus the tax value and that's going to give us our result for calculate total here so now what we'll do is say let total equal the results from calculate total and we'll say print line the total of your grocery bill is and we'll put a placeholder in there actually we'll just do total and close it off with a curly brace so just to review really quickly we have a new bill being created here with an empty VEC of grocery items and a tax rate of 2.7 percent we have two grocery items here we take the bill and we add those grocery items by overloading the add operator in the implementation for add of type grocery item right up here and then we calculate the results by implementing this custom calculate total function on our grocery bill type and then we simply return the results so let's go ahead and run this and as you can see the total of your grocery bill is 5.7512 of course you could use math rounding to get the value down to 5.75 so that you only have two decimal points of precision but for now we're just going to leave it as is so the key learning points from this video are that you can overload operators by using the add trait subtrate Mall trait div trait and REM trait for modulo and that's going to allow you to basically override the operator for your own custom data structures in Rust so if you want to be able to add items together this is how you would do it and feel free to follow the same similar pattern here for the other traits that we reviewed over in the Ops module somewhere right over here so in any case the documentation here for rust by example doesn't show all of them it only shows add but again the traits are going to be pretty similar to each other so just feel free to take a look at the documentation for things like sub and sub is basically the same thing where you just have a right hand side as a an optional generic you don't have to use the generic type but if you want to add a different type to whatever type you're implementing the add or subtract traits on then you do need to specify the generic type and then it just takes the right hand side value right you have your left hand and your right hand side operators operands I should say you've got the plus sign or subtract or multiply or divide as the operator and then whatever object is going to result from that is going to be the data type that you declare as the output type for that trait implementation so feel free to play around with these traits but this is how you can do operator overloading in the rust programming language thank you so much for watching again please leave a like on this video If you learned something new or if you just enjoyed the presentation also please subscribe to the channel I'm an independent to content creator so your support is very meaningful to me and then also please leave a comment down below and let me know what you thought of this video thanks and we'll see you in the next video take care
Info
Channel: Trevor Sullivan
Views: 1,492
Rating: undefined out of 5
Keywords: rust, rustlang, rust developer, rust programming, rust software, software, open source software, systems programming, data structures, rust structs, rust enums, rust coding, rust development, rustlang tutorial, rust videos, rust programming tutorial, getting started with rust, beginner with rust programming, rust concepts
Id: hfftA0FlaRY
Channel Id: undefined
Length: 36min 42sec (2202 seconds)
Published: Tue Sep 19 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.