我如何在 Go 中表示可选字符串?
How do I represent an Optional String in Go?
我希望建模一个可以有两种可能形式的值:不存在或字符串。
自然的方法是使用 Maybe String
,或 Optional<String>
,或 string option
,等等。但是,Go 没有这样的变体类型。
然后我想,根据 Java、C 等,替代方案是可空性,或者 Go 中的 nil
。但是,nil
不是 Go 中 string
类型的成员。
搜索,然后想到使用*string
类型。这可以工作,但看起来很尴尬(例如,我不能像获取结构文字的地址那样获取字符串文字的地址)。
在 Go 中为这样的值建模的惯用方法是什么?
您可以为此使用 sql.NullString
, but I personally would stick to *string
. As for awkwardness, it's true that you can't just sp := &"foo"
unfortunately. But there is a 之类的东西:
func strPtr(s string) *string {
return &s
}
对 strPtr("foo")
的调用应该是内联的,所以它实际上是 &"foo"
.
另一种可能性是使用 new
:
sp := new(string)
*sp = "foo"
一个合乎逻辑的解决方案是使用 Ainar-G 提到的 *string
。 详细说明了获取指向值的指针的可能性(int64
但同样适用于 string
)。包装器是另一种解决方案。
仅使用 string
可选的 string
表示 string
加上 1 个特定值(或状态)表示 "not a string"(但 null
)。
这 1 个特定值可以存储(发出信号)在另一个变量中(例如 bool
),您可以将 string
和 bool
打包到 struct
我们到达了包装器,但这不适合 "using just a string
" 的情况(但仍然是一个可行的解决方案)。
如果我们只想坚持 string
,我们可以从 string
类型的可能值中取出 1 个特定值(其中有 "infinity" 个可能值作为长度不受限制(或者可能是因为它必须是 int
但没关系)),我们可以将这个特定值命名为 null
值,该值表示 "not a string" .
表示null
最方便的值是string
的零值,也就是空的string
:""
。将此指定为 null
元素的便利之处在于,每当您创建一个 string
变量而未明确指定初始值时,它将使用 ""
进行初始化。此外,当从 map
中查询值为 string
的元素时,如果键不在 map
.
中,也会产生 ""
此解决方案适用于许多现实生活中的用例。例如,如果可选的 string
应该是一个人的名字,一个空的 string
并不真正意味着一个有效的人名,所以你不应该首先允许它。
当然,在某些情况下,空 string
确实表示 string
类型变量的有效值。对于这些用例,我们可以选择另一个值。
在 Go 中,string
实际上是一个只读字节片。请参阅博客 post Strings, bytes, runes and characters in Go,其中详细解释了这一点。
所以 string
是一个字节片,在有效文本的情况下是 UTF-8 编码字节。假设您想在可选的 string
中存储一个有效文本(如果您不想,那么您可以只使用 []byte
而不是它可以具有 nil
值),您可以选择string
值表示无效的 UTF-8 字节序列,因此您甚至不必做出妥协来从可能的值中排除有效文本。最短的无效 UTF-8 字节序列只有 1 个字节,例如 0xff
(还有更多)。注意:您可以使用 utf8.ValidString()
函数来判断 string
值是否为有效文本(有效的 UTF-8 编码字节序列)。
您可以将此异常值设置为 const
:
const Null = "\xff"
这么短也意味着检查 string
是否等于这个会非常快。
按照这个约定,你已经有了一个可选的 string
,它也允许空的 string
.
在 Go Playground 上试用。
const Null = "\xff"
func main() {
fmt.Println(utf8.ValidString(Null)) // false
s := Null
fmt.Println([]byte(s)) // [255]
fmt.Println(s == Null) // true
s = "notnull"
fmt.Println(s == Null) // false
}
对于接口类型,您可以使用更自然的赋值语法。
var myString interface{} // used as type <string>
myString = nil // nil is the default -- and indicates 'empty'
myString = "a value"
引用值时,通常需要 才能明确检查。
// checked type assertion
if s, exists := myString.(string); exists {
useString(s)
}
此外,由于 stringers,在某些情况下 'optional' 类型将被自动处理——这意味着您不需要显式转换值。 fmt
包使用此功能:
fmt.Println("myString:",myString) // prints the value (or "<nil>")
警告
赋值时没有类型检查。
在某些方面,这是比处理指针更简洁的方法。但是,因为它使用接口类型,所以它不限于持有特定的底层类型。风险在于您可能无意中分配了不同的类型——这将被视为与上述条件中的 nil
相同。
下面是使用接口进行赋值的演示:
var a interface{} = "hello"
var b = a // b is an interface too
b = 123 // assign a different type
fmt.Printf("a: (%T) %v\n", a, a)
fmt.Printf("b: (%T) %v\n", b, b)
输出:
a: (string) hello
b: (int) 123
请注意,接口是通过重复分配的,因此 a
和 b
是不同的。
你可以使用 Go 惯用的接口:
type (
// An interface which represents an optional string.
StrOpt interface{ implStrOpt() }
StrOptVal string // A string value for StrOpt interface.
StrOptNone struct{} // No value for StrOpt interface.
)
func (StrOptVal) implStrOpt() {} // implement the interface
func (StrOptNone) implStrOpt() {}
这就是您的使用方式:
func Foo(maybeName StrOpt) {
switch val := maybeName.(type) {
case StrOptVal:
fmt.Printf("String value! -> %s\n", string(val))
case StrOptNone:
fmt.Println("No value!")
default:
panic("StrOpt does not accept a nil value.")
}
}
func main() {
Foo(StrOptVal("hello world"))
Foo(StrOptNone{})
}
在 playground 中测试它。
我希望建模一个可以有两种可能形式的值:不存在或字符串。
自然的方法是使用 Maybe String
,或 Optional<String>
,或 string option
,等等。但是,Go 没有这样的变体类型。
然后我想,根据 Java、C 等,替代方案是可空性,或者 Go 中的 nil
。但是,nil
不是 Go 中 string
类型的成员。
搜索,然后想到使用*string
类型。这可以工作,但看起来很尴尬(例如,我不能像获取结构文字的地址那样获取字符串文字的地址)。
在 Go 中为这样的值建模的惯用方法是什么?
您可以为此使用 sql.NullString
, but I personally would stick to *string
. As for awkwardness, it's true that you can't just sp := &"foo"
unfortunately. But there is a
func strPtr(s string) *string {
return &s
}
对 strPtr("foo")
的调用应该是内联的,所以它实际上是 &"foo"
.
另一种可能性是使用 new
:
sp := new(string)
*sp = "foo"
一个合乎逻辑的解决方案是使用 Ainar-G 提到的 *string
。 int64
但同样适用于 string
)。包装器是另一种解决方案。
仅使用 string
可选的 string
表示 string
加上 1 个特定值(或状态)表示 "not a string"(但 null
)。
这 1 个特定值可以存储(发出信号)在另一个变量中(例如 bool
),您可以将 string
和 bool
打包到 struct
我们到达了包装器,但这不适合 "using just a string
" 的情况(但仍然是一个可行的解决方案)。
如果我们只想坚持 string
,我们可以从 string
类型的可能值中取出 1 个特定值(其中有 "infinity" 个可能值作为长度不受限制(或者可能是因为它必须是 int
但没关系)),我们可以将这个特定值命名为 null
值,该值表示 "not a string" .
表示null
最方便的值是string
的零值,也就是空的string
:""
。将此指定为 null
元素的便利之处在于,每当您创建一个 string
变量而未明确指定初始值时,它将使用 ""
进行初始化。此外,当从 map
中查询值为 string
的元素时,如果键不在 map
.
""
此解决方案适用于许多现实生活中的用例。例如,如果可选的 string
应该是一个人的名字,一个空的 string
并不真正意味着一个有效的人名,所以你不应该首先允许它。
当然,在某些情况下,空 string
确实表示 string
类型变量的有效值。对于这些用例,我们可以选择另一个值。
在 Go 中,string
实际上是一个只读字节片。请参阅博客 post Strings, bytes, runes and characters in Go,其中详细解释了这一点。
所以 string
是一个字节片,在有效文本的情况下是 UTF-8 编码字节。假设您想在可选的 string
中存储一个有效文本(如果您不想,那么您可以只使用 []byte
而不是它可以具有 nil
值),您可以选择string
值表示无效的 UTF-8 字节序列,因此您甚至不必做出妥协来从可能的值中排除有效文本。最短的无效 UTF-8 字节序列只有 1 个字节,例如 0xff
(还有更多)。注意:您可以使用 utf8.ValidString()
函数来判断 string
值是否为有效文本(有效的 UTF-8 编码字节序列)。
您可以将此异常值设置为 const
:
const Null = "\xff"
这么短也意味着检查 string
是否等于这个会非常快。
按照这个约定,你已经有了一个可选的 string
,它也允许空的 string
.
在 Go Playground 上试用。
const Null = "\xff"
func main() {
fmt.Println(utf8.ValidString(Null)) // false
s := Null
fmt.Println([]byte(s)) // [255]
fmt.Println(s == Null) // true
s = "notnull"
fmt.Println(s == Null) // false
}
对于接口类型,您可以使用更自然的赋值语法。
var myString interface{} // used as type <string>
myString = nil // nil is the default -- and indicates 'empty'
myString = "a value"
引用值时,通常需要
// checked type assertion
if s, exists := myString.(string); exists {
useString(s)
}
此外,由于 stringers,在某些情况下 'optional' 类型将被自动处理——这意味着您不需要显式转换值。 fmt
包使用此功能:
fmt.Println("myString:",myString) // prints the value (or "<nil>")
警告
赋值时没有类型检查。
在某些方面,这是比处理指针更简洁的方法。但是,因为它使用接口类型,所以它不限于持有特定的底层类型。风险在于您可能无意中分配了不同的类型——这将被视为与上述条件中的 nil
相同。
下面是使用接口进行赋值的演示:
var a interface{} = "hello"
var b = a // b is an interface too
b = 123 // assign a different type
fmt.Printf("a: (%T) %v\n", a, a)
fmt.Printf("b: (%T) %v\n", b, b)
输出:
a: (string) hello b: (int) 123
请注意,接口是通过重复分配的,因此 a
和 b
是不同的。
你可以使用 Go 惯用的接口:
type (
// An interface which represents an optional string.
StrOpt interface{ implStrOpt() }
StrOptVal string // A string value for StrOpt interface.
StrOptNone struct{} // No value for StrOpt interface.
)
func (StrOptVal) implStrOpt() {} // implement the interface
func (StrOptNone) implStrOpt() {}
这就是您的使用方式:
func Foo(maybeName StrOpt) {
switch val := maybeName.(type) {
case StrOptVal:
fmt.Printf("String value! -> %s\n", string(val))
case StrOptNone:
fmt.Println("No value!")
default:
panic("StrOpt does not accept a nil value.")
}
}
func main() {
Foo(StrOptVal("hello world"))
Foo(StrOptNone{})
}
在 playground 中测试它。