Go语言."空白标识符"
其实前面讨论过“常量千位分隔符”,如果结合函数的参数特征
一、_ 是 Go 的"空白标识符":我不要这个值
func(_ string, _ mustache.RenderFunc) (string, error)
// ↑ ↑
// 第1个参数不用 第2个参数不用
_(下划线)是 Go 里专门的垃圾桶/黑洞标识符,含义是"这个位置有个值,但我明确声明不用它"。这里函数签名规定必须有两个入参(text string 和 render RenderFunc,mustache 库回调时会传进来),但函数体里只想要当前时间,两个参数都用不上,就用 _ 占位。
等价的"老实写法"是给它们起名字:
func(text string, render mustache.RenderFunc) (string, error) {
_ = text // 不用也得显式丢掉
_ = render
return time.Now().Format(time.DateTime), nil
}
用你的老语言对照:
- Delphi:没有专门符号,参数写出来不用就行(顶多编译器 hint "Parameter never used")
- C#:C# 9 起也有
_弃元(discard),Func<string, RenderFunc, (string, Exception)> f = (_, _) => ...,语义一模一样
一个重要细节:Go 里未使用的函数参数是允许的,不像未使用的局部变量那样编译报错。所以这里写 _ 不是语法强制,而是给读代码的人(和自己)发信号——"我知道签名要求这两个参数,我是故意不用的",比起 text1、foo 这种废名字干净。_ 在 Go 里的其他常见出场:
v, _ := os.Stat(full) // 忽略 error(只在确定无足轻重时!)
for _, name := range list // 忽略下标,只要值
_ = someInterface(t) // 编译期接口实现断言,丢弃结果
二、(string, error) 是返回值列表:Go 函数可以返回多个值
func(...) (string, error) {
return time.Now().Format(time.DateTime), nil
// └────── 第1个:string ──────┘ └ 第2个:error,nil 表示没错误
}
Go 的函数可以一次返回多个值,这是一等语法,不需要像 C# 那样包成 Tuple<string, Exception> 或用 out 参数。约定俗成的模式是:最后一个返回值是 error,成功时它必须是 nil。你项目里到处是这个形态:
body, err := r.renderFile(file, ctx) // (string, error)
html, err := io.ReadAll(...)
tmpl, err := mustache.ParseStringPartials(...)
调用方的标准处理姿势(你在 render.go 里见过很多次):
info, err := os.Stat(full)
if err != nil {
return "", err // 失败:第1个返回无意义的零值(空串),第2个带错误
}
对照你的经验:
| 成功值 + 错误的表达 | |
|---|---|
| Delphi | 函数返回值 + out 参数,或抛异常 |
| C# | 返回值 + throw/out/Tuple/Result<T> |
| Go | 多返回值 (T, error),不抛异常 |
Go 刻意不用异常做常规错误处理,而是把错误当普通返回值逼着调用方显式检查,这就是为什么代码里 if err != nil 特别多——初看啰嗦,实际让"哪一步可能失败、失败后怎么走"在代码里完全显式。
返回值还可以起名字,叫命名返回值(类似隐式 out 参数,defer 里常配合 recover 改写):
func nowText() (s string, err error) {
s = time.Now().Format(time.DateTime)
return // 裸 return,自动返回当前的 s 和 err(此时是零值 nil)
}
本项目没有用到这种写法,知道即可。
小结这行:_ = 两个入参我故意不用;(string, error) = 返回一个字符串和一个错误,这里返回时间串和 nil(无错误)。整句连起来读就是:"一个接收文本和渲染器但都不使用、返回当前时间字符串且永不报错的无名函数"。
浙公网安备 33010602011771号