1 00:00:06,590 --> 00:00:09,640 - Alright, so now that we've learned the guidelines around 2 00:00:09,640 --> 00:00:12,080 when to use value and pointer semantics, 3 00:00:12,080 --> 00:00:14,660 let's go back to that other code I showed 4 00:00:14,660 --> 00:00:18,500 where we were looking at a slice of user values 5 00:00:18,500 --> 00:00:20,960 and a iteration over that. 6 00:00:20,960 --> 00:00:23,500 Now, again, I always wanna create a slice of values 7 00:00:23,500 --> 00:00:26,520 as my core data, and that is the point of truth, 8 00:00:26,520 --> 00:00:30,010 those two values right there inside the slice literal. 9 00:00:30,010 --> 00:00:33,790 But we talked before, for a value of type user, 10 00:00:33,790 --> 00:00:36,460 should we be using value semantic or pointer semantics? 11 00:00:36,460 --> 00:00:39,430 I mean, is it reasonable to make copies of people? 12 00:00:39,430 --> 00:00:41,500 And the answer really, for me, is no. 13 00:00:41,500 --> 00:00:44,690 When we really should be using pointer semantics 14 00:00:44,690 --> 00:00:46,100 for that user value. 15 00:00:46,100 --> 00:00:49,460 I wanna show you how mixing semantics can cause a huge 16 00:00:49,460 --> 00:00:50,770 amount of pain. 17 00:00:50,770 --> 00:00:52,140 Look on line 50. 18 00:00:52,140 --> 00:00:54,560 Notice that I'm using value semantics 19 00:00:54,560 --> 00:00:56,470 for my user collection. 20 00:00:56,470 --> 00:00:59,410 Already that's a smell, we shouldn't be making copies 21 00:00:59,410 --> 00:01:00,690 of users, right? 22 00:01:00,690 --> 00:01:03,080 Because think about it, we're making a copy 23 00:01:03,080 --> 00:01:05,040 of the slice here, which is fine, 24 00:01:05,040 --> 00:01:07,690 but now we're operating on a copy of the data. 25 00:01:07,690 --> 00:01:10,140 On the first iteration, we're not working with this data, 26 00:01:10,140 --> 00:01:11,730 we're working with a copy of this data. 27 00:01:11,730 --> 00:01:12,800 That's what we want when we're dealing 28 00:01:12,800 --> 00:01:15,080 with value semantics, so that's not what's wrong. 29 00:01:15,080 --> 00:01:17,210 What's wrong is we shouldn't be working with a copy at all, 30 00:01:17,210 --> 00:01:20,430 we should be working with Ed directly or Erick directly, 31 00:01:20,430 --> 00:01:23,150 but we're not, and here's the mix. 32 00:01:23,150 --> 00:01:26,890 I'm now using a pointer semantic API against a copy 33 00:01:26,890 --> 00:01:30,030 of the data, and if I run this, guess what? 34 00:01:30,030 --> 00:01:33,760 Ed's email address or Erick's email address isn't going 35 00:01:33,760 --> 00:01:35,760 to change, the copy's going to change. 36 00:01:35,760 --> 00:01:38,510 And this code is very hard to read and reason about. 37 00:01:38,510 --> 00:01:40,430 This is why I cannot stress enough, 38 00:01:40,430 --> 00:01:43,910 we must have semantic consistency throughout the code base 39 00:01:43,910 --> 00:01:46,480 so we don't get into this kind of trouble. 40 00:01:46,480 --> 00:01:50,690 Now, one of the things I wanna make sure you understand is 41 00:01:50,690 --> 00:01:52,880 that methods are really made up. 42 00:01:52,880 --> 00:01:54,260 They really don't exist. 43 00:01:54,260 --> 00:01:57,420 All we have is state and all we have is functions. 44 00:01:57,420 --> 00:02:00,690 Methods give us this syntactic sugar 45 00:02:00,690 --> 00:02:02,980 that a piece of data has behavior. 46 00:02:02,980 --> 00:02:05,480 That's what it's there for, to give us syntactic sugar, 47 00:02:05,480 --> 00:02:08,170 this belief system that data has behavior, 48 00:02:08,170 --> 00:02:09,930 and there are times when it's very important 49 00:02:09,930 --> 00:02:11,360 for data to have behavior. 50 00:02:11,360 --> 00:02:14,370 Again, we've got to learn when should data have behavior 51 00:02:14,370 --> 00:02:17,680 and when it shouldn't, and I want data having behavior 52 00:02:17,680 --> 00:02:19,820 to be the exception, not the rule. 53 00:02:19,820 --> 00:02:22,410 This is one of the things you've got to kinda fight 54 00:02:22,410 --> 00:02:23,670 because you've been trained, 55 00:02:23,670 --> 00:02:25,590 always allow data to have behavior. 56 00:02:25,590 --> 00:02:26,930 No, no, no, no, no, no, no, no. 57 00:02:26,930 --> 00:02:30,910 Functions, for me, are always going to be better choice 58 00:02:30,910 --> 00:02:33,690 when it's reasonable and practical to use them. 59 00:02:33,690 --> 00:02:37,150 So, let's talk about a couple of things here in this code 60 00:02:37,150 --> 00:02:39,050 as we continue to go through it. 61 00:02:39,050 --> 00:02:41,200 I want you to look at this type data. 62 00:02:41,200 --> 00:02:44,290 There it is, data, and data has two acts of behavior, 63 00:02:44,290 --> 00:02:47,400 displayName, which is using our value semantics, 64 00:02:47,400 --> 00:02:50,490 and setAge, which is using our pointer semantics. 65 00:02:50,490 --> 00:02:53,010 The other thing, I wanna mention something really quick. 66 00:02:53,010 --> 00:02:56,270 Setters and getters are not an API. 67 00:02:56,270 --> 00:02:59,520 If I see in a code review any function like this, 68 00:02:59,520 --> 00:03:01,940 or in this case, method like this, called set, 69 00:03:01,940 --> 00:03:03,700 we've gotta have a conversation. 70 00:03:03,700 --> 00:03:06,200 Setters and getters just bloat a code base, 71 00:03:06,200 --> 00:03:09,020 require more testing, and really add no value. 72 00:03:09,020 --> 00:03:11,150 An API really should provide something. 73 00:03:11,150 --> 00:03:14,030 We're gonna talk about that more during design. 74 00:03:14,030 --> 00:03:16,990 But setting and getting is not providing anything, 75 00:03:16,990 --> 00:03:20,140 and I really want to avoid these types of methods. 76 00:03:20,140 --> 00:03:22,040 But here we are, we've got setAge 77 00:03:22,040 --> 00:03:23,280 using our pointer semantics 78 00:03:23,280 --> 00:03:25,620 and displayName using our value semantics. 79 00:03:25,620 --> 00:03:28,150 One of the things I want you to see very quickly, 80 00:03:28,150 --> 00:03:30,850 that is, if I define a new type, 81 00:03:30,850 --> 00:03:33,370 let's just say I even call it Bill, 82 00:03:33,370 --> 00:03:38,370 does this new type Bill have behavior like data does? 83 00:03:38,460 --> 00:03:41,020 Remember, we're saying this new type, 84 00:03:41,020 --> 00:03:44,600 this new name type, Bill, is based on data, 85 00:03:44,600 --> 00:03:47,130 yet I want you to understand that behavior, 86 00:03:47,130 --> 00:03:50,590 there is no behavior associated with Bill data. 87 00:03:50,590 --> 00:03:55,210 All of the behavior is only still associated with data. 88 00:03:55,210 --> 00:03:56,130 Why is this? 89 00:03:56,130 --> 00:03:59,720 One, because these methods are not declared inside the type, 90 00:03:59,720 --> 00:04:02,210 like you would see in a class-based system, 91 00:04:02,210 --> 00:04:04,690 they're declared outside the type. 92 00:04:04,690 --> 00:04:08,300 This is Go, again, separating state from behavior. 93 00:04:08,300 --> 00:04:10,080 We're not really wanting to combine it, 94 00:04:10,080 --> 00:04:11,420 we're keeping it separate. 95 00:04:11,420 --> 00:04:14,620 Your syntax is also keeping it separated. 96 00:04:14,620 --> 00:04:18,250 So, understand that even though Bill is based on data, 97 00:04:18,250 --> 00:04:20,740 the basing is based on its memory model, 98 00:04:20,740 --> 00:04:23,180 not on this idea of behavior. 99 00:04:23,180 --> 00:04:27,810 Okay, so I've got this data type right there. 100 00:04:27,810 --> 00:04:31,380 I've got displayName, I've got setAge, there we are, 101 00:04:31,380 --> 00:04:34,320 and what I've shown you here is how we call methods. 102 00:04:34,320 --> 00:04:35,510 There's nothing new here. 103 00:04:35,510 --> 00:04:37,760 I'm gonna create a value of type data, 104 00:04:37,760 --> 00:04:40,880 there it is on line 29, so nothing new here. 105 00:04:40,880 --> 00:04:42,900 Here's our data value, right? 106 00:04:42,900 --> 00:04:44,820 There it is, I've put Bill inside of it. 107 00:04:44,820 --> 00:04:48,870 Let's do that, there's Bill, and then on line 36, 108 00:04:48,870 --> 00:04:53,870 I called displayName using d, I call setAge using d. 109 00:04:54,210 --> 00:04:56,550 Again, Go will adjust to make the call. 110 00:04:56,550 --> 00:04:58,150 But what I really wanna share with you, 111 00:04:58,150 --> 00:05:01,670 'cause you'll see this in stack traces anyway, 112 00:05:01,670 --> 00:05:04,300 but the reality is that, again, methods are made up, 113 00:05:04,300 --> 00:05:05,700 it's syntactic sugar. 114 00:05:05,700 --> 00:05:07,740 This is giving us a belief system 115 00:05:07,740 --> 00:05:10,650 that d has this behavior displayName, 116 00:05:10,650 --> 00:05:14,020 d has this behavior setAge, but when you call d.displayName 117 00:05:15,220 --> 00:05:20,220 or d.setAge, really what you're doing is calling a function. 118 00:05:20,920 --> 00:05:24,660 That receiver really is defined as a parameter 119 00:05:24,660 --> 00:05:25,840 'cause it really is a parameter. 120 00:05:25,840 --> 00:05:27,710 It's actually the first parameter 121 00:05:27,710 --> 00:05:29,737 to the function displayName. 122 00:05:29,737 --> 00:05:31,790 So, when you call d.displayName, 123 00:05:31,790 --> 00:05:34,110 the compiler's actually gonna make a call 124 00:05:34,110 --> 00:05:37,270 to data.displayName, that's the full function name, 125 00:05:37,270 --> 00:05:41,660 passing the receiver d as the first parameter. 126 00:05:41,660 --> 00:05:44,400 Notice here, when we call setAge, 127 00:05:44,400 --> 00:05:47,890 the name of the function leveraging the pointer semantics 128 00:05:47,890 --> 00:05:52,010 as part of its declaration, *data, parentheses, .setAge, 129 00:05:52,010 --> 00:05:53,850 that's the real name of the function. 130 00:05:53,850 --> 00:05:56,280 And here what you're seeing is Go adjusting 131 00:05:56,280 --> 00:05:57,530 to make the call. 132 00:05:57,530 --> 00:05:59,610 This is why Go can adjust to make the call, 133 00:05:59,610 --> 00:06:02,440 because it's really just data being passed in. 134 00:06:02,440 --> 00:06:04,210 Now, you are never, never, never, ever, 135 00:06:04,210 --> 00:06:06,720 never ever allowed to call a method like this. 136 00:06:06,720 --> 00:06:08,430 Don't do this, okay? 137 00:06:08,430 --> 00:06:11,210 Please, if you do it and you say that Bill showed you this, 138 00:06:11,210 --> 00:06:12,470 I'll throw you under the bus. 139 00:06:12,470 --> 00:06:14,430 I'll deny this is even in the video. 140 00:06:14,430 --> 00:06:15,630 You can't do this, okay? 141 00:06:15,630 --> 00:06:18,670 When we're calling methods, you gotta call methods 142 00:06:18,670 --> 00:06:21,110 with that syntactic sugar. 143 00:06:21,110 --> 00:06:24,420 But if you looked at a call stack on some sort of panic 144 00:06:24,420 --> 00:06:25,970 and you're seeing method calls, 145 00:06:25,970 --> 00:06:27,260 they're gonna look like this. 146 00:06:27,260 --> 00:06:30,810 So, my point here is that methods are syntactic sugar, 147 00:06:30,810 --> 00:06:32,210 they give us this belief system 148 00:06:32,210 --> 00:06:34,020 that a piece of data has behavior. 149 00:06:34,020 --> 00:06:37,680 It does give data behavior, but we really have underneath, 150 00:06:37,680 --> 00:06:39,040 technically, are just functions. 151 00:06:39,040 --> 00:06:40,940 The receiver truly is a parameter, 152 00:06:40,940 --> 00:06:42,790 and it's really the first parameter. 153 00:06:42,790 --> 00:06:46,350 Now, I wanna drive home a little harder now the idea 154 00:06:46,350 --> 00:06:48,630 of value and pointer semantics with this, 155 00:06:48,630 --> 00:06:50,830 with the idea that, if you know your semantic, 156 00:06:50,830 --> 00:06:53,990 if you know what semantic's in play, you know behavior. 157 00:06:53,990 --> 00:06:55,900 This is everything, this is where I've been trying 158 00:06:55,900 --> 00:06:58,800 to get you since we stared this video. 159 00:06:58,800 --> 00:07:00,800 So, let's take a look at this code here. 160 00:07:01,670 --> 00:07:04,740 Now, on line 52, what are we doing? 161 00:07:04,740 --> 00:07:08,200 I'm getting the method displayName, 162 00:07:08,200 --> 00:07:11,170 already bound to d, there it is, already bound to d, 163 00:07:11,170 --> 00:07:12,540 but I'm not making the method call. 164 00:07:12,540 --> 00:07:14,320 Look, there's no parentheses. 165 00:07:14,320 --> 00:07:17,223 I'm assigning it to a variable named f1. 166 00:07:18,900 --> 00:07:23,900 Functions in Go are values, they are literally typed values, 167 00:07:25,346 --> 00:07:27,180 and that means that we can pass a function 168 00:07:27,180 --> 00:07:30,300 by its name anywhere we want in our program 169 00:07:30,300 --> 00:07:32,960 as long as, again, type information is the same, 170 00:07:32,960 --> 00:07:34,410 the function's signature. 171 00:07:34,410 --> 00:07:36,620 But here, I'm creating a variable. 172 00:07:36,620 --> 00:07:40,710 I'm creating a level of indirection, if you think about it, 173 00:07:40,710 --> 00:07:43,770 to be able to access and call displayName. 174 00:07:43,770 --> 00:07:45,627 I'm creating a level of indirection. 175 00:07:45,627 --> 00:07:47,340 I'm gonna be able to make the method call 176 00:07:47,340 --> 00:07:51,460 like you see on line 55 through the variable itself. 177 00:07:51,460 --> 00:07:54,330 But in order for that 55, line 55, 178 00:07:54,330 --> 00:07:58,000 that function call through the variable to work, 179 00:07:58,000 --> 00:07:59,880 we need to set up some things here. 180 00:07:59,880 --> 00:08:04,880 So, f1 is a reference type and it's just a pointer, 181 00:08:04,930 --> 00:08:07,380 but if it's just a pointer, it's gotta point to something. 182 00:08:07,380 --> 00:08:11,460 And it does, it points to a data structure 183 00:08:11,460 --> 00:08:14,070 that is a two-word data structure. 184 00:08:14,070 --> 00:08:19,070 Now, the first word here is gonna point to the code for, 185 00:08:20,520 --> 00:08:22,540 in our case, right now, displayName. 186 00:08:22,540 --> 00:08:25,220 Remember, displayName really is a function. 187 00:08:25,220 --> 00:08:29,610 So, we now have a level of indirection here. 188 00:08:29,610 --> 00:08:31,990 I wanna specify this, this is important. 189 00:08:31,990 --> 00:08:33,670 I have a level of indirection, 190 00:08:33,670 --> 00:08:36,763 two pointers to get to the code, 191 00:08:37,970 --> 00:08:40,190 but remember, I can't call this code, 192 00:08:40,190 --> 00:08:44,400 I can't call displayName without d. 193 00:08:44,400 --> 00:08:47,520 The whole point here is I need d, I need d. 194 00:08:47,520 --> 00:08:49,760 But what d am I using? 195 00:08:49,760 --> 00:08:53,130 This is where the semantics are critical. 196 00:08:53,130 --> 00:08:55,683 Let's go back and look at displayName for a second. 197 00:08:56,610 --> 00:09:01,150 displayName is using value semantics. 198 00:09:01,150 --> 00:09:03,240 Value semantics means that every piece 199 00:09:03,240 --> 00:09:06,680 of code is operating on its own copy of the data. 200 00:09:06,680 --> 00:09:09,180 Value semantics are at play. 201 00:09:09,180 --> 00:09:11,320 Copies of the data. 202 00:09:11,320 --> 00:09:12,530 You know what that means? 203 00:09:12,530 --> 00:09:15,930 It means that this f1 variable is not allowed 204 00:09:15,930 --> 00:09:20,737 to reference d, it has to be given its own copy of d. 205 00:09:23,050 --> 00:09:26,910 This variable always is operating on its own copy 206 00:09:26,910 --> 00:09:28,690 because displayName is making sure 207 00:09:28,690 --> 00:09:31,200 that we're using value semantics. 208 00:09:31,200 --> 00:09:32,530 Guess what, also? 209 00:09:32,530 --> 00:09:36,289 We have indirection here, and any time in Go 210 00:09:36,289 --> 00:09:37,122 where you have indirection, 211 00:09:37,122 --> 00:09:38,710 we're gonna have some interesting 212 00:09:39,600 --> 00:09:41,270 things around escape analysis, 213 00:09:41,270 --> 00:09:45,580 but right now, we've already made a copy, we made a copy. 214 00:09:45,580 --> 00:09:46,910 You know what that means? 215 00:09:46,910 --> 00:09:48,900 Allocation, it has to allocate. 216 00:09:48,900 --> 00:09:50,660 We don't know about this at compile time, 217 00:09:50,660 --> 00:09:52,300 this is happening at runtime. 218 00:09:52,300 --> 00:09:53,890 What you're seeing here at the function 219 00:09:53,890 --> 00:09:58,700 is really our first introduction to decoupling in Go. 220 00:09:58,700 --> 00:10:02,890 f1 is decoupling not only the code we wanna execute, 221 00:10:02,890 --> 00:10:05,400 but the data we need to execute against it. 222 00:10:05,400 --> 00:10:09,520 And since we're making a copy of d, there's an allocation. 223 00:10:09,520 --> 00:10:12,960 Okay, great, so now when we call f1 on line 55, 224 00:10:12,960 --> 00:10:15,330 we have an indirect line to the code 225 00:10:15,330 --> 00:10:18,420 and we know what data, so therefore on line 58, 226 00:10:18,420 --> 00:10:21,360 if I make a change to the data here, 227 00:10:21,360 --> 00:10:25,790 if I go, okay, you're no longer Bill anymore, you're Joan, 228 00:10:25,790 --> 00:10:27,840 are we gonna see that change? 229 00:10:27,840 --> 00:10:30,540 Absolutely not, we're not operating on this d, 230 00:10:30,540 --> 00:10:32,700 we're operating on our own copy, why? 231 00:10:32,700 --> 00:10:34,230 Value semantics were at play. 232 00:10:34,230 --> 00:10:36,050 Why was value semantics at play? 233 00:10:36,050 --> 00:10:39,370 Because displayName uses value semantics on 234 00:10:39,370 --> 00:10:40,820 the receiver choice. 235 00:10:40,820 --> 00:10:43,460 Okay, great, let's do this again. 236 00:10:43,460 --> 00:10:44,970 Look at this code here. 237 00:10:44,970 --> 00:10:48,223 Now we're gonna leverage setAge into a variable named f2. 238 00:10:49,180 --> 00:10:50,380 Okay, no problem. 239 00:10:50,380 --> 00:10:53,370 Here's f2, right here, we know it's a reference type, 240 00:10:53,370 --> 00:10:54,240 it's a pointer. 241 00:10:54,240 --> 00:10:57,350 It also is going to have its own internal data structure. 242 00:10:57,350 --> 00:11:00,620 We know that this is gonna point to the code for setAge. 243 00:11:00,620 --> 00:11:03,650 We've got that, we've got our double indirection, 244 00:11:03,650 --> 00:11:06,243 which is giving us our levels of decoupling. 245 00:11:07,460 --> 00:11:10,650 But what semantic is at play for setAge? 246 00:11:10,650 --> 00:11:15,650 Well, we know that setAge is using a pointer receiver, 247 00:11:15,800 --> 00:11:17,520 pointer semantics. 248 00:11:17,520 --> 00:11:21,140 Sharing the data, not making a copy. 249 00:11:21,140 --> 00:11:22,160 What does that mean? 250 00:11:22,160 --> 00:11:24,130 It means that now this pointer doesn't point 251 00:11:24,130 --> 00:11:28,323 to its own copy, this pointer points to the original, 252 00:11:29,667 --> 00:11:31,530 original, so when we use f2 253 00:11:31,530 --> 00:11:34,220 to make the method call on line 73, 254 00:11:34,220 --> 00:11:36,140 we're operating against the original data 255 00:11:36,140 --> 00:11:39,040 that's been shared, which means on line 76, 256 00:11:39,040 --> 00:11:42,740 if we change Joan to Sammy, guess what? 257 00:11:42,740 --> 00:11:46,760 We see the change, pointer semantics are at play. 258 00:11:46,760 --> 00:11:49,450 Now, this is something that's very interesting. 259 00:11:49,450 --> 00:11:53,490 We have double indirection again to get to the data, 260 00:11:53,490 --> 00:11:55,790 and the escape analysis algorithm has 261 00:11:55,790 --> 00:11:57,930 what we would call a flaw in that, 262 00:11:57,930 --> 00:12:01,780 once we have indirection like this, double indirection, 263 00:12:01,780 --> 00:12:05,130 the escape analysis algorithms can't track whether 264 00:12:05,130 --> 00:12:07,950 or not this value can stay on a stack or not. 265 00:12:07,950 --> 00:12:09,890 In other words, even though there's no reason 266 00:12:09,890 --> 00:12:14,010 for d to end up on the heap, it has to now allocate. 267 00:12:14,010 --> 00:12:17,740 So, there's a cost to decoupling in Go, 268 00:12:17,740 --> 00:12:20,510 and it's really important, we're gonna continue to say this. 269 00:12:20,510 --> 00:12:25,400 When you decouple a piece of concrete data in Go, 270 00:12:25,400 --> 00:12:27,600 you're going to have the cost of indirection 271 00:12:28,530 --> 00:12:30,090 and allocation. 272 00:12:30,090 --> 00:12:33,970 There's no way around it right now, no way around it. 273 00:12:33,970 --> 00:12:37,120 And so, we have to make sure that if we're decoupling, 274 00:12:37,120 --> 00:12:40,230 that the cost of indirection and allocation is worth it. 275 00:12:40,230 --> 00:12:43,900 We do not wanna blindly decouple code in Go. 276 00:12:43,900 --> 00:12:46,000 We wanna do it because it's the right thing to do, 277 00:12:46,000 --> 00:12:47,580 it's the right engineering choice. 278 00:12:47,580 --> 00:12:49,310 It's reasonable and practical. 279 00:12:49,310 --> 00:12:51,680 Because this cost, we know, is great. 280 00:12:51,680 --> 00:12:54,950 Remember, this is number two on our lack 281 00:12:54,950 --> 00:12:57,410 of performance list in our programs. 282 00:12:57,410 --> 00:13:00,600 Allocation is number two, so we don't wanna take these 283 00:13:00,600 --> 00:13:05,220 allocations lightly, but if the decoupling is adding value, 284 00:13:05,220 --> 00:13:08,500 then I'll take this cost all day long 285 00:13:08,500 --> 00:13:10,740 'cause if I can make sure that I can minimize 286 00:13:10,740 --> 00:13:12,890 cascading changes throughout a code base, 287 00:13:12,890 --> 00:13:14,920 if I can make sure that I can work 288 00:13:14,920 --> 00:13:17,600 with different pieces of concrete data 289 00:13:17,600 --> 00:13:19,870 without blowing up a code base, 290 00:13:19,870 --> 00:13:22,480 well, I'll take the cost of allocation all day long. 291 00:13:22,480 --> 00:13:23,880 Then it's worth it. 292 00:13:23,880 --> 00:13:28,740 Alright, so our methods are giving us the ability 293 00:13:28,740 --> 00:13:32,640 to give a piece of data behavior. 294 00:13:32,640 --> 00:13:34,500 We still have to learn, when is it reasonable 295 00:13:34,500 --> 00:13:36,370 and practical for data to have behavior? 296 00:13:36,370 --> 00:13:39,473 I want this to be the exception and not the rule.