Func
函数是 Go 代码复用的基本单元。本节介绍三种常用的函数使用模式:不定长参数、指针传参和函数作为值传递,这些都是 Web3 开发中封装工具函数的基础。
不定长参数与空接口
golang 函数中允许传递不定长的数据,或者传递 interface{} 空接口,用来接收任意类型的数据。
args ...interface{}表示任意数量、任意类型的参数,args在函数体内是一个切片- 遍历时可以用类型断言
v.(type)区分具体类型(需在switch语句中使用)
package main
import (
"fmt"
"math/big"
)
func main() {
type Tree struct {
leaves int
name string
}
myFunc(1, big.NewInt(23), Tree{99, "trace"}, "hello world", []byte("hello"))
}
func myFunc(args ...interface{}) {
for _, v := range args {
fmt.Println(v)
}
}
输出:
1
23
{99 trace}
hello world
[104 101 108 108 111]
指针传参
函数之间传递指针内存地址可以共同操作相同的数据,避免 copy data 带来的拷贝负担。
Go 中所有传参都是值传递,指针传递的其实是「指针的值」——副本指向同一片内存,因此函数内对 *a 的修改对外部可见。大结构体传参时传指针可以显著减少拷贝开销:
package main
import (
"fmt"
)
func main() {
a := 1
b := add(&a)
fmt.Println(a) // 2,a 被 add 函数修改
fmt.Println(b) // 2
}
func add(a *int) (addone int) {
*a = *a + 1
addone = *a
return
}
函数作为值传递
函数也可以作为参数传参,处理特定的业务逻辑。这在 Go 中称为「一等函数」——函数可以像普通值一样赋值给变量、存入切片、作为参数和返回值,是实现回调、过滤器和中间件的基础:
package main
import (
"fmt"
)
// 定义函数类型,签名必须是 func(int) bool
type testInt func(int) bool
func check(a int) bool {
switch a % 2 {
case 0:
return true
case 1:
return false
}
return false
}
// filter 接收一个函数 f 作为过滤条件
func filter(a []int, f testInt) (b []int) {
for _, v := range a {
if f(v) {
b = append(b, v)
}
}
return
}
func main() {
a := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
odd := filter(a, check)
// check 对偶数返回 true,因此过滤出的实际是偶数
fmt.Println(odd) // [0 2 4 6 8 10]
}
泛型 (Generics)
泛型让你在定义函数、类型时预留「类型参数」,从而写出与具体类型解耦、又能保持类型安全的代码。
[T any] 表示声明一个类型参数 T,any 约束 T 可以是任意类型(语义等价于 interface{},但泛型保留了编译期的真实类型)。使用时必须把 T 替换为具体类型,例如 SelectOption[string]、SelectOption[int]。这样既避免了 interface{} 的装箱开销和类型断言,又能在编译期校验类型。
// SelectOption 表示下拉框中的单条选项,Label 展示文案,Value 绑定实际值
type SelectOption[T any] struct {
Label string
Value T
}
func main() {
// 用具体类型 string 实例化泛型类型
options := make([]SelectOption[string], 0, 3)
options = append(options, SelectOption[string]{
Label: "Ethereum",
Value: "0x1",
})
options = append(options, SelectOption[string]{
Label: "Optimism",
Value: "0x10",
})
for _, opt := range options {
fmt.Printf("label=%s value=%s\n", opt.Label, opt.Value)
}
}
输出:
label=Ethereum value=0x1
label=Optimism value=0x10
同理,泛型函数也能复用逻辑而无需为每种类型各写一份,例如:
// 泛型函数:返回切片中的第一个元素,适用于任意类型
func first[T any](s []T) (T, bool) {
var zero T
if len(s) == 0 {
return zero, false
}
return s[0], true
}
func main() {
v, ok := first(options)
fmt.Println(v.Label, ok) // Ethereum true
}
多类型实例化
同一个泛型类型可以在不同地方分别用不同的具体类型实例化。SelectOption[string] 和 SelectOption[int] 在编译期是两个完全不同的类型,彼此独立、互不影响:
func main() {
// 地方一:string 版选项(链 ID 用字符串标识)
chainOpts := make([]SelectOption[string], 0, 2)
chainOpts = append(chainOpts, SelectOption[string]{Label: "Ethereum", Value: "0x1"})
chainOpts = append(chainOpts, SelectOption[string]{Label: "Optimism", Value: "0x10"})
// 地方二:int 版选项(区块高度用整数)
heightOpts := make([]SelectOption[int], 0, 2)
heightOpts = append(heightOpts, SelectOption[int]{Label: "Block 1", Value: 1})
heightOpts = append(heightOpts, SelectOption[int]{Label: "Block 100", Value: 100})
fmt.Println(chainOpts[0].Value) // 0x1
fmt.Println(heightOpts[1].Value) // 100
}
需要注意的约束:
- 同一切片内类型必须一致。
[]SelectOption[string]只能容纳SelectOption[string],混入SelectOption[int]会直接编译报错——两者虽然都由同一个模板生成,但类型系统认为它们无关。 - 各自独立。上面
chainOpts和heightOpts可以共存于同一函数、同一包,互不干扰。 - 如果确实需要「一个切片装多种类型的值」,应改用
interface{}/any,但这样就失去了泛型带来的编译期类型安全,一般不推荐混用。