logo
Published on

Go Basic

Authors
  • avatar
    Name
    Bowen Y
    Twitter

Go Basic

xmapf, err := p.Lookup("Map") can I use the xmapf as a function directly?

In Go, when you use the plugin.Lookup method, it returns a plugin.Symbol, which is a type alias for interface{}. This means that the returned value is of type interface{} and doesn't have a concrete type until you use a type assertion.

You cannot use xmapf and xreducef directly as functions because they are of type interface{}. To use them as functions, you need to perform a type assertion to convert them to the specific function type you expect.

Here's a detailed breakdown of why you need the type assertion:

  1. plugin.Lookup Returns interface:

    • The Lookup method of the plugin.Plugin type returns a plugin.Symbol, which is defined as interface{}.
    • This means the returned value is of an empty interface type, which can hold any value but does not have a specific type.
  2. Type Assertion:

    • To use the returned value as a specific function type, you need to assert its type.
    • The type assertion xmapf.(func(string, string) []mr.KeyValue) converts the interface{} value to a function with the signature func(string, string) []mr.KeyValue.
    • Without this assertion, you cannot call xmapf as a function because it is still of type interface{}.

[]rune vs string in GO?

Strings in Go:

  • Definition: A string in Go is a sequence of bytes (uint8), and it is immutable.
  • UTF-8 Encoding: Go strings are UTF-8 encoded by default, allowing them to represent any Unicode character, including Chinese characters and other non-ASCII characters.
  • Length: The length of a string, as returned by len(s), is the number of bytes in the string, not the number of characters.
  • Usage: Sequence of bytes, useful for storage and transmission. UTF-8 encoded strings can include multi-byte characters.

Runes in Go:

  • Definition: A rune is an alias for int32 and is used to represent a Unicode code point. It emphasizes that the value represents a character.
  • Usage: Runes are used when you need to work with individual characters of a string, especially when dealing with non-ASCII characters. Useful for text manipulation and processing.

Key Points:

  1. String Length:

    • The length of a string (len(s)) gives the number of bytes.
    • For UTF-8 encoded strings, non-ASCII characters (e.g., Chinese characters) use more than one byte.
  2. Rune Length:

    • Converting a string to a slice of runes ([]rune(s)) allows you to work with Unicode code points.
    • The length of a rune slice (len([]rune(s))) or the number of runes (utf8.RuneCountInString(s)) gives the number of characters.

Type Assertion VS Type Conversion

Type assertion is necessary and specifically designed for extracting and working with concrete values stored in interfaces. Type conversion, on the other hand, is for converting known, compatible types to one another. The inability to use direct conversion with interface arises because, with interface, the underlying type isn't known at compile time and needs to be determined at runtime, which is precisely what type assertions facilitate.

#######################
### Type Conversion ###
#######################
package main

import "fmt"

type Stringer interface {
	String() string
	// Print() string
}

type Printer interface {
	Print() string
}

type MyType struct {
	value string
}

func (m MyType) String() string {
	return m.value
}

func (m MyType) Print() string {
	return m.value
}

func main() {
	var s Stringer = MyType{"Hello, world!"}
	p := Printer(s) // Type conversion from Stringer to Printer
	fmt.Println(p.Print())
}

ERROR: cannot convert s (variable of type Stringer) to type Printer: Stringer does not implement Printer (missing method Print)

NOTE:
It works when Stringer include all the methods defined in Printer(uncommen Print method and it works)
######################
### Type Assertion ###
######################
package main

import "fmt"

type Stringer interface {
	String() string
}

type Printer interface {
	Print() string
}

type MyType struct {
	value string
}

func (m MyType) String() string {
	return m.value
}

func (m MyType) Print() string {
	return m.value
}

func main() {
	var s Stringer = MyType{"Hello, world!"}
	p := s.(Printer) // Type conversion from Stringer to Printer
	fmt.Println(p.Print())
}

