Twitch

Lesson Description
The "Pointers & Struct Methods" Lesson is part of the full, Complete Go for Professional Developers course featured in this preview video. Here's what you'd learn in this lesson:
Melkey explains the difference between passing a variable by value and by reference. When a variable is passed by value, a copy of the variable is passed, and the original value will not be changed if the copy is changed. When passed by reference, any mutations are made on the original referenced variable.
Preview
CloseTranscript from the "Pointers & Struct Methods" Lesson
>> Melkey: We have a few of these trucks now we have person, contact, employee. We're gonna shift our focus on the person struct, because something I wanted to address is this topic of passing values by reference or copies of each other, right? So let's say we have this function called func modifyPersonName, okay?
And let's say this function is going to accept a person type, so again we can just do person and actually type in our person type here. Because now person if we look at a definition, it's a type, it's a shrug, it's fully something that we can use and define, right?
This function's not going to return anything because we're gonna try and modify a value. So here, what I'm going to write, let me just make some more room here. Let's do fmt.Println "name before: " and we can access the property of our struct. We can do person.Name, like so, all right?
Here I'm gonna do modifyPersonName, I'm gonna pass my person that we declared. And over here, what I'm gonna do is person.Name, we'll set this to Melkey. All right, we can put fmt.Println, inside scope, new name. You've got person.Name, just to demonstrate that we did, in fact, modify something in our code here and then above after we call the function in our parent function, the main function, we say name after, okay?
So if I run this, you can see name before is John inside scope, new name is Melkey, name after is John, okay? Because what's happening here is the person that we've passed in our modifyPersonName function is a copy of the actual person that we declare all the way up here, okay?
So when we change the property name of the copied person, this only exists in the scope of our function. Once the function is executed, this variable, this person copy is deleted, it is no longer there and that memory is then reallocated, or it's freed up, if you want to pass a value by reference, right?
So if you actually want to call the address and modify the address of a variable, what we can do is change the type of modifyPersonName to accept a pointer to person. This is the Syntax for pointers, it is this asterisk here, and this basically says this function accepts person, but the type is a pointer to person, okay?
And then we can see here we cannot use variable of type person as pointer, person, because we need to pass in the address of our person variable. And to do this, I believe this is the ampersand symbol, if I'm not mistaken, and this directly is the address of our person, okay?
So now, because person here is a pointer to this person variable that we've created. Once we modify person.Name, we can simply go back, right? If I save this now, from this now, the value persists, because we are no longer creating a copy of person that lives within the scope of the function, okay?
And a quick, pretty simple example, I guess, to demonstrate this pointer concept, I'll make it brief, we could do x as like 20, right? We're gonna use infer type, we know x is 20, then we could do pointer, and we can do the address of x, okay? Now we have these two variables, we have x and we have a pointer 2x.
If we do a fmt.Printf, we can say value of x is the only modulo d, okay? And address of x is gonna be modulo p, let's break the line and then pass in x and then pass in the pointer, all right? And if I save this and I run it, you can see here, value of x is 20, and the address, it's this address here.
If we want to then modify this value, we can de reference the value from our pointer using the following notation. We can use asterisk, pointer, ptr, so the name of the pointer right here that points to the address of where x was declared, and we can reset this to 30, okay?
So here, if I just copy this print statement, okay? We have two, I'll do value of x and value of new x, and if I save this, you can see now we have modified the underlying value of where a pointer is pointing to. It's kinda a tough sentence, but yeah, we have the ability to create a variable.
We have the ability to create a pointer to the exact address of set variable, and then we have the ability to dereference, to extract the value associated with that memory address and a modify, like so.
>> Speaker 2: Does it go let you change pointers like where they're pointing to, could you seg fault and go, or is that not possible?
>> Melkey: No, it can't change the underlying address of a pointer, now before we wrap up, there's one more thing I want to, showcase. So I'm gonna focus on this function here, this function, if you remember earlier when we had the question about structs and passing structs. If we look at this function, we are passing person, struct for this function, which, okay, not the end of the world. But structs give us a very cool ability, which is to create methods on them, method receivers or methods on the actual structure itself. Now, just as a quick refresher, we have this person struct that's defined. We're good, we know what that is, we can add fields if you want, and can access them, we should be familiar. Here now, what I'm going to do is here, right after the func keyword, I'm gonna do parentheses, and I'm gonna put person *Person, okay? And I'm gonna change this like so, now, what have I done? I have specifically said modifyPersonName is now a method on my person struct type, okay? So you can see on line 11 or relative 11, I get this error, undefined name or undefined, modifyPersonName. This function no longer exists because in the global scope of our app, modifyPersonName no longer exists, modifyPersonName, the function only exists on our person struct. So what you can do is modify this, let's remove the argument, and here we have person, we can do person.modifyPersonName, do we see that? So instead of calling this function free-range, it's now a method on our person struct. So every new variable we create, new object that we create that is a person struct will have access to the modifyPersonName function to it, okay? And some syntax, some just sugar here, typically you wouldn't see person, person, you do something like p, so now you have p.Name and like p.Name here. Instead of doing the full person and then person type, you do something like P or A, it's like a letter. And this component here, this like bracket component is called a method receiver, okay? This means, we have a method called the function name, and this is only existing on the data type, which is person. And we can, be a little bit more, I don't know, and just change this like this, right?
>> Melkey: Just to be a little bit more pragmatic, I suppose, and then all the things should work as intended.
>> Speaker 3: How carefully should I consider when to pass by copy or a reference?
>> Melkey: Yeah, great question, so how carefully should you consider it? I mean, you obviously don't want to re look at your entire code and re architecture anything. I'd say a rule of thumb is, if you're passing structs along your application through multiple different functions, multiple different fields over and over again, that would be like a bit of a smell. I'd probably start reconsidering maybe these should be methods on the struct instead. Maybe I should be doing a pointer reference to the struct as opposed to just handling it as a copy. And like one gotcha that I tried to explain earlier is the scope of function, like the closures of a function, right? If you are intending to modify a value outside, like if you make a helper function and that function has a specific role of modifying a value. And you call that function, you pass it a copy, then the value isn't gonna persist, right? Nothing's gonna really happen, that's when you would use a pointer. If you want to persist changes, that would be a use case of why you would use references over passing values as copies. Yeah, and just one little thing here, this is intended, you cannot have two methods, obvious, but you can have two of the same methods on the same struct.
>> Speaker 2: Even if they have a different signature, like if you had a different type or a different number of types, would that be allowed?
>> Melkey: As arguments or as the method receiver?
>> Speaker 2: Maybe I misunderstand, you have name string there.
>> Melkey: Yeah, let's just do something like that, right?
>> Speaker 3: Like overloads and-
>> Speaker 2: Yeah, overloading as opposed to overriding.
>> Melkey: Hold on, it's too strange, even if you change like the type, let me just be a little more specific here. You need to change the method signature, it still would error out cuz it still looks at the actual name implementation. You can't share it, if you change this to person 2, and you have this method receivers, person 2, that's gonna be fine.
>> Speaker 2: Right.
>> Melkey: Yeah.
>> Speaker 4: Does the capitalization rule apply to the fields as well.
>> Melkey: Yeah, yes it does, so if you have two functions, let's have modified person and modified person age, for example. And one of them is declared with a lowercase m, where this one is declared with a capital M. The capital M would be exported through other packages where the lowercase m will not be, it will only be within the scope of the package.
>> Speaker 2: Does it go let you change pointers like where they're pointing to, could you seg fault and go, or is that not possible?
>> Melkey: No, it can't change the underlying address of a pointer, now before we wrap up, there's one more thing I want to, showcase. So I'm gonna focus on this function here, this function, if you remember earlier when we had the question about structs and passing structs. If we look at this function, we are passing person, struct for this function, which, okay, not the end of the world. But structs give us a very cool ability, which is to create methods on them, method receivers or methods on the actual structure itself. Now, just as a quick refresher, we have this person struct that's defined. We're good, we know what that is, we can add fields if you want, and can access them, we should be familiar. Here now, what I'm going to do is here, right after the func keyword, I'm gonna do parentheses, and I'm gonna put person *Person, okay? And I'm gonna change this like so, now, what have I done? I have specifically said modifyPersonName is now a method on my person struct type, okay? So you can see on line 11 or relative 11, I get this error, undefined name or undefined, modifyPersonName. This function no longer exists because in the global scope of our app, modifyPersonName no longer exists, modifyPersonName, the function only exists on our person struct. So what you can do is modify this, let's remove the argument, and here we have person, we can do person.modifyPersonName, do we see that? So instead of calling this function free-range, it's now a method on our person struct. So every new variable we create, new object that we create that is a person struct will have access to the modifyPersonName function to it, okay? And some syntax, some just sugar here, typically you wouldn't see person, person, you do something like p, so now you have p.Name and like p.Name here. Instead of doing the full person and then person type, you do something like P or A, it's like a letter. And this component here, this like bracket component is called a method receiver, okay? This means, we have a method called the function name, and this is only existing on the data type, which is person. And we can, be a little bit more, I don't know, and just change this like this, right?
>> Melkey: Just to be a little bit more pragmatic, I suppose, and then all the things should work as intended.
>> Speaker 3: How carefully should I consider when to pass by copy or a reference?
>> Melkey: Yeah, great question, so how carefully should you consider it? I mean, you obviously don't want to re look at your entire code and re architecture anything. I'd say a rule of thumb is, if you're passing structs along your application through multiple different functions, multiple different fields over and over again, that would be like a bit of a smell. I'd probably start reconsidering maybe these should be methods on the struct instead. Maybe I should be doing a pointer reference to the struct as opposed to just handling it as a copy. And like one gotcha that I tried to explain earlier is the scope of function, like the closures of a function, right? If you are intending to modify a value outside, like if you make a helper function and that function has a specific role of modifying a value. And you call that function, you pass it a copy, then the value isn't gonna persist, right? Nothing's gonna really happen, that's when you would use a pointer. If you want to persist changes, that would be a use case of why you would use references over passing values as copies. Yeah, and just one little thing here, this is intended, you cannot have two methods, obvious, but you can have two of the same methods on the same struct.
>> Speaker 2: Even if they have a different signature, like if you had a different type or a different number of types, would that be allowed?
>> Melkey: As arguments or as the method receiver?
>> Speaker 2: Maybe I misunderstand, you have name string there.
>> Melkey: Yeah, let's just do something like that, right?
>> Speaker 3: Like overloads and-
>> Speaker 2: Yeah, overloading as opposed to overriding.
>> Melkey: Hold on, it's too strange, even if you change like the type, let me just be a little more specific here. You need to change the method signature, it still would error out cuz it still looks at the actual name implementation. You can't share it, if you change this to person 2, and you have this method receivers, person 2, that's gonna be fine.
>> Speaker 2: Right.
>> Melkey: Yeah.
>> Speaker 4: Does the capitalization rule apply to the fields as well.
>> Melkey: Yeah, yes it does, so if you have two functions, let's have modified person and modified person age, for example. And one of them is declared with a lowercase m, where this one is declared with a capital M. The capital M would be exported through other packages where the lowercase m will not be, it will only be within the scope of the package.
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops