Tech Stack Walkthrough

Introduction to Golang Part 2

Summary

In this video, I explain the difference between using uppercase and lowercase names in Go and how they determine the public or private visibility of functions and variables. I demonstrate this through an example, where I create an authentication package with two functions. The video highlights how exporting functions creates a contract with other packages, allowing them to use certain functions. The focus is on how visibility is managed within Go packages for better code organization.

Transcript

You might have noticed that sometimes you use uppercase names and other times they use lowercase names. This is not really by accident because this is how you export something or make something either public or private in Go. This is mostly related to packages and we will touch upon packages soon. But for now, let me just do a quick example so you can see what I mean. So if you create something, let's say we have authentication package, right? So we create a new directory called auth. We go into auth and then we say, let's just create auth.go. We create a package called auth and then let's create two functions, right? We create one that's called authenticate me. And in here, we can also have like functionality that we only want to be available in this package, right? So whenever you export something from a package, you are essentially creating a contract with all the other packages saying like, hey, you can use this if you want to and we don't really want to break that. So let's say we have another one that's in charge of validating, I don't know, passwords. So validate password, right? Now, in this case, the authenticate me we can use throughout our program. We just need to import the auth package and we can use this function. But validate password will stay hidden unless you're inside the auth packages. So if we go back to main.go here, we should be able to say auth right there. And you can see we can only import the authenticate me because it's exported, it's uppercased, right? So this is how you mainly manage private and public functions in Go. Go is not an object-oriented language and does not have inheritance. It uses compositions where you combine structs to reflect a larger structure. This could be that we have, let's say, a wheel, a chassis, some brakes, structures that you didn't combine into a larger structure, a car in this case. To create a structure, or as they're known in Go, a struct, you use the type keyword, then the name of the struct, then the struct keyword, and then brackets where you can specify the fields of your struct. A simple user struct could look something like this. So let's jump down here and say type user and then struct. And then you can define the fields that this user structure has, right? And let's just say it has for now an ID field that is a UUID. And as I mentioned, Go does not have inheritance, it has composition. So let's say that we are creating a banking, some financial system. So we might have the concept of a user, right? And then a concept of an account. And this account would have also an ID. And let's say it has a balance as well, that's an int64. And to reflect this in code, that a user and account belongs together somehow, we might also have the concept of a wallet. So this wallet would again have an ID, and then it would have a user and an account associated with it, right? Now, since we don't have inheritance, account and user does not know anything about wallet. It's only wallet who knows about account and user. And we can define methods sort of similar as you would in object-oriented programming, where they have like the typical example is you have, what, a dog class that has the method bug or something like that. We can do the same thing in Go. So by saying, instead of type, say, func, point it to wallet. And let's say we have a method called withdraw. And it just returns some integer amount, right? Now, to do this, we will say return wallet. We do it from account. And we just return the account balance. And then say amount is also int64. So we just return this. This is not really realistic, right? You need to update the balance. You need to check if you have enough funds and all of those kind of things. But essentially, this is how you define a method. Now, account and user does not know anything about wallet. So they would not be able to access withdraw. It's only available through wallet. And we need to have a user and account before we can create a wallet struct. So this is how you build structures and how you reflect whatever your domain, your business area revolves with in Go. Go uses duck typing. And what this means is that if some structure has certain type of behavior, things like methods, like on a wallet struct, it can be described by that behavior. It is called that because of the phrase, if it quacked like a duck, walks like a duck, it's probably a duck. If you take our wallet from earlier, we might have a scenario where we have many different types of wallets. But if we were to do a transaction, like withdraw money from the wallet, we're only really interested in the fact that the wallet have a method that's able to withdraw funds. So we can describe that using an interface. We can describe this in code by creating an interface. And this is very similar to how you create structs. You provide the type keyword and the name of the interface, and then the interface keyword. You typically see interfaces in Go ending on ER to indicate behavior. So if you were to look up in the standard library, you will see something like the stringer interface or the iowriter interface. But this is only a convention. It's not a requirement, so we can call them whatever we want. And for our withdraw interface, we only want the withdraw method. So now that anything that has this method is in the eyes of Go and the compiler, our withdraw, right? And this becomes very handy because we like right now we only have one type of wallet, but let's say we have a wallet of type. Let's say we have a Visa wallet, right? Now, if we were to implement withdraw on the Visa wallet, we could just say that it's a withdrawer, so that we have a method function. There's a function called send money. It takes in an amount. That's an int. It's four. And then it takes a wallet that has the withdraw interface on it, and it doesn't care at all what type of wallet. So now, since the wallet up here has the method withdraw, we can pass that to the send money function, but we cannot pass the Visa wallet since it does not have the withdraw function. And if we were to go up and let's here create a wallet that is just our normal wallet, right? And let's ignore the... We actually don't have a return value for send money, so let's just send money. I'm going to send a thousand or whatever money we have, and then we pass the wallet. No issue. But if we say Visa wallets, and it's the Visa wallet, we will now have an error because the Visa wallet does not have the withdraw method. If we were to add it, so we can just do like this and do it on the Visa wallet, our error now goes away because it has this method. So this is how you define interfaces in Golang. It's tempting when learning to use interfaces to use them all the time. However, this will most likely lead to over-engineered spaghetti code unless you have a very deep understanding of the domain you're operating within. As a rule of thumb, these interfaces should be discovered, not created or not thought of beforehand. These are very powerful to create abstractions and polymorphism and should be used with care.

Early Access

$95 $65 USD

Get access

Invoices and receipts available for easy company reimbursement