Go中recover恢复panic后,函数返回值行为是否符合语言规范?
严格来说,调用f1()或f2()会返回什么值?
// T为某一类型 func f1() T { defer func() { if r := recover(); r != nil { fmt.Println("recover") } }() g() //触发panic return something }
// T为某一类型 func f2() (v T) { defer func() { if r := recover(); r != nil { fmt.Println("recover") } }() v = X //赋值操作 g() //触发panic return something }
经测试,f1()返回T的零值,f2()返回X。
问题
该行为是由Go语言规范保证的,还是属于未定义行为(UB)?(后者可表述为:是否允许Go编译器实现为让f1()返回例如T类型的随机值?)
参考Go语言规范中「处理panic」章节的内容:
recover函数允许程序管理触发panic的goroutine的行为。假设函数G延迟执行函数D,D调用了recover,且在G所在的同一goroutine中,某函数触发了panic。当延迟函数执行到D时,D调用recover的返回值为传递给panic调用的值。如果D正常返回,未触发新的panic,则panic序列停止。此时,G与panic调用之间的函数调用状态会被丢弃,程序恢复正常执行。G中在D之前延迟的函数会被执行,G的执行将终止并返回给其调用者。
该章节未明确说明G的调用者会收到什么具体返回值,但结合Go语言的其他规范规则,这个行为是被明确保证的,并非未定义行为:
无命名返回值的情况(f1):
Go语言中所有变量(包括函数返回值对应的内存空间)都会被默认初始化为对应类型的零值。f1()在触发panic时,return something语句并未执行,函数的返回值空间始终保持初始的零值状态。当panic被恢复后,函数直接终止并返回这个零值,编译器无法返回随机值。命名返回值的情况(f2):
命名返回值v在函数启动时就完成了初始化,后续执行的v = X已经修改了它的值。触发panic时,return something语句同样未执行,但命名返回值的当前状态(即X)会被保留。当panic被恢复后,函数终止并返回这个已赋值的状态,这符合Go语言对命名返回值生命周期的定义。
综上,f1()返回T的零值、f2()返回X的行为是Go语言规范保证的,不属于未定义行为,编译器不能随意返回其他值。
内容的提问来源于stack exchange,提问作者ynn

