Golang Conversions - Ints To Strings And Strong Typing
Go is a strongly typed language, which means at any point a developer should know exactly what type of value they are dealing with. For example, if we have a function that prints a string, we can’t just give it an integer and expect it to work. We have to cast it to a string explicitly:
func main() {
num := 5
numString := strconv.Itoa(num)
printString(numString)
}
func printString(s string) {
fmt.Println(s)
}
If we don’t cast the value, the go compiler won’t even let us compile the program.
Dynamic Typing is Slow
Developers coming from dynamically typed languages often get annoyed with Go’s strong typing. They think that the compiler should just know what they mean and do the type cast implicitly. Strongly typed languages won’t guess for you. They make you make the decisions.
There is a reason for this. Type conversions take time and resources. If the Go runtime were to dynamically type every value then programs would run a lot slower in general. If you want slow programs then go back to Python or Javascript.
Strong Typing Is Explicit
In addition to strong typing being faster, strong typing allows the developer to know exactly what type of value they are dealing with. I can’t tell you how many times in Python I’ve had to run a program and print out what type of object something is. (Looking at you NumPy)
Strong Typing Saves Memory
In one of our production apps, we were storing millions of ints in memory. Being on 64-bit machines, this means that we were storing 64 bits for each integer when in reality the integer stored was never greater than 10. By swapping out ints for uint8s we saved 80% of the memory that our application was using. The guy paying our cloud bill was quite happy about that.
While changing int and float types can save memory, beware of these kinds of optimizations. A program can become quite hard to read if every other line is:
toRound := float64(someNumber)
toSave := float32(toRound)
The truth is that most of the standard library functions and popular packages (and hopefully the stuff you write too) uses “default” sizes. For example, math.Round uses float64s and time.AddDate uses ints. Unless the memory savings are significant, it’s usually best to stick with “normal”.
Interfaces - Not Duck Types
Interfaces allow for a kind of polymorphism in Go. Their purpose is not to give developers a way to sneak dynamic typing into the language. I’ve seen developers do things like:
func jsonToDynamic(dat []byte) {
m := map[string]interface{}{}
json.Unmarshal(dat, &m)
// do something with m["hello"]
}
I humbly ask… why? When unmarshalling JSON, 99% of the time we should know the shape of the object. If we know the shape of the object, then we should unmarshal into a struct where each field’s type is declared. We will even get a nice unmarshal error if the shape is malformed.
Related Articles
How To Separate Library Packages in Go
Mar 29, 2020 by Lane Wagner - Boot.dev co-founder and backend engineer
I’ve often seen, and have been responsible for, throwing code into packages without much thought. I’ve quickly drawn a line in the sand and started putting code into different folders (which in Go are different packages by definition) just for the sake of findability. Learning to properly build small and reusable packages can take your Go career to the next level.
I Wrote Go-TinyDate, The Missing Golang Date Package
Mar 23, 2020 by Lane Wagner - Boot.dev co-founder and backend engineer
time.Time makes dealing with dates and times in Go a breeze, and it even comes bundled in the standard library! However, a time.Time{} struct uses more than 24 bytes of memory under most conditions, and I’ve run into situations where I need to store millions of them in memory, but all I really needed was a UTC date! Go-TinyDate solves this with just 4 bytes of memory.
How to Use Mutexes in Go
Mar 19, 2020 by Lane Wagner - Boot.dev co-founder and backend engineer
Golang is King when it comes to concurrency. No other language has so many tools right out of the box, and one of those tools is the standard library’s sync.Mutex{}. Mutexes let us safely control access to data across multiple goroutines.
Best Practices for Interfaces in Go
Mar 15, 2020 by Lane Wagner - Boot.dev co-founder and backend engineer
Interfaces in Go allow us to treat different types as the same data type temporarily because both types implement the same kind of behavior. They’re central to a Go programmer’s toolbelt and are often used improperly by new Go developers, which leads to unreadable and often buggy code.