最近翻开源代码的时候看到了一种很有意思的switch用法,分享一下。
注意这里讨论的不是typed switch
,也就是case语句后面是类型的那种。
直接看代码:
func (s *systemd) Status() (Status, error) {
exitCode, out, err := s.runWithOutput("systemctl", "is-active", s.unitName())
if exitCode == 0 && err != nil {
return StatusUnknown, err
}
switch {
case strings.HasPrefix(out, "active"):
return StatusRunning, nil
case strings.HasPrefix(out, "inactive"):
// inactive can also mean its not installed, check unit files
exitCode, out, err := s.runWithOutput("systemctl", "list-unit-files", "-t", "service", s.unitName())
if exitCode == 0 && err != nil {
return StatusUnknown, err
}
if strings.Contains(out, s.Name) {
// unit file exists, installed but not running
return StatusStopped, nil
}
// no unit file
return StatusUnknown, ErrNotInstalled
case strings.HasPrefix(out, "activating"):
return StatusRunning, nil
case strings.HasPrefix(out, "failed"):
return StatusUnknown, errors.New("service in failed state")
default:
return StatusUnknown, ErrNotInstalled
}
}
你也可以在这找到它:代码链接
简单解释下这段代码在做什么:调用systemctl命令检查指定的服务的运行状态,具体做法是过滤systemctl的输出然后根据得到的字符串的前缀判断当前的运行状态。
有意思的在于这个switch,首先它后面没有任何表达式;其次在每个case后面都是个函数调用表达式,返回值都是bool类型的。
虽然看起来很怪异,但这段代码肯定没有语法问题,可以编译通过;也没有语义或者逻辑问题,因为人家用的好好的,这个项目接近4000个星星不是大家乱点的。
这里就不卖关子了,直接公布答案:
- 如果
switch
后面没有任何表达式,那么它等价于这个:switch true
; - case表达式按从上到下从左到右的顺序求值;
- 如果case后面的表达式求出来的值和switch后面的表达式的值一样,那么就进入这个分支,其他case被忽略(除非用了fallthrough,但这会直接跳进下一个case的分支,不会执行下一个case上的表达式)。
那么上面那一串代码就好理解了:
- 首先是
switch true
,期待有个case能求出true这个值; - 从上到下执行
strings.HasPrefix
,如果是false就往下到下一个case,如果是true就进入这个case的分支。
它等价于下面这段:
func (s *systemd) Status() (Status, error) {
exitCode, out, err := s.runWithOutput("systemctl", "is-active", s.unitName())
if exitCode == 0 && err != nil {
return StatusUnknown, err
}
if strings.HasPrefix(out, "active") {
return StatusRunning, nil
}
if strings.HasPrefix(out, "inactive") {
// inactive can also mean its not installed, check unit files
exitCode, out, err := s.runWithOutput("systemctl", "list-unit-files", "-t", "service", s.unitName())
if exitCode == 0 && err != nil {
return StatusUnknown, err
}
if strings.Contains(out, s.Name) {
// unit file exists, installed but not running
return StatusStopped, nil
}
// no unit file
return StatusUnknown, ErrNotInstalled
}
if strings.HasPrefix(out, "activating") {
return StatusRunning, nil
}
if strings.HasPrefix(out, "failed") {
return StatusUnknown, errors.New("service in failed state")
}
return StatusUnknown, ErrNotInstalled
}
可以看到,光从可读性上来说的话两者很难说谁更优秀;两者同样需要注意把常见的情况放在最前面来减少不必要的匹配(这里的switch-case不能像给整数常量时那样直接进行跳转,实际执行和上面给出的if语句是差不多的)。
那么我们再来看看两者的生成代码,通常我不喜欢去研究编译器生成的代码,但这次是个小例外,对于执行流程上很接近的两段代码,编译器会怎么处理呢?
我们做个简化版的例子:
func status1(cmdOutput string, flag int) int {
switch {
case strings.HasPrefix(cmdOutput, "active"):
return 1
case strings.HasPrefix(cmdOutput, "inactive"):
if flag > 0 {
return 2
}
return -1
case strings.HasPrefix(cmdO