Exploring Data Types In Go

Exploring Data Types In Go

Data types are a fundamental aspect of programming and development, providing a structured way to represent and manipulate information. In Go programming, a statically typed language, understanding data types is crucial for writing reliable and efficient code. This comprehensive guide aims to explore the various data types supported by Go, including both primitive and composite types. By gaining a comprehensive understanding of these data types, you will be equipped to utilize them effectively in your Go programs.

Primitive Data Types In Go

Go offers a range of primitive data types to handle different kinds of values. These include:

Integers

An integer serves as a fundamental data type that represents both unsigned and signed whole numbers. An unsigned whole number refers to a non-negative integer, while a signed integer encompasses both negative and positive values.

To facilitate the diverse needs of programming, there exist multiple integer types that vary in size and range, catering to different platforms and memory requirements.

Signed Integers

  • int: The size of this type depends on the platform, with either 64 bits or 32 bits. In 32-bit systems, it ranges from -2,147,483,648 to 2,147,483,647. In 64-bit systems, its range expands starting from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807.

  • int8: This type occupies 8 bits or 1 byte, accommodating values from -128 to 127.

  • int16: With a size of 16 bits or 2 bytes, it can hold values ranging from -32,768 to 32,767.

  • int32: Taking up 32 bits or 4 bytes, it encompasses values between -2,147,483,648 and 2,147,483,647.

  • int64: With a size of 64 bits or 8 bytes, it can represent values from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807.

Unsigned Integers

  • uint: Similar to the int type, the size of uint can be either 64 bits or 32 bits, depending on the platform. In 32-bit systems, it ranges from 0 to 4,294,967,295. In 64-bit systems, its range expands starting from 0 to 18,446,744,073,709,551,615.

  • uint8: Occupying 8 bits or 1 byte, it accommodates non-negative values from 0 to 255.

  • uint16: With a size of 16 bits or 2 bytes, it can represent values ranging from 0 to 65,535.

  • uint32: This type occupies 32 bits or 4 bytes, enabling the representation of values between 0 and 4,294,967,295.

  • uint64: With a size of 64 bits or 8 bytes, it encompasses values from 0 to 18,446,744,073,709,551,615.

If a specific integer type is not explicitly defined, the compiler defaults to using the int type.

// Initialize with the var keyword 
var num int = 10
// Initialize with short hand 
num := 10

In addition to these integer types, a special type called uintptr exists, primarily utilized for storing pointer values of any size. The uintptr type solely stores the value without referencing it. Notably, changes in the actual memory position are not reflected in the stored value. The size of uintptr depends on the platform, occupying 32 bits on 32-bit machines and 64 bits on 64-bit machines.

Floats

Float data types are essential for storing numbers with a decimal point. There are two primary float types, each with a different balance between precision and performance.

// Initialize with the var keyword 
var num float64
// Initialize with short hand 
num := 3.14
  • float34: Values with decimal points are computed using single-precision floating-point representation, utilizing 32 bits of memory. float32 is commonly used when performance and memory efficiency take precedence over precision. The 32 bits are allocated as follows: 1 bit for the sign, 8 bits for the exponent, and 23 bits for the mantissa.

  • float64: For higher precision, double-precision floating-point values are employed. This type uses 64 bits of memory. In contrast to float32, float64 offers greater precision. The 64 bits are divided into 1 bit for the sign, 11 bits for the exponent, and 52 bits for the mantissa. It is worth noting that float64 is the default type inferred by the compiler.

Complex Numbers

Complex numbers are mathematical entities that consist of two parts: a real part and an imaginary part. Complex numbers are initialized using a complex number constructor, typically written as

There are two common complex number data types:

  • complex64: This type represents complex numbers where both the real and imaginary parts are of type float32, which uses 32 bits of memory for each part.

  • complex128: This type represents complex numbers with both the real and imaginary parts being of type float64. It is the default complex number type inferred by the compiler and offers higher precision due to the use of 64 bits of memory for each part.

// Initialize with the var keyword 
var num complex128
// Initialize with short hand 
num := complex(3, 2)

Bytes

The term byte refers to the int8 integer type. A byte is specifically designed to store 8 bits of information. This size is sufficient to contain ASCII characters, which allows byte to be used as a representation of the char type.

In Go, the default type for working with individual characters is the rune type. However, when dealing with single-byte characters or ASCII values, the byte type is often implicitly used. This is because a byte, with its 8-bit size, is capable of representing the range of values required for ASCII characters.

var rbyte byte := 'a'

Runes

The term rune represents the int32 data type. It serves as an integer value that corresponds to a UNICODE code point character. Go uses rune as the default type for working with characters.

