我希望建模一个可以有两种可能形式的值:absent或string。
执行此操作的自然方式是Maybe String
,Optional<String>
或string option
等。但是,Go没有这样的变体类型。
然后我想,在Java,C等之后,替代方案是可空性,或者在Go中nil
。但是,nil
不是Go中的string
类型的成员。
搜索,然后我想使用类型*string
。这可能有效,但看起来很尴尬(例如,我不能采用字符串文字的地址,就像我可以获取结构文字的地址一样)。
在Go中模拟这样一个值的惯用方法是什么?
答案 0 :(得分:7)
您可以使用类似sql.NullString
的内容,但我个人会坚持*string
。至于尴尬,不幸的是,你不能只是sp := &"foo"
。但是有一个workaround:
func strPtr(s string) *string {
return &s
}
应该内联对strPtr("foo")
的调用,因此它有效&"foo"
。
另一种可能性是使用new
:
sp := new(string)
*sp = "foo"
答案 1 :(得分:6)
逻辑解决方案是使用Ainar-G提到的*string
。 This other answer详细说明了获取指向值的指针的可能性(int64
,但同样适用于string
)。包装器是另一种解决方案。
string
可选string
表示string
加1个特定值(或州),表示“不是字符串”(但是null
)。
这1个特定值可以存储(发信号通知)在另一个变量(例如bool
)中,您可以将string
和bool
打包到struct
中到达包装器,但这不适合“只使用string
”(但仍然是一个可行的解决方案)。
如果我们只想坚持string
,我们可以从string
类型的可能值中取出1个特定值(由于长度不受限制,因此具有“无穷大”可能值(或者可能它必须是int
但是没关系)),我们可以将此特定值命名为null
值,该值表示“不是字符串”。
指示null
的最方便的值是string
的零值,即空string
:""
。指定此null
元素具有以下便利:无论何时创建string
变量而未明确指定初始值,它都将使用""
进行初始化。此外,如果密钥不在map
中,则查询值为string
的{{1}}中的元素也会产生""
。
此解决方案适合许多实际使用案例。例如,如果可选的map
应该是某个人的姓名,则空string
并不代表有效的人名,因此您不应该首先允许这样做。
当空string
确实代表string
类型变量的有效值时,可能会出现这种情况。对于这些用例,我们可以选择其他值。
在Go中,string
实际上是一个只读的字节切片。请参阅博文Strings, bytes, runes and characters in Go,详细解释了这一点。
因此string
是一个字节片,在有效文本的情况下是UTF-8编码的字节。假设您要在可选string
中存储有效文本(如果不这样做,那么您可以使用string
而不是[]byte
值),您可以选择nil
值表示无效的UTF-8字节序列,因此您甚至不必妥协以从可能的值中排除有效文本。最短的无效UTF-8字节序列仅为1个字节,例如string
(还有更多)。注意:您可以使用utf8.ValidString()
函数来判断0xff
值是否为有效文本(有效的UTF-8编码字节序列)。
您可以将此特殊值设为string
:
const
做这个简短也意味着检查const Null = "\xff"
是否等于此是非常快的
根据此约定,您已经有一个可选的string
,它也允许空string
。
在Go Playground上尝试。
string
答案 2 :(得分:0)
使用接口类型,您可以使用更自然的赋值语法。
var myString interface{} // used as type <string>
myString = nil // nil is the default -- and indicates 'empty'
myString = "a value"
引用该值时,通常需要type assertion才能使检查明确。
// checked type assertion
if s, exists := myString.(string); exists {
useString(s)
}
此外,由于存在stringers,在某些上下文中将自动处理“可选”类型-这意味着您无需显式转换值。 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
是不同的。