Go为何区分uuid.UUID与[16]byte?类型解析机制疑问
我有一个PostgreSQL数据库表,包含id(UUID类型)和company_url(varchar类型)两列,数据如下:
2fc35af4-5f5c-445e-86c5-01d93513b8be | https://test.com 2bf31b75-d1f3-4a9c-a530-73b714816e9e | https://test2.com
使用Go语言访问该表的代码如下(为简化省略错误处理和凭据):
package main import ( "fmt" "database/sql" "github.com/google/uuid" _ "github.com/lib/pq" ) func main() { connStr := "host= password= port= dbname= user=" db, _ := sql.Open("postgres", connStr) rows, _ := db.Query("SELECT * FROM companies;") for rows.Next() { // var id [16]byte // 无法正确获取值 var id uuid.UUID // 可正确获取值 var companyURL string rows.Scan(&id, &companyURL) fmt.Println(id, companyURL) } }
google/uuid包中UUID的定义为:
type UUID [16]byte
疑惑点:既然uuid.UUID与[16]byte本质是相同底层类型,为何前者能正确获取UUID值,后者得到的却是16个零的数组?该包没有二进制文件、init函数,Scan也未做隐式修改。
核心原因是google/uuid.UUID类型实现了sql.Scanner接口,而原生的[16]byte数组没有实现这个接口。
当调用rows.Scan时,Go标准库会检查传入参数是否实现了sql.Scanner接口:如果实现了,就调用该类型自定义的Scan方法处理数据库返回的数据;如果没实现,就使用默认扫描逻辑。
PostgreSQL的UUID类型返回的是字符串格式的UUID(比如2fc35af4-5f5c-445e-86c5-01d93513b8be),而非直接的16字节二进制数组。原生[16]byte没有Scan方法,默认逻辑无法将字符串UUID转换成16字节数组,因此得到全零数组。
而uuid.UUID的Scan方法专门适配了这种场景:它能接收数据库返回的字符串、字节切片等格式的UUID,将其解析为对应的16字节数组存储,所以能正确获取值。
你可以在google/uuid包的源码中找到类似这样的Scan实现:
func (u *UUID) Scan(src interface{}) error { switch src := src.(type) { case string: return u.UnmarshalText([]byte(src)) case []byte: return u.UnmarshalBinary(src) // 其他类型的兼容处理 } return fmt.Errorf("invalid UUID type: %T", src) }
正是这个自定义的Scan方法,让uuid.UUID能正确适配PostgreSQL的UUID类型数据,而原生[16]byte做不到这一点。
内容的提问来源于stack exchange,提问作者S_py_go_Xru