NOTE:
It works well because the underlying struct implement Print method. Will raise error when remove Print method on MyType.
  • Interface Satisfiability: In Go, a type satisfies an interface if it implements all the methods of the interface. This also means that one interface can be satisfied by another if the former includes all the methods of the latter.
  • Type Conversion: When converting between interface types, the source interface must include all the methods required by the destination interface. If an interface A includes all methods of another interface B, any value that satisfies A can also satisfy B. Therefore, type conversion between these interfaces is allowed.
package main

import "fmt"

type Stringer interface {
	String() string
	Print() string
}

type Printer interface {
	Print() string
}

type MyType struct {
	value string
}

func (m MyType) String() string {
	return m.value
}

func (m MyType) Print() string {
	return m.value
}

func main() {
	var s Stringer
	p := s.(Printer) // Type conversion from Stringer to Printer
	fmt.Printf("%#v", p)
}

ERROR: panic: interface conversion: interface is nil, not main.Printer
  • s is a nil interface of type Stringer (it has not been assigned any concrete value that satisfies Stringer).
  • The type assertion s.(Printer) attempts to assert that the dynamic type of s implements the Printer interface. However, since s is nil, it does not contain any concrete value, let alone one that implements Printer.
  • This leads to a runtime panic with the error: interface conversion: interface is nil, not main.Printer. The reason is that Go's type assertion mechanism checks if the actual concrete value inside the interface can be asserted to the target type. When nil, there's no value to assert, causing a panic.
package main

import "fmt"

type Stringer interface {
	String() string
	Print() string
}

type Printer interface {
	Print() string
}

type MyType struct {
	value string
}

func (m MyType) String() string {
	return m.value
}

func (m MyType) Print() string {
	return m.value
}

func main() {
	var s Stringer
	p := Printer(s) // Type conversion from Stringer to Printer
	fmt.Printf("%#v", p)
}

NOTE: Just output <nil>
  • s is still a nil interface of type Stringer.
  • The type conversion Printer(s) attempts to convert s to the Printer interface. Unlike type assertion, type conversion does not involve a runtime check for the dynamic type of the value inside the interface.
  • Since both Stringer and Printer are interfaces and Go's type system allows this conversion (because Stringer includes all the methods in Printer), the conversion is syntactically valid.
  • The result is that p will also be a nil interface of type Printer. The output will simply show <nil>, indicating that p is a nil interface.

Key Differences

  1. Type Assertion:

    • Runtime Check: Ensures the dynamic value inside the interface matches the target type.
    • Panic on Nil: Panics if the value is nil or doesn't implement the asserted type.
    • Use Case: To access concrete type information or a specific interface implementation.
  2. Type Conversion:

    • No Runtime Check: Does not verify the dynamic type of the value; only checks the compatibility of the types.
    • No Panic on Nil: Allows conversion as long as the source interface type includes all methods of the target interface type, even if the value is nil.
    • Use Case: To change the static type of an interface or value without concern for the underlying concrete type, as long as the methods align.

Method with Function Receiver

package main

import "fmt"

// Define a function type
type MyFunc func(int, int) int

// Define a method with a function receiver
func (f MyFunc) Describe() {
	fmt.Println("This is a method attached to a function type.")
}

func (f MyFunc) Print(a int) {
	fmt.Println(a)
}

func (f MyFunc) Apply(a int, b int) {
	fmt.Println(f(a, b))
}

func main() {
	// Define a function that matches the MyFunc type
	var add MyFunc = func(a, b int) int {
		return a + b
	}

	add.Describe()
	add.Print(1)
	add.Apply(2, 3)

	mul := MyFunc(func(a, b int) int {
		return a * b
	})
	mul.Describe()
	mul.Print(1)
	mul.Apply(2, 3)
}


RESULT:
This is a method attached to a function type.
1
5
This is a method attached to a function type.
1
6

Goroutine

