Carbon 语言逻辑运算符设计解析:and / or / not 的语法、优先级与实现
2026/9/11 8:39:25 网站建设 项目流程

Carbon 语言逻辑运算符设计解析:and / or / not 的语法、优先级与实现

【免费下载链接】carbon-langCarbon Language's main repository: documents, design, implementation, and related tools. (NOTE: Carbon Language is experimental; see README)项目地址: https://gitcode.com/GitHub_Trending/ca/carbon-lang

本篇文章基于 Carbon Language 官方提案 p000680-and-or-not,系统梳理 Carbon 在布尔逻辑运算上的核心设计决策:为何选择andornot三个关键字而非&&||!,这三者的优先级、结合性、类型转换与重载规则如何定义,以及这些规则最终如何在 Carbon 工具链的词法、语法与语义检查阶段落地。读完本文,你将理解该设计背后的工程权衡,并能据此正确编写 Carbon 条件表达式,避免常见的优先级与短路陷阱。

问题背景:Carbon 需要布尔逻辑运算

布尔逻辑的与、或、非(AND、OR、NOT)是几乎所有编程语言处理条件判断的基础构件。Carbon 作为一门面向 C++ 迁移与互操作的新语言,同样必须提供这三个运算符。提案在 Problem 一节明确指出:逻辑 AND、OR、NOT 是使用 Boolean 值工作的重要积木,Carbon 应当支持它们。

不过,"支持"并不等于"照搬 C++"。围绕用什么拼写优先级怎么定短路语义如何实现,提案展开了详细的背景调查与方案对比,这正是本文要展开的核心内容。

业界现状:两种主流的写法流派

提案在 Background 中梳理了主流编程语言的两种做法:

流派一:&&||!(源自 C)

  • &&||最早由 C 语言引入,其目的正是为了与普通位运算运算符区分开,明确表达短路求值(short-circuiting)行为。
  • 如今 C、C++、C#、Swift、Rust、Haskell 等大量语言都采用这套写法,且&&||一律短路求值。

流派二:andornot(偏重可读性与脚本风格)

  • Python、Pascal、Nim、SQL 以及各种 BASIC 变体常使用关键字拼写。
  • 短路行为因语言而异:Python 的and/or短路;Pascal 与 Visual Basic 本身不短路,但通过and then/or else(Pascal)与AndAlso/OrElse(Visual Basic)提供短路版本。
  • C++ 直接把andornot识别为关键字,作为&&||!词法同义词;C 语言则通过标准头文件<iso646.h>以宏形式提供同样的拼写。
  • Perl、Ruby、Raku 两种写法都支持,其中标点形式优先级更高、关键字形式优先级更低,两者都短路;Raku 还提供andthen/orelse,区别在于短路时产出的值不同,而非是否短路。
  • Zig 提供andor!的混合组合。

标点运算符的已知痛点

