9 minutes read

Go (or Golang) is a statically typed programming language. Its robust type system is one of its key features, allowing developers to create custom types and type aliases.

Custom types and type aliases provide ways to enhance code readability, improve maintainability, and express domain-specific concepts more clearly. In this topic, you'll dive into these concepts and learn about their practical use cases.

Custom types

Custom types in Go allow developers to define new types based on existing ones or built-in primitive types. These new types come with their own set of methods and behaviors, enabling developers to encapsulate logic and enhance the clarity of their code.

You can declare new types in Go using the following syntax, where type is a keyword:

type Temperature float64 // In degrees: 36.6 (can represent °C, °F, or K)
type UUID string // Universally Unique Identifier: "550e8400-e29b-41d4-a716-446655440000"
type ISBN string // International Standard Book Number: "978-3-16-148410-0"

Type alias

On the other hand, type aliases provide alternative names for existing types without creating a distinct new type. In short, a type alias is an alternate spelling for a type rather than a completely separate type.

The syntax of alias declaration is similar to type definition. However, notice the use of the = symbol to indicate that it's an alias and not a new distinct type:

type intSlice = []int

// a function represents an operation for two floats, e.g. addition or subtraction
type float32OperationFunc = func(a, b float32) float32

Apart from being used as alternative names for existing types, type aliases were actually introduced in Go to support large-scale refactoring, allowing for gradual code repair when moving a type between packages. You can read more about the origin of type aliases in the official proposal document.

Custom types vs. type alias

Both custom types and type aliases are based on some other type. There are, however, subtle differences in their usage.

Interchangeability: Custom types are distinct from the types they're based on. This means you can't directly interchange them without explicit conversion. For instance:

temp := Temperature(25.5)
f := float64(temp)

In contrast, you can use values of type aliases interchangeably with the original type without any type casting:

var i intSlice
i = intSlice{1,2,3,4,5}
i = []int{1,2,3,4,5}

Method definitions: Since type alias does not define a new type, you cannot declare methods for it. Custom types, on the other hand, can have methods associated with them. Here's an example with the Temperature custom type:

func (t Temperature) CelsiusToFahrenheit() Temperature {
    return Temperature(t*9/5 + 32)
}

Versatility of custom types

Custom types in Go are incredibly flexible because they can use aspects of both primitive types and structures, offering developers a tool for creating expressive and organized code. Here's a closer look at how they combine these features:

  • Type safety: custom types ensure type safety, just like basic types. They prevent you from inadvertently mixing different types, adding an extra layer of error prevention.
  • Single value and methods: Custom types based on primitive types represent a single value but can also contain additional methods. For example, the Temperature custom type defined as float64, encapsulates a single floating-point value and provides a method to convert from Celsius to Fahrenheit degrees.
  • Encapsulation: custom types based on structures allow for encapsulation. You can encapsulate related data (in structure fields) and methods within a custom type, making your code more organized and readable.

Practical use cases of custom types

  • Type aliases improve naming clarity: when you want to provide more descriptive and expressive names for existing types without introducing new behavior.
  • Custom types and encapsulation: when you want to encapsulate specific behavior to a particular type. For example, you can create types Celsius and Fahrenheit and add a method to each type for conversion:
type Celsius float64

func (c Celsius) ToFahrenheit() Fahrenheit {
    return Fahrenheit(c*9/5 + 32)
}

type Fahrenheit float64

func (f Fahrenheit) ToCelsius() Celsius {
    return Celsius((f - 32) * 5 / 9)
}
  • Custom types and domain modeling: when you want to model complex domain-specific concepts while building an application:
type Money struct {
    Amount   float64
    Currency string
}

func (m Money) Add(other Money) Money {
    return Money{
        Amount:   m.Amount + other.Amount,
        Currency: m.Currency,
    }
}

Single field struct vs. custom type

As you saw earlier, you can create a new type based on a primitive type or a structure. One of the advantages of using primitive-based custom type is that you can still use operators:

type Temperature float64

f1 := Temperature(312)
f2 := Temperature(100)
res := f2 - f1

However, in this case, you cannot extend the Temperature type because it is based on a primitive type. To overcome this situation, you can define Temperature as a struct with a single field Value of type float64:

type Temperature struct {
    value float64
}

f1 := Temperature{312}
f2 := Temperature{100}
res := f2.value - f1.value

Given the above characteristics, here are some recommendations when deciding between a primitive-based custom type and a single-field struct:

  • For simplicity and direct operations: Use a primitive-based custom type like type Temperature float64 when you need straightforward data representations and anticipate frequent arithmetic or direct operations on the data.
  • For future extensibility and additional fields: Use a single-field struct like type Temperature struct{ value float64 } if you foresee the possibility of adding more fields or behaviors in the future. While methods can be associated with both, only struct-based types allow for the inclusion of additional fields without significant refactoring.

Conclusion

Custom types and type aliases in Go are essential tools that enable developers to create more expressive and organized code. While type aliases can help programmers enhance code readability, custom types go further by providing a way to model domains and contribute to type safety.

Remember, whether you're creating a custom type to encapsulate behavior or using a type alias to improve naming clarity, leveraging these concepts can significantly contribute to the quality of your codebase.

9 learners liked this piece of theory. 0 didn't like it. What about you?
Report a typo