Golang中Option、Empty、Null与指针的对比解析

Golang中Option、Empty、Null与指针的对比解析 大家好

我对 Go 语言有点困惑。Go 没有 ‘null’ 值,我认为这是一个好的设计,但它也没有 Option 的概念。

阅读文档时,指针被用来决定是按引用传递还是按值传递。因此,选择使用指针是关于可变性、相同实例和性能的问题。

由于没有 Option,一个常见的模式是:如果一个值可以为空(nil 指针),就使用指针。这样一来,选择指针突然变成了关于某物是否可以为空的问题。

处理这个问题的正确方式是什么?只能接受这种混合的概念吗?

2 回复

我在 Go 中的编程方式与我在其他语言(例如 C#)中的编程方式不同,C# 具有新的可空性特性,只需在类型名后加上 ? 即可使其成为可选类型。在 Go 中,我表示某个东西是可选的,这很大程度上取决于上下文。例如,对于参数可选的函数,如果只有一两个参数是可选的,我通常会创建单独的函数(例如,除了 NewThing,还有 NewThingWithContext 等)。如果需要更多的可配置性,我会使用“函数式选项”

所以,与其这样:

package mypkg

func NewThing(timeout *time.Duration) (*Thing, error) {
    // ...
}
thingA, err := mypkg.NewThing(nil)

d := 5 * time.Second
thingB, err := mypkg.NewThing(&d)

我更喜欢这样做:

package mypkg

type ThingOption func(t *Thing) error

func NewThing(options ...ThingOption) (*Thing, error) {
    // ...
}
myThing, err := mypkg.NewThing(
	mypkg.ThingTimeout(5 * time.Second),
)

虽然更冗长,但我喜欢这种模式。

如果你指的是结构体字段值是可选的,根据值的类型,我可能只会使用一个哨兵值,例如:

type MyStruct struct {
    // Timeout for an operation.  If 0, no timeout is set.
    Timeout time.Duration
}

或者,如果这种可选类型在我的包中非常常用,我可能会为它定义一个可选类型:

type OptionalTimeout struct {
    // HasDuration is set when the Duration field's value should be
    // respected.  A Duration of 0 when HasDuration is true indicates
    // that the operation should be aborted immediately
    HasDuration bool

    Duration time.Duration
}

如果你指的是结构体字段或函数参数之外的其他上下文,我需要看到具体情况才能描述我在那种情况下会怎么做。

更多关于Golang中Option、Empty、Null与指针的对比解析的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在 Go 中处理可选值的正确方式确实是指针。虽然这混合了“可为空”和“引用语义”的概念,但这是 Go 的惯用做法。让我通过代码示例说明:

type User struct {
    ID    int
    Name  string
    Email *string  // 可选字段使用指针
}

func main() {
    // 明确表示有值
    email := "user@example.com"
    user1 := User{
        ID:    1,
        Name:  "Alice",
        Email: &email,
    }
    
    // 明确表示无值
    user2 := User{
        ID:    2,
        Name:  "Bob",
        Email: nil,  // 明确表示没有email
    }
    
    // 使用时的检查
    if user1.Email != nil {
        fmt.Println("Email:", *user1.Email)
    }
    
    if user2.Email == nil {
        fmt.Println("No email provided")
    }
}

对于函数返回可选值:

func FindUser(id int) (*User, error) {
    if id == 0 {
        return nil, errors.New("user not found")  // nil表示不存在
    }
    return &User{ID: id, Name: "Test"}, nil
}

// 使用
user, err := FindUser(1)
if err != nil {
    // 处理错误
}
if user != nil {
    // 使用user
}

对于需要区分“零值”和“未设置”的场景,可以使用指针包裹:

type Config struct {
    Timeout *int    // nil表示未设置,非nil表示明确设置的值(包括0)
    Retries *int
}

func main() {
    cfg := Config{
        Timeout: nil,          // 未设置
        Retries: func() *int { // 明确设置为0
            v := 0
            return &v
        }(),
    }
    
    if cfg.Timeout == nil {
        fmt.Println("Timeout using default")
    }
    
    if cfg.Retries != nil && *cfg.Retries == 0 {
        fmt.Println("Retries explicitly set to 0")
    }
}

这种模式在 Go 标准库和主流项目中普遍使用。虽然它确实混合了“可为空”和“引用语义”,但 Go 社区已经接受了这种简洁性。指针在这里清晰地表达了“这个值可能存在,也可能不存在”的意图。

回到顶部