https://www.gopherguides.com/articles/leveraging-the-go-type-system [logo] * Virtual go training * Courses + Intro + Advanced + Testing + Optimization + Web API + gRPC + Mastering + Custom + My Courses * Team * Blog * Contact * Login [cory-lanou] Cory LaNou Wed, 03 Feb 2021 Leveraging the Go Type System If you haven't worked in a typed language before, it may not be obvious at first the power that it brings. This article will show you how to leverage the type system to make your code easier to use and more reusable. Target Audience This article is aimed at developers that are new to Go and have little to no Go experience. The Problem For this article, we will look at how to handle categorical data. In this case, specifically how to handle the genre category for classifying a book. To start, we'll define a data structure for a Book , in which we'll want to categorize it via the genre : package books type Book struct { ID int Name string Genre string } Now that we have the book defined, let's go ahead and define some constants for genre : const ( Adventure = "Adventure" Comic = "Comic" Crime = "Crime" Fiction = "Fiction" Fantasy = "Fantasy" Historical = "Historical" Horror = "Horror" Magic = "Magic" Mystery = "Mystery" Philosophical = "Philosophical" Political = "Political" Romance = "Romance" Science = "Science" Superhero = "Superhero" Thriller = "Thriller" Western = "Western" ) So far, this seems fine. However, the genre constants are strings. While this makes for a very "humanized" way of reading the code, it's not very efficient as it pertains to a computer program. Strings will take up more storage space, and more memory in the program (not to mention if we stored millions of data records to a database). As such, we really want to use a smaller data type to represent this data. In Go, one way we can do this is to create constants that are based on the int type. const ( Adventure = 1 Comic = 2 Crime = 3 Fiction = 4 Fantasy = 5 Historical = 6 Horror = 7 Magic = 8 Mystery = 9 Philosophical = 10 Political = 11 Romance = 12 Science = 13 Superhero = 14 Thriller = 15 Western = 16 ) We also need to change the Book structure to now represent Genre as an int: type Book struct { ID int Name string Genre int } While we now have a more effecient memory model for Genre , it's not as "human" friendly. If I print out the value of a Book , we now just get an integer value. To show this, we'll write a quick test showing the output: package books import ( "testing" ) func TestGenre(t *testing.T) { b := Book{ ID: 1, Name: "All About Go", Genre: Magic, } t.Logf("%+v\n", b) if got, exp := b.Genre, 8; got != exp { t.Errorf("unexpected genre. got %d, exp %d", got, exp) } } And here is the output. $ go test -v ./... === RUN TestGenre books_test.go:14: {ID:1 Name:All About Go Genre:8} --- PASS: TestGenre (0.00s) PASS ok github.com/gopherguides/corp/_blog/types/leveraging-types/src/v2 (cached) Notice that the Genre just shows a value of 8 . Any time we debug the code, or write a report, etc, we now need to figure out what 8 actually represents for a human being. To do this, we can write a helper function that takes the Genre value, and determines what the "human" representation should be: func GenreToString(i int) string { switch i { case 1: return "Adventure" case 2: return "Comic" case 3: return "Crime" case 4: return "Fiction" case 5: return "Fantasy" case 6: return "Historical" case 7: return "Horror" case 8: return "Magic" case 9: return "Mystery" case 10: return "Philosophical" case 11: return "Political" case 12: return "Romance" case 13: return "Science" case 14: return "Superhero" case 15: return "Thriller" case 16: return "Western" default: return "" } } A Better Way While all the above code works fine, it's really missing some key points. * If a value for a Genre has to change in the future, we not only have to change the constant value, but we also have to update the GenreToString function. If we don't, this will create a bug in our code. * We aren't leveraging the type system to encapsulate this behavior for Genre . We'll show you what we mean by that shortly. The first thing we really need to do is write a more resilient GenreToString function. What we mean by resilient is that even if the value of the Genre constant changes in the future, the GenreToString function will not need to change. The correct way to do that is no longer use hard coded values, but use the value of the constant themselves: func GenreToString(i int) string { switch i { case Adventure: return "Adventure" case Comic: return "Comic" case Crime: return "Crime" case Fiction: return "Fiction" case Fantasy: return "Fantasy" case Historical: return "Historical" case Horror: return "Horror" case Magic: return "Magic" case Mystery: return "Mystery" case Philosophical: return "Philosophical" case Political: return "Political" case Romance: return "Romance" case Science: return "Science" case Superhero: return "Superhero" case Thriller: return "Thriller" case Western: return "Western" default: return "" } } Ok, that's much cleaner (and readable), but we still haven't solved the fact that when we print it out, it shows a data value ( int ), and not a "human" readable value. Types to the Rescue Instead of using a generic int type for Genre , we can create our own type based on an existing type. In this case, we'll create a new type called Genre based on the int type: type Genre int Now, we'll define our constants as Genre types: const ( Adventure Genre = 1 Comic Genre = 2 Crime Genre = 3 Fiction Genre = 4 Fantasy Genre = 5 Historical Genre = 6 Horror Genre = 7 Magic Genre = 8 Mystery Genre = 9 Philosophical Genre = 10 Political Genre = 11 Romance Genre = 12 Science Genre = 13 Superhero Genre = 14 Thriller Genre = 15 Western Genre = 16 ) So far, the code doesn't really feel different. However, now that Genre is it's own type, we can add methods to it. This allows us to encapsulate the "human" behavior we want to the type, and not as a generic function. To do this, we'll add a String method to the Genre type: func (g Genre) String() string { switch g { case Adventure: return "Adventure" case Comic: return "Comic" case Crime: return "Crime" case Fiction: return "Fiction" case Fantasy: return "Fantasy" case Historical: return "Historical" case Horror: return "Horror" case Magic: return "Magic" case Mystery: return "Mystery" case Philosophical: return "Philosophical" case Political: return "Political" case Romance: return "Romance" case Science: return "Science" case Superhero: return "Superhero" case Thriller: return "Thriller" case Western: return "Western" default: return "" } } Now, we'll be able to use the String method when we want to see what the "human" value of a Genre is: b := Book{ ID: 1, Name: "All About Go", Genre: Magic, } fmt.Println(b.Genre.String()) Output: Magic Magic Formatting In Go, if you add a String method to any type, the fmt package will now use your String method to "pretty print" the representation of your type. Because of this, we will now see that if we print out the book in our tests, we get a "human-readable" Genre as well: func TestGenre(t *testing.T) { b := Book{ ID: 1, Name: "All About Go", Genre: Magic, } t.Logf("%+v\n", b) if got, exp := b.Genre.String(), "Magic"; got != exp { t.Errorf("unexpected genre. got %q, exp %q", got, exp) } } Output: $ go test -v -run=TestGenre -count=1 . === RUN TestGenre books_test.go:16: {ID:1 Name:All About Go Genre:Magic} --- PASS: TestGenre (0.00s) PASS ok book 0.059s We now see the value for Genre in the printed output is Magic , and not 8 . It's also important to note that our test actually didn't change, only the way in which we leveraged our new type for Genre . What about Iota? For those of you that are familiar with Go already, you might have looked at this problem and asked "Why didn't you just use iota?". Iota is an identifier that you can use in Go to also create incrementing number constants. While there are several reasons I didn't use iota here, I did dedicate an entire article to the topic. Read all about it in Where and When to use Iota in Go . Summary While this example was purposefully basic in nature, it illustrates the power of defining your own type, and leveraging the type system in Go to create more resilient, readable, and reusable code. Want More? Check out our previous article, Embracing the Go Type System and learn how to use the type system to avoid common mistakes in Go. Are you a company looking for Go training? The Gopher Guides want to help. We specialize in Instructor Led Virtual Go Training. Contact us More Articles [embracing-] Embracing the Go Type System * Cory LaNou * 01/26/2021 Go is a typed language, but most Go developers don't truly embrace it. This short article talks about tips and tricks to write more robust code using custom types in Go. Learn more [whats-in-a] What's in a name? * Dave Cheney * 06/30/2020 The art of writing software is the act of communication. You might feel that this is a private conversation about your code with a persnickety compiler, but the audience for software is much larger. It's the people who use your libraries and your APIs, the folks who work with you maintaining the codebase, and it's you, in the future, searching for clues about why you wrote what you wrote the way you wrote it. This talk is all about naming. It's about the names we give to things in our programs and how those decisions can affect the maintainability of the software we write. Learn more [test-clean] What's new in Go 1.14: Test Cleanup * Tim Raymond * 02/11/2020 The process of writing a unit test usually follows a certain set of steps. First, we set up dependencies of the unit under test. Next, we execute the unit of logic under test. We then compare the results of that execution to our expectations. Finally, we tear down any dependencies and restore the environment to the state we found it so as not to affect other unit tests. In Go 1.14, the testing package now includes a method, testing.(*T).Cleanup , which aims to make creating and cleaning up dependencies of tests easier. Learn more View all Subscribe to our newsletter [ ] [ ] Subscribe Navigation * Virtual GO Training * Team * Blog * Contact * Media Kit * Login Courses * Intro to Go * Advanced Go * Testing in Go * Profiling & Optimization * Web API in Go * gRPC/gateway in Go * Mastering Go * Custom Go Courses Contact * +1 715-313-0145 * info@gopherguides.com [logo] * * * * (c) 2020 gopher guides. all rights reserved *