漫行记Wandering
Journal 3,150 字 11 分钟 Golang

Go Type Conversion and Assertion

In Go, type conversion and type assertion are two important concepts that allow you to work with different types in a flexible manner. Let’s take a quick look at how they work and their limitations.

Basic Type Conversion

Heads up: constants are checked at compile time, variables are checked at runtime.

Constants conversion

In Go, constants are checked at compile time, so unsafe conversions are rejected. Here are some failed examples and please note the error messages in the comments.

	const a = 3.14
	fmt.Println(int(a)) // cannot convert a (untyped float constant 3.14) to type int
	
	fmt.Println(int(3.14)) // cannot convert 3.14 (untyped float constant) to type int
	
	const a float32 = 3.14
	fmt.Println(int(a)) // cannot convert a (constant 3.14 of type float32) to type int
	
	fmt.Println(uint8(300)) // constant 300 overflows uint8

As you can see, if there is truncation or overflow, the compiler will reject it. So it means if there is no truncation or overflow, we can safely convert between constants.

    const a = 3
    fmt.Println(int64(a)) // prints 3, safe conversion

    const b = 255
    fmt.Println(uint8(b)) // prints 255, safe conversion

Numbers To Numbers

Go require explicit conversion between different numeric types. In this process, it might slightly truncate or overflow, you should make sure the conversion is safe by yourself.

Float to Int truncates the decimal part, does not round.

var f floag = 3.14
var i int = int(f) // Explicit conversion from float to int, it prints 3.

Larger Int to Smaller Int might overflow.

var n int = 300
var b uint8 = uint8(n) // Explicit conversion from int to uint8, it prints 44 due to overflow.

Negative int to uint reinterprets bits

var n int = -42
var u uint = uint(n) // it prints 18446744073709551574 on a 64-bit system, which is 2^64 - 42.

Strings <-> Slices

There are three cases:

  1. String to []byte: converts each character to its UTF-8 byte value.
  2. String to []rune: converts each character to its Unicode code point.
  3. []byte or []rune to String: converts the byte or rune slice to a string.

Boolean conversions

Go does not allow implicit conversion from number to bool, which means 0 does not mean false, and non-zero does not mean true. We must explicitly compare the value.

var x int = 1
if x != 0 { ... }

Defined types and Underlying types

We can create custom types based on existing types. Even if the underlying type is the same, Go treats them as distinct So we need to explicitly convert between them.

type MyInt int
var a Myint = 42
var b int = a //cannot use a (variable of int type MyInt) as int value in variable declaration
var c int = MyInt(b) // Explicit conversion from MyInt to 

type Celsius float64
type Fahrenheit float64
var c Celsius = 100
var f Fahrenheit = Fahrenheit(c)

Interfaces and Type Assertion

  • interface{} can hold any value. And Type Assertion are used for interfaces.
var x interface{}
x = 42          
x = "hello"
  • Type Assertion extracts the concrete type:
var a interface{} = "hello"
s := a.(string) // s is "hello"
  • Safe assertion using comma-ok. Without ok, wrong type assertion panics at runtime. With ok, returns *zero value + false if types don’t match.
var a interface{} = 42
s, ok := a.(string) // ok is false, s is zero value ""

Interface -> Interface

If underlying type satisfies target interface, conversion succeeds.

type Writer interface {
    Write([]byte) (int, error)
}

type Reader interface {
    Read([]byte) (int, error)
}

type ReadWriter interface {
    Reader
    Writer
}

type MyFile struct {
    name string
}

func (f *MyFile) Read([]byte) (int, error)  { return 0, nil }
func (f *MyFile) Write([]byte) (int, error) { return 0, nil }

func main() {
    var f *MyFile = &MyFile{"test.txt"}

    var w Writer = f
    var r Reader = f
    var rw ReadWriter = f
    
    fmt.Printf("w: %T\n", w)   // *main.MyFile
    fmt.Printf("r: %T\n", r)   // *main.MyFile
    fmt.Printf("rw: %T\n", rw) // *main.MyFile
    
    var w2 Writer = rw  // ReadWriter -> Writer
    fmt.Printf("w2: %T\n", w2)
    
    if rw2, ok := w.(ReadWriter); ok {
        fmt.Printf("rw2: %T\n", rw2)
    }
}

Pointer Conversions

Pointers are types, converting unrelated pointer types directly is disallowed, we need to involve unsafe.Poninter as a bridge. so it’s like this *T1 -> unsafe.Pointer -> *T2.

var p *int
var i *int64 = p //  cannot use p (variable of type *int) as *int64 value in variable declaration

var p *int
var i *int64 = (*int64)(unsafe.Pointer(p))
← 随笔