提案特别列举了标点写法带来的真实风险,这也是 Carbon 最终转向关键字拼写的重要动因:

  1. &&&|||极易混淆:当逻辑运算符与位运算符并存时,手误将&&写成&是常见错误来源。业界有明确的安全规则(如 CERT 规则 EXP46-C 建议不要在布尔型操作数上使用位运算符);提案还引用了 2021 年一起真实事故:ChromiumOS 因&&/&手误导致登录功能故障。
  2. !难以辨认:有轶事证据表明,在某些上下文里(尤其紧邻(Il1等形状相近字符时),部分读者很难看清!这个字符。

提案核心:三个关键字形式的逻辑运算符

提案 Proposal 给出的方案非常简洁,Carbon 提供三个操作符用于 Boolean 值的逻辑运算:

  • and:提供短路的逻辑与(logical AND)运算。
  • or:提供短路的逻辑或(logical OR)运算。
  • not:提供逻辑非(logical NOT)运算。

其中andor是中缀二元运算符,not是前缀一元运算符。值得注意的一个细节是:这三个拼写在 C++ 中本就是含义相同的关键字,Carbon 采用它们不会与合法 C++ 标识符发生冲突,因而允许开发者在既有 C++ 代码库中提前采用 Carbon 风格语法(提案当时还配套提供了将该写法应用到 Carbon 项目 C++ 代码中的演示性 PR)。

细节设计:优先级、结合性、转换与重载

提案的 Details 一节给出了四个维度的精确规则。

优先级:极低,且 and / or 之间没有优先级关系

  • andornot的优先级非常低。当一个表达式作为if条件且不加括号地使用这些运算符时,它们总是该表达式中优先级最低的运算符。
  • 任何可能用于构造布尔值的合理运算符都可作为其子表达式,特别是比较运算符(如<==)优先级高于所有逻辑运算符
  • not可以出现在and/or内部,但andor不能在没有括号的情况下互相直接嵌套

官方给出的示例与等价加括号形式:

if (n + m == 3 and not n < m) {

等价于:

if (((n + m) == 3) and (not (n < m))) {

而下述两种写法都是错误的,必须加括号:

if (cond1 == not cond2) { // ... if (cond1 and cond2 or cond3) {

这里的关键设计点是:andor之间不建立任何优先级关系。主流语言普遍规定&&高于||(见下文备选方案分析),但提案认为该规则"在几十年间跨越多种语言仍未被相当比例的开发者可靠掌握",因此决定干脆不定义这条优先级边,强制开发者用括号表达意图。

结合性:左结合,not不可重复

  • andor都是左结合的。
  • not表达式不能作为另一个not表达式的操作数——not not b不加括号即为错误。

官方示例:

// OK if (not a and not b and not c) { ... } if (not (not a)) { ... } // Error if (not a or not b and not c) { ... } if (not not a) { ... }

注意not a or not b and not c报错,正是因为它同时混用了andor(没有括号)而触发"无优先级关系必须加括号"的规则。

类型转换:与 if 条件完全一致

andornot的操作数会按照if条件相同的方式转换为 Boolean 值。具体含义是:

  • 如果某些值(如指针、整数)不能直接作为if条件使用(必须显式与 null 或零比较),那么它们同样不能直接作为andornot的操作数。
  • 如果未来提供了决定如何对某个值进行真值分支的扩展点(例如提供到布尔类型的转换),该扩展点对andornot同样生效。

换言之,这三者的"真值判定规则"与if严格绑定,杜绝了"条件判断一套规则、逻辑运算另一套规则"的分裂。

重载:不可重载

逻辑运算符andornot不可重载。正如上文所述,任何允许类型定制if行为(如operator bool转换)的机制,都会同样作用于andornot,因此无需也不应提供独立的运算符重载入口。

设计论证:基于 Carbon 项目目标

提案在 Rationale based on Carbon's goals 一节,把上述每条设计决定都映射到了 Carbon 的两个核心目标上。

代码易读、易理解、易编写

  • and/or替代&&/||,规避了&&&的视觉混淆。
  • not替代!,规避了小号标点字符难以察觉的问题。
  • andor之间不设优先级,强制用括号表达组合,避免了两者混用时的可读性灾难。
  • 关键字而非标点的形式,强调了这些运算符不是普通运算,而是带有控制流语义(短路)的构造。
  • notandor保持相同优先级,便于视觉上快速扫描条件整体结构、识别嵌套层级。

与既有 C++ 代码的互操作与迁移

  • andornot在 C++ 中本就是含义相同的关键字,因此 Carbon 关键字不会与合法 C++ 标识符冲突,还允许在 C++ 代码库中提前采用 Carbon 语法。
  • 虽然这三个运算符在 C++ 中可重载,但&&||几乎是重载率最低的运算符,!也通常只是被当作转bool的迂回手段。目前没有已知需求要求 Carbon 代码调用 C++ 重载的operator&&/operator||/operator!。至于ifandornot调用 C++ 中(可能为explicit的)operator bool的机制,提案明确表示属于范围之外的工作。

备选方案与取舍分析

提案记录了六个被认真考虑过的备选方案,理解它们能更深入地把握最终设计的边界。

备选一:三个运算符全部使用标点拼写

即沿用 C 传统使用&&||!

  • 优点:对熟悉这套写法的开发者更亲切。
  • 缺点:① 关键字能提示and/or除计算外还影响控制流;②!对部分读者难以看清;③ 与&|并存时有混淆风险;④ 若未来要改优先级规则,不同拼写可提醒"行为与&&不同";⑤ 在多数英文键盘布局上,&&/||/!需要按 Shift 键并离开字母键区,比字母拼写更难敲。

备选二:为 AND 与 OR 建立优先级

多数语言规定 AND 高于 OR:

if (a && b || c && d) { ... } // ... 等价于 ... if ((a && b) || (c && d)) { ... }

这一规则可从布尔代数角度解释:&&相当于"乘法"、||相当于"加法",乘法通常结合得更紧。但提案引用 运算符优先级提案 #555 中"何时添加优先级边"的判定标准指出:尽管这条规则在多种语言中存在数十年,仍有相当比例开发者不能可靠掌握,业界甚至普遍建议开启编译器警告要求改写为显式括号形式——因此 Carbon 选择不添加这条优先级边

备选三:给 NOT 高优先级

可模仿 C++ 让not高优先级,但会带来不便:

var x: Bool = cond1 == not cond2; // 本提案中非法,需加括号

以及一个隐蔽的等价关系:

var y: Bool = not cond1 == cond2; // 等价于 var y: Bool = cond1 != cond2; // 可能并非开发者本意

不过,给not高优先级会得到更复杂的规则,并破坏andornot三者间的对称性。

备选四:NOT 使用标点!

not在操作数含括号嵌套(内部又有and/or)时可能不如!易读,且没有!运算符会让!=的拼写缺少一致性论据。但反方理由更充分:破坏三者对称;Python 就用这套关键字且同样有!=,实践上未见混淆;not在部分场景更易读、更易从上下文跳出;!已在泛型领域被用于表示早期替换,避免同一语法承担多重职责;函数式的not (...)!(...)更符合读者预期。

备选五:两种 NOT 并存

同时提供低优先级not和高优先级!,似乎"两头都占"。但代价是:两种形式必有一种被闲置,或需要额外风格规则指导何时用哪个;而且只提供!却不提供&&||会显得突兀。

备选六:允许重复 NOT

允许not not x作为把x转成 Boolean 的惯用法。但提案认为将来会有更清晰的转换语法(如x as Bool),届时not not x反而会成为反模式,甚至可能暗示"多敲了一个 not"的 bug。依据 运算符优先级提案 #555 的"存疑时不加规则、等待真实世界经验"原则,Carbon 最终让not not b成为必须加括号的错误。

备选七:AND/OR 产出"决定性值"

即让and/or返回(未转换的)决定结果的那个值,而非 Boolean。例如对可空指针pqp or qp非空时产出p,否则产出q。这是 Python、Perl、Raku 的做法,也是 C++ 的std::conjunction/std::disjunction的做法。优点是可语法化地求"列表中的第一个 truthy 值或最后一个 falsy 值";缺点同样明显:结果传入函数调用时更难读懂、要求链式条件各分支有公共类型(否则要兜底规则)、与类型推导结合时容易推出意外类型(如var x: auto = p and q;推导出指针类型而非 Boolean)。Carbon 因此坚持产出 Boolean 值。

在 Carbon 工具链中的落地实现

回到当前仓库源码,可以看到上述设计已经在工具链中完整落地,这为文档中的规则提供了实现级佐证。

词法层:三个关键字 token

andornot在词法阶段被注册为关键字。在 token_kind.def 中可以看到CARBON_KEYWORD_TOKEN(And, "and")(第 177 行)、CARBON_KEYWORD_TOKEN(Not, "not")(第 208 行)与CARBON_KEYWORD_TOKEN(Or, "or")(第 211 行)。同时,符号表中保留着!Exclaim)与!=ExclaimEqual)等符号 token,但!在 precedence.cpp 中被明确列为"将来可能是运算符"的符号,而非当前逻辑非运算符——与提案"避免!承载多重职责"的意图一致。

语法层:优先级与结合性的精确建模

优先级规则的核心实现在 precedence.cpp:

  • 第 32 行MarkHigherThan({Relational, LogicalPrefix}, {LogicalAnd, LogicalOr})明确声明比较运算符(Relational)与逻辑前缀(LogicalPrefix,即not)优先级高于逻辑与/逻辑或,正是提案中"比较运算符高于所有逻辑运算符"与"not可用于and/or内部"的代码化表达。
  • not在前缀解析中映射为LogicalPrefix(第 154-155 行),andor在中缀解析中分别映射为LogicalAndLogicalOris_binary = true(第 200-203 行)。
  • 结合性方面,第 120-123 行把LogicalAndLogicalOr的对角线填充为LeftFirst(左结合);而LogicalAndLogicalOr之间、以及LogicalPrefix自身的对角线没有填充,对应第 126-127 行注释"对于其他运算符,要求显式括号"。

这一实现与提案规则一一对应:a and b and c合法且左结合;a and b or c报错要求加括号;not not a报错要求加括号。

语法测试文件也固化了这些行为,例如:

  • fail_precedence_and_or.carbon:a and b or c;触发OperatorRequiresParentheses诊断,并给出了完整语法树(ShortCircuitOperatorAndShortCircuitOperatorOr节点均带has_error)。
  • associative.carbon:a and b and c;被解析为左结合结构(a and b) and c
  • 同目录下还有对称的fail_precedence_or_and.carbon等测试,覆盖or/and反向混用、as转换、赋值等场景的优先级边界。

语义层:短路求值如何实现

短路语义在语义检查阶段的 handle_operator.cpp 中实现:

  • HandleShortCircuitOperand(第 407 行起)为短路操作数建立基本块,并通过AddDominatedBlockAndBranchIf生成条件分支,这从实现上印证了and/or是"带控制流语义"的构造,而非普通函数调用。
  • HandleShortCircuitOperator(第 459 行起)负责合并分支结果,对andor共用同一套处理逻辑,仅通过is_or参数区分。
  • not则生成SemIR::UnaryOperatorNot指令(第 331 行),作为一元运算符直接求值。

从源码结构可以推断,and/or的短路通过显式支配块(dominated block)与分支指令完成:左操作数先求值,据此决定是否继续求值右操作数,最终汇合点产出 Boolean 值。这与提案"操作数按if条件方式转换为 Boolean"的规定共同保证了语义一致性。

结语

Carbon 对逻辑运算的设计是一份"少即是多"的范本:用and/or/not三个关键字换取可读性与 C++ 互操作的便利,用"不定义and/or之间的优先级"换取无歧义的表达式,用"与if共享转换规则、不可重载"换取统一的真值语义。如果你想亲手验证这些规则,可以直接阅读并运行 toolchain/parse/testdata/operators/ 目录下的语法测试(例如bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/parse/testdata/operators/fail_precedence_and_or.carbon),或在语义检查源码中追踪ShortCircuit相关处理逻辑,体验一份提案从纸面规则到编译器实现的完整闭环。

【免费下载链接】carbon-langCarbon Language's main repository: documents, design, implementation, and related tools. (NOTE: Carbon Language is experimental; see README)项目地址: https://gitcode.com/GitHub_Trending/ca/carbon-lang

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询