模块

现在我们已经能写一些简单的代码了,今天我们来看下如何使用Go module来进行多个模块的代码编写。

mkdir hello
cd hello
go mod init example.com/hello

我们通过pkg.go.dev搜索我们需要的包,并将该包使用import引入到我们的hello.go文件中。

package main

import "fmt"

import "rsc.io/quote"

func main() {
    fmt.Println(quote.Go())
}

然后我们运行go mod tidy来进行自动的添加或删除需要的包。

go mod tidy
go run .

运行后我们发现本地多了一个go.mod的文件,打开该文件后发现引用中带了一个版本的选项,用模块版本编号的每个部分来表示版本的稳定性和向后兼容性,同时也反映了较上个版本以来模块更改的性质。

require rsc.io/quote v1.5.2

version number

现在调用外部模块已经成功了,我们在本地创建个模块试着调用下。

mkdir greetings
cd greetings
go mod init example.com/greetings
# 创建一个greetings.go文件
cat > greetings.go << EOF
package greetings

import "fmt"

func Hello(name string) string {
	message := fmt.Sprintf("Hi, %v. Welcome!", name)
	return message
}
EOF

模块创建好后我们使用tree .查看下当前的目录结构。

.
├── greetings
│   ├── go.mod
│   └── greetings.go
└── hello
    ├── go.mod
    └── hello.go

修改hello/hello.go的代码,调用我们自己的本地模块greetings

package main

import (
	"fmt"

	"example.com/greetings"
)

func main() {
	out := greetings.Hello("小黑")
	fmt.Println(out)
}

通过上面的操作我们知道go mod tidy会自动查找并下载依赖,但由于我们的模块是在本地任意创建的未进行发布,所以需要使用go mod edit调整下我们模块的地址。

go mod edit -replace example.com/greetings=../greetings

执行后我们会发现go.mod多了一个行

replace example.com/greetings => ../greetings

配置完我们就可以直接go mod tidy后运行代码了,当我们模块写的比较完善后,就可以进行模块发布

go mod tidy
go run .

反射

反射是程序自身可以访问和检测自身状态的一种能力,在我们写Go代码中可能很少用到,但有时合理的利用反射能让我们的代码更简洁和灵活。Go中使用reflect实现了反射的能力,核心是reflect.TypeOf和reflect.ValueOf两个方法。

func TypeOf(i any) Type : 获取动态类型i的反射信息,然后我们看下返回的reflect.Type类型。

// 调用TypeOf
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x))

// 查看Type定义
type Type interface {
	Align() int
	FieldAlign() int
	Method(int) Method
	MethodByName(string) (Method, bool)
	NumMethod() int
	Name() string
	PkgPath() string
	Size() uintptr
	String() string
	Kind() Kind
	Implements(u Type) bool
	// ...
}

我们跟踪进去查看下reflect.TypeOf的具体实现是将参数强制转换为emptyInterface,而emptyInterface中实现的reflect/type.go:rtype和系统runtime/type.go:_type中的完全一致,是直接同步的,所以可以直接强制转换。

func TypeOf(i any) Type {
	eface := *(*emptyInterface)(unsafe.Pointer(&i))
	return toType(eface.typ)
}

func toType(t *rtype) Type {
	if t == nil {
		return nil
	}
	return t
}

func ValueOf(i any) Value : 获取类型i的具体值信息,然后我们看下返回的reflect.Value类型,reflect.Value没有提供任何公开字段,但提供了获取和写入的一些方法。

// 调用ValueOf
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("value:", v.Float())

// 查看Value定义
type Value struct {
	// 包含过滤或未导出的字段
}

func (v Value) Addr() Value
func (v Value) Bool() bool
// 赋值
func (v Value) Set(x Value)
// 类型
func (v Value) Type() Type
// 返回接口值或指针
func (v Value) Elem() Value
// ...

文章《The Laws of Reflection》中总结了反射的三大定律。

  • 反射可以从接口值中获得反射对象。如:reflect.TypeOf() / reflect.ValueOf()
  • 反射可以从反射对象中获得接口值。如:y := v.Interface().(float64)
  • 要修改反射对象,其值必须可设置。如:值类型的变量不可设置

我们常见的有以下几种使用方式:

// 无参调用
reflect.ValueOf(t).MethodByName(name).Call(nil)

// 有参调用
value := []reflect.Value{a, b}
reflect.ValueOf(t).MethodByName(name).Call(value)

// 接收返回值
ret := reflect.ValueOf(t).MethodByName(name).Call(nil)

// 类型判断
t := reflect.TypeOf(a)
switch t.Kind(){
	case reflect.Int:
		// ..
	case reflect.String:
		// ..
}

参考