Goroutines are functions that can be run concurrently with other functions. They are managed by the Go runtime, which multiplexes thousands of goroutines onto a smaller number of operating system threads. This management allows goroutines to be highly efficient, with much less overhead compared to traditional threads.

The Go runtime handles the scheduling and execution of goroutines, making them easy to use and manage. Unlike traditional threads, goroutines are not associated with a fixed system thread. The Go scheduler can move a goroutine between OS threads as needed, which is similar to how coroutines can be suspended and resumed. However, unlike coroutines that typically require explicit yielding, goroutines can be preempted by the Go scheduler, giving them behavior more akin to threads.

In summary, while goroutines share some similarities with coroutines in terms of lightweight and cooperative multitasking, they also have characteristics of threads, such as preemptive scheduling and potential concurrency on multiple CPU cores. This unique combination makes them a hybrid concept within Go's concurrency model.

Type VS Kind

TODO:

nil slice VS empty slice

In Go, explicitly initializing slices like pushStack and popStack to empty slices ([]int) is not strictly necessary because Go automatically initializes slices to their zero value, which is nil. You can omit the initialization if you prefer, as a nil slice behaves like an empty slice in most cases (for example, it can be appended to, and its length will be 0).

In Go, the behavior of nil slices is designed to avoid the common pitfalls associated with null pointers in languages like C. Here's why a nil slice can be used without causing null pointer issues:

1. Go's zero value philosophy

Go emphasizes simplicity and safety by ensuring that all types have well-defined zero values, which are safe to use. For slices, the zero value is nil, and it behaves like an empty slice. This is part of Go's design to minimize runtime errors and make code more predictable.

In C, NULL is not a valid pointer to an array, and dereferencing NULL causes undefined behavior or a crash. In Go, a nil slice has well-defined behavior:

  • You can call len() on a nil slice, and it will return 0.
  • You can call cap() on a nil slice, and it will return 0.
  • You can append to a nil slice, and it will automatically create a new underlying array.

This makes it safe to use a nil slice without the need for explicit checks or initialization, unlike in C.

2. Internal representation of slices

A Go slice is a structure with three components:

  • Pointer to the underlying array
  • Length (number of elements)
  • Capacity (size of the underlying array)

A nil slice is simply a slice where the pointer is nil, and both length and capacity are 0. Importantly, this structure means you never directly work with raw memory or deal with unsafe operations like dereferencing a null pointer.

Here's what happens with a nil slice:

var s []int  // s is a nil slice
fmt.Println(len(s))  // prints 0
fmt.Println(cap(s))  // prints 0
s = append(s, 1)     // works fine, appends 1 to the slice
fmt.Println(s)       // prints [1]

3. nil VS empty slice

If we think of a slice like this:

[pointer] [length] [capacity] then:

nil slice: [nil][0][0] empty slice: [addr][0][0] // it points to an address

func main() {

    var nil_slice []int
    var empty_slice = []int{}

    fmt.Println(nil_slice == nil, len(nil_slice), cap(nil_slice))
    fmt.Println(empty_slice == nil, len(empty_slice), cap(empty_slice))

}

OUTPUT:
true 0 0
false 0 0
  1. nil pointers

The behavior with nil pointers in Go is different from nil slices. While you can safely work with a nil slice as an empty slice, attempting to dereference or operate on a nil pointer without proper initialization will result in a runtime error, similar to what happens in C.

	var p *int  // p is a nil pointer
	fmt.Println(p)  // Prints: <nil>
	fmt.Println(*p) // Causes panic: runtime error: invalid memory address or nil pointer dereference
type MyQueue struct {
	pushStack *[]int
	popStack  *[]int
}

func Constructor() MyQueue {
	myQueue := MyQueue{}
	myQueue.pushStack = &[]int{}
	return myQueue
}

func main() {
	test := Constructor()
	fmt.Println(test)
	fmt.Println(tt.popStack == nil)
	fmt.Println(tt.pushStack == nil)
}

