Golang Basics: 1

Golang Basics: 1

Type system and variables in go

In programming, a type system is a set of rules that defines the behaviour of a programming language's variables and values. Type systems are an important aspect of language design, as they help to prevent errors and ensure that code is safe and easy to understand.

Golang, or Go, is a statically-typed programming language that has a simple type system. In Go, variables are declared with a specific type, and the type of a variable cannot be changed after it has been declared. This can be a departure from dynamically-typed languages like Python or JavaScript, where variables can be reassigned to values of different types.

To declare a variable in Go, you can use the var keyword followed by the variable name and type. For example:

var age int = 29
var name string = "Atharva"

You can also use the shorthand syntax to declare and initialize a variable in a single line:

age := 29
name := "John"

It's important to choose descriptive and meaningful names for your variables, especially when working on larger projects. In Go, it's considered idiomatic to use lowercase letters for variable names or to use MixedCaps or mixedCaps rather than underscores to write multiword names.

var user_age int // bad
var userAge int // good

Go has a number of built-in types, including integers (int, int8, int16, int32, int64), unsigned integers (uint, uint8, uint16, uint32, uint64), and boolean values (bool).

In addition to these basic types, Go also has several composite types, including:

array: An array is a fixed-size sequence of elements of the same type. For example:

var a [3]int // a is an array of 3 integers

slice: A slice is a dynamically-sized sequence of elements of the same type. Slices are more flexible than arrays, but they do not have a fixed size and must be initialized using the make function. For example:

var s []int // s is a slice of integers
s = make([]int, 3) // s is now an array of 3 integers

struct: A struct is a composite data type that consists of a collection of fields. Structs can be used to represent complex data structures, such as records or objects. For example:

type Person struct {
    Name string
    Age  int
}

var p Person // p is a variable of type Person

map: A map is a collection of key-value pairs. Maps are useful for storing and accessing data by key. For example:

var m map[string]int // m is a map from strings to integers
m = make(map[string]int) // m is now an empty map
m["age"] = 29 // add a key-value pair to the map

There are a few advantages to using structs over maps in Go:

  1. Type safety: When you use a struct, the fields are explicitly defined and have a fixed type. This can help to prevent errors and make your code easier to understand. With a map, the types of keys and values can vary, which can make the code less predictable and more prone to errors.

  2. Performance: Structs can be more efficient than maps in terms of performance, especially when working with large datasets. This is because structs have a fixed size and layout, which makes them easier for the Go runtime to work with. Maps, on the other hand, are more dynamic and can require more memory and CPU resources to manipulate.

  3. Readability: Structs can make your code more readable and easier to understand, especially when working with complex data structures. By explicitly defining the fields and their types, you can make your code more self-explanatory and easier for others to follow.

Of course, there are also situations where maps may be more appropriate than structs. For example, if you need to store data that doesn't have a fixed structure, or if you need to access data by key rather than by field name, a map may be a better choice.

In general, it's a good idea to choose the data structure that best fits your needs. Structs and maps both have their advantages and trade-offs, and the best choice will depend on the specific requirements of your project.

Generics

Generics are a way to write code that can work with multiple types, rather than being tied to a specific type. This allows developers to write more flexible and reusable code, without having to write separate functions or data structures for each specific type they need to support.

One of the main benefits of using generics is that they can help improve the type of safety of your code. By specifying the types that a function or data structure should support, you can ensure that your code only works with values of the appropriate type, rather than having to check and handle different types manually. The below example is taken from https://go.dev/doc/tutorial/generics and you should read it for a more detailed explanation.

type Number interface {
    int64 | float64
}

This interface is defined as accepting either an int64 or a float64 value. This allows the SumNumbers function, which we'll look at shortly, to work with both integer and float values.

// SumIntsOrFloats sums the values of map m. It supports both floats and integers
// as map values.
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}

// SumNumbers sums the values of map m. Its supports both integers
// and floats as map values.
func SumNumbers[K comparable, V Number](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}

The SumIntsOrFloats the function has two type parameters: K and V. The K type parameter represents the type of the keys in the map, and must be a type that implements the comparable interface. The V type parameter represents the type of the values in the map, and can be either an int64 or a float64. This allows the function to work with maps containing either type of value.

The SumNumbers the function also has two type parameters: K and V. The K the type parameter is the same as in SumIntsOrFloats, representing the type of the keys in the map. The V type parameter represents the type of the values in the map, and must be a type that implements the Number interface. The Number the interface is defined at the beginning of the code snippet as either an int64 or a float64, so SumNumbers can only work with maps containing values of these types.

Both SumIntsOrFloats and SumNumbers operate on the maps by iterating over the values and summing them up, returning the final sum. The main difference between the two functions is the types of values they can work with: SumIntsOrFloats can work with both int64 and float64 values, while SumNumbers can only work with int64 and float64 values.

Please read more about the comparable type, to understand which data types and how and when you should efficiently use them.

In summary, the type system in Go is a crucial aspect of the language that helps to ensure the safety and reliability of your code. By understanding the different types available in Go and following idiomatic naming conventions, you can write code that is easy to understand and maintain.

Did you find this article valuable?

Support Atharva Pandey by becoming a sponsor. Any amount is appreciated!