Creating Custom Types For Variables?

If I create a custom type for a const variable, how would this be useful to the developer?

Example:

type Count int

const (
    StudentCount Count = 30
    TeacherCount Count = 5
    SchoolCount Count = 2
)

Why would it be better than doing this?

const (
    StudentCount int = 30
    TeacherCount int = 5
    SchoolCount int = 2
)

What is the context?

When looking at this in isolation, a custom type has no obvious advantage over an int. It depends on the context in which the type and the constants are used.

Typically, a custom type can help prevent mixing unrelated data. Example:

type Meter float64
type Foot float64

This way, you cannot inadvertently pass a metric length to a function that expects an imperial length.

1 Like

Type aliases are mostly used to indicate intent. Often in conjunction with enums/iotas. For example from net/http/cookie:

// SameSite allows a server to define a cookie attribute making it impossible for
// the browser to send this cookie along with cross-site requests. The main
// goal is to mitigate the risk of cross-origin information leakage, and provide
// some protection against cross-site request forgery attacks.
//
// See https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00 for details.
type SameSite int

const (
	SameSiteDefaultMode SameSite = iota + 1
	SameSiteLaxMode
	SameSiteStrictMode
	SameSiteNoneMode
)

… or to extend the functionality of something with receivers and such. Here’s an example in net/http/header:

// A Header represents the key-value pairs in an HTTP header.
//
// The keys should be in canonical form, as returned by
// CanonicalHeaderKey.
type Header map[string][]string

// Add adds the key, value pair to the header.
// It appends to any existing values associated with key.
// The key is case insensitive; it is canonicalized by
// CanonicalHeaderKey.
func (h Header) Add(key, value string) {
	textproto.MIMEHeader(h).Add(key, value)
}

As you can see, headers’ underlying type is just a map[string][]string, but it has additional functionality / implications beyond map[string][]string. I’ve used this when I want to satisfy an interface for something but I don’t need a custom struct (similar to how header is used here). I’ve done this pretty recently when I needed to satisfy the ReaderAt interface for an AWS integration.

I most often use type aliases to convey something like “hey look here for the available options”. Consider the following, for example:

type TaskType string

const (
	TaskTypeWork     = "work"
	TaskTypePersonal = "personal"
)
// Makes it more obvious that you shouldn't pass something 
// like "someTypeIWasn'tExpecting" here.
func AddTask(t TaskType) {

}

It doesn’t prevent a developer from writing AddTask("oops") but it conveys a lot more information than the signature func AddTask(t string).

2 Likes

That makes sense. Thank you Christoph.

Thanks Dean for this great explanation.

Creating a custom type for a constant variable in Go can provide several benefits to the developer:

  1. Code Clarity and Readability: By defining a custom type for a constant, you can give it a meaningful name that describes its purpose or value. This improves the clarity and readability of your code. It becomes easier for other developers (including yourself) to understand the intent and usage of the constant.
  2. Type Safety: Go is a statically typed language that emphasizes type safety. By creating a custom type for a constant, you can enforce type checks and ensure that the constant is used correctly throughout your codebase. It prevents accidental misuse or assignment of an incorrect value.
  3. Abstraction and Encapsulation: Using a custom type for a constant allows you to encapsulate related constants within a specific type. This promotes code organization and abstraction, making it easier to manage and reason about different groups of constants. It can also help avoid naming conflicts if you have similar constants with different meanings.
  4. Semantic Meaning: The custom type itself can convey additional semantic meaning about the constant. For example, you could define a custom type called Temperature for a constant representing a temperature value. This provides a self-explanatory hint to other developers that the constant represents a temperature.
  5. Consistent API Design: If you’re designing a library or package, using custom types for constants can contribute to a consistent and well-defined API. By providing custom types for constants, you create a clear contract for developers using your code, making it easier for them to understand and utilize the constants correctly.
  6. Future Flexibility: Defining a custom type for a constant sets you up for future flexibility. If the underlying value or representation of the constant needs to change, you can update the custom type without affecting the usage of the constant throughout your codebase. This makes it easier to evolve your code as requirements change.
    :slightly_smiling_face:
    package main

import “fmt”

// Custom type for task status
type TaskStatus string

// Constants representing task status
const (
StatusPending TaskStatus = “pending”
StatusInProgress TaskStatus = “in-progress”
StatusCompleted TaskStatus = “completed”
)

// Function to get the status of a task
func GetTaskStatus() TaskStatus {
// In this example, we simply return a predefined status
return StatusInProgress
}

func main() {
// Using the custom type for the constant
fmt.Println(“Task status:”, StatusPending)

// Comparing task status
if GetTaskStatus() == StatusInProgress {
	fmt.Println("Task is in progress.")
}

}

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.