控制流结构是高级编程语言有别于汇编的原因之一. 它允许我们在抽象层面, 有条理地控制程序流程. 毫无疑问, 所有高级语言都支持控制流结构, 否则, 我还说个毛啊. 可惜, 有那么几种相当不错的控制流结构 Go 不支持.
出色的解决方案:模式匹配和复合表达式模式匹配配合数据结构或值使用的时候, 效果相当好. 简直就是 case/switch 的加强版. 我们可以像这样对值进行匹配:
(Rust)
match x { 0 | 1 => action_1(), 2 .. 9 => action_2(), _ => action_3() };或者像这样解构数据结构(deconstruct data structures):
(Rust)
deg_kelvin = match temperature { Celsius(t) => t + 273.15, Fahrenheit(t) => (t - 32)/1.8 + 273.15 };上面的例子, 有时也称作复合表达式. C 和 Go 中的 if 和 case/switch 语句只用来控制程序流程, 不会返回值; 而 Rust 和 Haskell 的 if 和 模式匹配语句则可以. 既然有值返回, 当然也能用来赋给其他东东. 这里给出一个 if 语句的例子:
(Haskell)
x = if (y == "foo") then 1 else 2 Go 的方案: C语言风格的无值语句( Valueless Statements)不是我故意找 Go 的茬; 它确实有几个不错的的控制流元素, 如, 用于并行计算的 select. 可惜没有我钟爱的复合表达式和模式匹配. Go 唯一支持赋值的语句, 是像这样的原子表达式 x := 5 或 x := foo().
嵌入式编程给嵌入式系统编写程序与在一个有完整操作系统的计算机上编写程序有很大不同。某些语言相比而言更适合嵌入式编程的需要。
对于不少人赞成Go语言可以给机器人编程这件事我很疑惑。基于一些原因,Go语言并不适合用来为嵌入式系统编写程序。这一节并不是对Go语言的指责,Go语言并不是被设计用来编写嵌入式程序的语言。这一章节针对那些吹捧Go语言可以胜任嵌入式编程的人。
子问题 #1:堆和动态内存分配
堆是一块在运行期创建的可以存储任意数量对象的内存区域。我们将对堆的使用称作”动态内存分配“。
通常,在嵌入式系统中使用堆存储空间是不明智的。较大的内存开销和需要管理复杂的数据结构是主要的原因,尤其是当你在一块主频只有8MHz,RAM只有2KB的MCU上写程序的时候。
在实时系统(因为某一操作耗时过长就可能会跪的系统)中使用堆也是不明智的,因为对堆上空间的申请和释放所消耗的时间有很大的不确定性。举个例子,如果你的MCU正在控制一个火箭的引擎,就在这时,如果一个对栈空间的申请比平常多消耗了几百毫秒,导致对阀门的错误计时,就会发生大爆炸。
还有一些原因致使动态内存分配对嵌入式编程没有多大用。例如,许多使用堆的语言同时也拥有垃圾收集机制。垃圾收集机制经常会暂停整个程序一会儿,在堆上寻找垃圾(不再被程序使用的内存)并清除它们。这比单纯的堆空间申请更加具有不确定性。
好的解决方案:让动态内存分配成为可选项
Rust语言的标准库中有很多特性依赖于堆。然而,Rust语言的编译器支持完全关闭这些有关堆的语言特性,并且能够静态地确保这些特性在程序中不被使用。写出完全不使用堆的Rust程序是完全可行的。
Go语言的解决方案:没有
Go语言严重依赖于对堆的运用。没有可行的方式让Go程序完全不使用堆。这不是Go语言的问题。这在Go语言的目的应用领域完全没有问题。
Go并不是一门实时的语言,通常我们不能担保合理复杂的Go程序的执行时间。这可能有点费解,我来解释一下:Go相对而言很快,但不是实时的,这两个概念非常不同。执行速度快对嵌入式程序来说很重要,但是真正重要的是能否担保某些操作的最大执行时间,而这恰恰是Go不能预测的。这个问题有很大一部分是Go语言对于堆空间和垃圾收集机制的使用造成的。