golang中一种不常见的switch语句写法示例详解

发布时间:2023-05-06

  最近翻开源代码的时候看到了一种很有意思的switch用法,分享一下。

  注意这里讨论的不是typed switch,也就是case语句后面是类型的那种。

  直接看代码:

  

  

?

  

1

  

2

  

3

  

4

  

5

  

6

  

7

  

8

  

9

  

10

  

11

  

12

  

13

  

14

  

15

  

16

  

17

  

18

  

19

  

20

  

21

  

22

  

23

  

24

  

25

  

26

  

27

  

28

  

func(s *systemd) Status() (Status, error) {

  

exitCode, out, err := s.runWithOutput(systemctl, is-active, s.unitName())

  

ifexitCode == 0 err != nil{

  

returnStatusUnknown, err

  

}

  

switch{

  

casestrings.HasPrefix(out, active):

  

returnStatusRunning, nil

  

casestrings.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())

  

ifexitCode == 0 err != nil{

  

returnStatusUnknown, err

  

}

  

ifstrings.Contains(out, s.Name) {

  

// unit file exists, installed but not running

  

returnStatusStopped, nil

  

}

  

// no unit file

  

returnStatusUnknown, ErrNotInstalled

  

casestrings.HasPrefix(out, activating):

  

returnStatusRunning, nil

  

casestrings.HasPrefix(out, failed):

  

returnStatusUnknown, errors.New(service in failed state)

  

default:

  

returnStatusUnknown, 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的分支。

  它等价于下面这段:

  

  

?

  

1

  

2

  

3

  

4

  

5

  

6

  

7

  

8

  

9

  

10

  

11

  

12

  

13

  

14

  

15

  

16

  

17

  

18

  

19

  

20

  

21

  

22

  

23

  

24

  

25

  

26

  

27

  

28

  

29

  

func(s *systemd) Status() (Status, error) {

  

exitCode, out, err := s.runWithOutput(systemctl, is-active, s.unitName())

  

ifexitCode == 0 err != nil{

  

returnStatusUnknown, err

  

}

  

ifstrings.HasPrefix(out, active) {

  

returnStatusRunning, nil

  

}

  

ifstrings.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())

  

ifexitCode == 0 err != nil{

  

returnStatusUnknown, err

  

}

  

ifstrings.Contains(out, s.Name) {

  

// unit file exists, installed but not running

  

returnStatusStopped, nil

  

}

  

// no unit file

  

returnStatusUnknown, ErrNotInstalled

  

}

  

ifstrings.HasPrefix(out, activating) {

  

returnStatusRunning, nil

  

}

  

ifstrings.HasPrefix(out, failed) {

  

returnStatusUnknown, errors.New(service in failed state)

  

}

  

returnStatusUnknown, ErrNotInstalled

  

}

  

  

  可以看到,光从可读性上来说的话两者很难说谁更优秀;两者同样需要注意把常见的情况放在最前面来减少不必要的匹配(这里的switch-case不能像给整数常量时那样直接进行跳转,实际执行和上面给出的if语句是差不多的)。

  那么我们再来看看两者的生成代码,通常我不喜欢去研究编译器生成的代码,但这次是个小例外,对于执行流程上很接近的两段代。

注册即送1000元现金券