你必须修复你的断言 (Zig)
恐惧是精神和代码库的杀手……一个讨论平台上的用户写道:我认为“在生产环境中禁用断言”是一种相当常见的技术,是吗?据我所知,这可能是正确的说法,但我认为这是一种无法挽回的坏习惯。我们首先来看看背景,因为这个讨论源于Zig中的std.debug.assert是如何工作的。一般来说,断言是一行代码,向程序引入一个新事实,例如“这个参数永远不会为null”或“这个整数永远不会是偶数”,它们的样子大致是这样的:assert ( my_arg != null ); assert ( my_num % 2 != 0 ); 如果你的类型系统可以用来强制执行其中一个约束,那么你可能会想使用你语言中的那些工具,而不是断言。例如,在Zig中,普通指针(例如*Foo)永远不会为null,而可选指针(?*Foo)可以,但它们也强迫你在可以访问值之前进行检查(而且Zig有专门的习惯用法)。断言可以用来明确声明代码中的前/后条件和不变量。这是有用的,因为如果你选择了好的断言,它们能够更好地保护你免受编程错误,尤其是在你对代码进行模糊测试时。一条断言的价值相当于一千个单元测试(如果你进行模糊测试,价值更是毫无疑问),但这是一个后续文章中的故事。Zig中的断言 Zig中的断言基于unreachable,这是一种标记无效代码路径的语言特性。const Op = enum { a , b , c } ; fn execute ( orig_op : Op ) void { var op = orig_op ; if ( op == . a ) { op = . b ; // 将.a转换为.b } const op_cost = switch ( op ) { . a => unreachable , // 绝对无法到达 . b => 50 , . c => 100 , } ; // 完成 op } 在这个例子中,.a的情况始终被if语句转换为.b的情况,这意味着,一旦我们到达switch,进入.a的情况就是不可能的。unreachable的另一个有趣的属性是,它可以用作一个语句,但在任何期待表达式(任何类型)的地方也是有效的。在上面的例子中,我们正在计算一个操作的“成本”,而且可能.a根本没有相关的成本。多亏了unreachable,我们甚至不需要为一个无论如何都不可能发生的情况想出一个尴尬的占位符值。Zig的标准库assert函数也利用了unreachable,具体实现如下:pub fn assert ( ok : bool ) void { if ( ! ok ) unreachable ; // 断言失败 } 构建模式 Zig有多种构建模式:Debug、ReleaseSafe、ReleaseFast和ReleaseSmall。这并不是一个对你的程序全局适用的设置:每个依赖项都可以在不同的模式下构建,你甚至可以在单个函数内使用@setRuntimeSafety实现块级粒度。 quando um assert é acionado, o "comportamento ilegal" ocorre. Modos verificados (Debug, ReleaseSafe, @setRuntimeSafety(true)) garantem uma falha do seu programa por pânico, enquanto modos não verificados (ReleaseFast, ReleaseSmall, @setRuntimeSafety(false)) resultam em "comportamento ilegal não verificado". Em resumo, comportamento ilegal não verificado significa que o programa se comportará de maneira inadequada. Neste exemplo específico, o que acontece hoje é que a instrução switch que atribui um valor a op_cost "cai" em um dos outros casos, devido a como o código de máquina é gerado. Mas isso não é garantido, e uma versão diferente do compilador pode gerar código de máquina que causa um comportamento imprevisível diferente. Aqui está um link do godbolt para que você possa ver por si mesmo. Esta é uma ferramenta afiada, mas é o que alimenta muitas otimizações poderosas; por exemplo, no nosso caso, o código de máquina necessário para implementar o primeiro ramo da instrução switch foi essencialmente eliminado do executável final. Aqui está outro link do godbolt onde você pode ver como um assert interage com a instrução switch subsequente em ReleaseSafe e ReleaseFast (observe como no ReleaseFast a função ignora todas as comparações e apenas retorna verdadeiro). Este é o tipo de coisa em que videogames e outras aplicações de mídia em tempo real confiam massivamente. Nem todo assert levará a um aumento de desempenho, mas compiladores otimizadores têm a capacidade de propagar informações não alcançáveis, resultando em otimizações não locais que podem ser difíceis de antecipar como programador. Asserts do Zig não são macros Ao abordar o Zig, uma coisa que surpreende especialmente os desenvolvedores de C/C++ é o fato de que std.debug.assert não é uma macro (e para sua informação, o Zig não tem macros). Nessas linguagens, é comum desativar as afirmações de uma maneira que, essencialmente, atua como se cada chamada à assert tivesse sido comentada, incluindo qualquer expressão passada para a macro. Isso significa que em C/C++ você nunca deve colocar uma expressão com efeitos colaterais em uma chamada de assert, pois toda essa operação será comentada quando as asserções forem desativadas. No Zig, isso simplesmente não é uma preocupação, pois std.debug.assert é uma função normal.
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