OUTPUT:
{0xc0000ba060 <nil>}
true
false
Nil SliceNil Slice Pointer
The slice itself is nil.The pointer is nil.
Can be operated on safely (e.g., append), as it behaves like an empty slice.Cannot be dereferenced or operated on without initialization (runtime panic if dereferenced while nil).
Safe to use with len(), cap(), and append() even when nil.Requires initialization before dereferencing or operations.
Common use case: when you want an empty or uninitialized slice.Common use case: when you want to pass a reference to a slice or allow nil-pointers explicitly.

No assignment or operations allowed outside the functions in Golang

In Go, you can declare variables outside of functions, but there are restrictions on what operations you can perform at the package level (i.e., outside of functions or methods).

Why can you declare variables outside functions in Go?

You can declare variables at the package level because Go allows package-level variables to hold global or shared state across functions and methods. These variables are typically initialized with constant values or default zero values and can be accessed throughout the package. For example:

var globalVar int // This is allowed

Why can't you assign values or do operations outside functions?

In Go, package-level code is restricted to declarations and constant expressions. This means you can declare variables and initialize them with simple constant expressions (like literals) but not perform complex operations or assignments. The reason for this restriction is to keep package-level code simple, efficient, and predictable. Operations or computations are expected to be done within functions, ensuring a clear separation of initialization and logic.

For example, this will produce an error:

var x = 5
x = x + 10 // Error: cannot assign to x

However, this is allowed:

var x = 5 // Declaration and initialization

If you need to perform complex operations or assignments, you should do them inside functions, such as init() or main(). For example:

var globalVar int

func init() {
    globalVar = 5 + 10 // Operations done inside the function
}

Conclusion

Go enforces these restrictions to prevent package-level code from becoming too complex or slow at compile-time, and to maintain clear, function-based execution of logic. This design helps Go maintain simplicity and performance, especially for large codebases.

Reference

https://stackoverflow.com/questions/50661263/why-cant-i-assign-values-to-global-variables-outside-a-function-in-c

init function in Golang

In Go, the init function is a special function that is automatically invoked by the Go runtime when a package is initialized. It is primarily used to set up initial states or perform setup tasks before the main program begins executing.

Here are the key characteristics of the init function:

1. No Arguments and No Return Values

The init function cannot take arguments and does not return any values. Its signature is simply:

func init() {
    // initialization code
}

2. Automatic Invocation

You do not call init() explicitly in your code. The Go runtime will automatically execute init() functions when the package is imported or when the program starts.

3. Purpose of init

The init function is typically used to:

  • Initialize global variables.
  • Set up necessary configurations (e.g., reading configuration files).
  • Initialize state or resources (e.g., establishing a database connection).
  • Register components with package-level structures (e.g., registering handlers).

4. Multiple init Functions

You can define more than one init function within a single package, even across multiple files in the same package. The Go runtime will call each init function in the order they appear in the source files, which are executed after global variable declarations but before main().

5. Order of Execution

  • Package-level variable declarations are evaluated first.
  • Then, the init() function is executed.
  • Finally, the main() function is executed after all init functions have been completed.

Example Usage

package main

import "fmt"

var globalVar int

func init() {
    globalVar = 10
    fmt.Println("Init function executed: globalVar =", globalVar)
}

func main() {
    fmt.Println("Main function executed")
}

Output:

Init function executed: globalVar = 10
Main function executed

6. Use Case: Package Initialization

When you import a package, the init function in that package is called automatically before your code begins. This is useful for initializing resources or performing setup without requiring the importing code to do anything explicitly.

For example, in a logging package, init() might configure default log settings, so when the package is imported, logging is ready to be used:

package log

func init() {
    // Set up logging defaults
    fmt.Println("Logging initialized")
}

Important Notes:

  • init functions cannot be called explicitly from other parts of the code.
  • They should be used carefully to avoid complex initialization logic, which can lead to difficult-to-trace errors or performance issues.

The init function is most useful when you need to set up package-level state before any other part of the program runs.