- Published on
Go Basic
- Authors
- Name
- Bowen Y
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:
plugin.Lookup Returns interface:
- The
Lookup
method of theplugin.Plugin
type returns aplugin.Symbol
, which is defined asinterface{}
. - This means the returned value is of an empty interface type, which can hold any value but does not have a specific type.
- The
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 theinterface{}
value to a function with the signaturefunc(string, string) []mr.KeyValue
. - Without this assertion, you cannot call
xmapf
as a function because it is still of typeinterface{}
.
[]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 forint32
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:
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.
- The length of a string (
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.
- Converting a string to a slice of runes (
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 anil
interface of typeStringer
(it has not been assigned any concrete value that satisfiesStringer
).- The type assertion
s.(Printer)
attempts to assert that the dynamic type ofs
implements thePrinter
interface. However, sinces
isnil
, it does not contain any concrete value, let alone one that implementsPrinter
. - 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. Whennil
, 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 anil
interface of typeStringer
.- The type conversion
Printer(s)
attempts to converts
to thePrinter
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
andPrinter
are interfaces and Go's type system allows this conversion (becauseStringer
includes all the methods inPrinter
), the conversion is syntactically valid. - The result is that
p
will also be anil
interface of typePrinter
. The output will simply show<nil>
, indicating thatp
is a nil interface.
Key Differences
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.
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 anil
slice, and it will return0
. - You can call
cap()
on anil
slice, and it will return0
. - 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
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 Slice | Nil 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
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.
init
3. Purpose of 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).
init
Functions
4. Multiple 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 allinit
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.