The rune type in Go has a size of 4 bytes, allowing it to store a wide range of UNICODE characters. However, it's important to note that the actual storage of characters depends on the chosen character encoding. In Go, characters are typically saved using the UTF-8 encoding. Under UTF-8, each character may be stored using 1 to 4 bytes, depending on its specific code point value.

// Initialize with the var keyword 
var ch rune
// Initialize with short hand 
ch := 'A'

Composite Data Types In Go

In addition to primitive types, Go provides composite data types that enable developers to create more complex data structures. These include:

Strings

Strings are represented as slices of runes, allowing them to store a sequence of characters. Strings can be initialized using two different forms: raw string literals enclosed in back ticks or normal strings enclosed in double quotes

When using raw string literals, the back ticks preserve the exact content of the string, including special characters and newlines, without any escape sequences. On the other hand, normal strings enclosed in double quotes can include escape sequences such as \n for a newline or \t for a tab.

package main 

import (
    "fmt"
) 

func main() {
    // Declare a normal string
    normalString := "This is a normal string." 

    // Declare a raw string
    rawString := `This is a raw string.` 

    // Print the normal string
    fmt.Println(normalString) 

    // Print the raw string
    fmt.Println(rawString)
}

It's important to note that the len() function in Go does not directly return the number of characters in a string. Instead, it returns the total number of bytes occupied by the string in memory. Since strings in Go are UTF-8 encoded, certain characters may require more than one byte to represent them. Therefore, the length returned by len reflects the byte count, not the character count.

Arrays

In Go, arrays are a type of composite data structure that represents a fixed sequence of values with the same type. Unlike slices, which are dynamically sized, arrays have a predetermined length that remains fixed throughout their lifetime.

They are considered non-referenced composite types, due to the fact that when an array is assigned to a new variable or passed as a function argument, a complete copy of the array is made. Any modifications made to the new variable or within the function do not affect the original array. This behavior ensures that arrays remain independent and preserves data integrity.

Arrays provide a way to organize and access collections of values with a fixed size. They offer benefits such as efficient memory usage and direct element access. However, the fixed size nature of arrays may limit their flexibility in certain scenarios. In such cases, slices, which are more versatile and dynamically resizable, are often preferred.

var fruits = [3]string{"apple", "banana", "orange"}
var arr [size]dataType

Slices

Slices are a powerful abstraction over arrays. They serve as references to the underlying values of an array and provide a dynamic and flexible way to work with collections of data.

Slices have three main attributes:

  • length: The length of a slice represents the number of elements it currently holds. It can change dynamically as elements are added or removed. The length of a slice can be obtained using the len() function.

  • capacity: The capacity of a slice represents the maximum number of elements it can hold without reallocation. It defines the size of the underlying array. The capacity of a slice can be obtained using the cap() function.

  • reference: slice holds a reference to the underlying array. This means that any modifications made to the slice will directly affect the original array, and vice versa. Slices allow efficient and direct access to array elements without the need for explicit copying.

// Initialize with the var keyword 
fruits := []string{"apple", "banana", "orange"}
// Initialize with make command
numbers := make([]int, 5, 10)

Structs

Structs are used to define custom data types. A struct is a collection of named fields, each of which can have a different type. It allows developers to group together related data into a single composite type.

Structs provide a way to create user-defined data structures with their own set of fields and their respective data types. By organizing data into structs, developers can encapsulate related information and create meaningful abstractions for their programs.

type Person struct {
    name string
    age  int
}

Maps

Maps are a built-in data structure that implements the hash map concept. Maps provide a powerful way to store and retrieve data using key-value pairs. They are a reference type, meaning they store references to the underlying data rather than the data itself.

scores := make(map[string]int)

Maps in Go are designed to efficiently store and retrieve values based on unique keys. Each key in a map must be of the same type, and each key can be associated with a corresponding value of any type. This flexibility allows developers to store and organize data in a convenient and meaningful way.

Channels

Channels provide a powerful way to establish communication and synchronization between Goroutines, which are lightweight concurrent units of execution. Channels serve as communication routes through which Goroutines can send and receive values.

numbers := make(chan int)

In Go, channels can be either buffered or unbuffered. The main difference between them is that buffered channels have a capacity to hold multiple values, while unbuffered channels do not have any capacity and require both sender and receiver to be ready for communication at the same time.

Pointers

Pointers in Go are variables that hold the memory addresses of other variables. They allow direct manipulation of memory locations, providing a powerful tool for efficient memory management and data sharing.

To declare a pointer variable in Go, you use the * symbol followed by the type of the variable it points to. Here's an example:

var ptr *dataType

To access the value stored at a memory address through a pointer, you use the dereference operator *. Here's an example

fmt.Println(*ptr) // Output: 42

Hopefully you have been able to understand more about data types in Go lang.

For related information (golangbyexample.com/all-data-types-in-golan..)