)
Carbon 语言 Lambda 与函数声明的连续语法设计proposals/p003848-lambdas 深度解析【免费下载链接】carbon-langCarbon Languages 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 仓库中的正式设计提案 proposals/p003848-lambdas.md 为核心系统讲解 Carbon 语言中 lambda 表达式与函数声明统一语法fn引入符、简洁表达式体、$0位置参数、捕获与函数字段的完整设计同时结合 toolchain/parse/handle_lambda.cpp、toolchain/parse/testdata/lambda/lambda.carbon 等实现源码说明该语法在编译器前端中的落地现状。读完本文你将掌握 Carbon 中从 lambda 到函数声明的连续语法全貌理解捕获、位置参数等核心机制的语义约束并能对照源码定位对应的解析实现与测试用例。背景与设计动机Carbon 是实验性语言其设计目标之一是Code that is easy to read, understand, and write见 docs/project/goals.md 中的对应章节同时强调与现有 C 代码的互操作与迁移Interoperability with and migration from existing C code。在 C 中lambda 在使用点定义、通常匿名如果迁移工具只能将 C lambda 机械替换为具名函数声明就必须为每个 lambda 挑选名字造成显著的可用性负担。因此Carbon 需要一种原生的 lambda 语法来承接 C lambda 的迁移场景。提案参考了三种以捕获capture为核心的语言特性——lambda 因此拥有状态和生命周期C 的 lambda 表达式[capture] (params) - ret { body }Swift 的闭包closures及其 Shorthand Argument Names简写参数名即$0/$1Rust 的闭包。这三者与 Carbon 方向最接近的共同点是捕获使函数对象持有状态、具备生命周期。本提案的核心主张是lambda 与函数声明之间采用大体连续continuous的语法——两者都用fn引入是否带名字name是区分函数声明与lambda 表达式的唯一关键其余语法对两类函数完全通用。语法概览lambda 与函数声明的对照提案首先给出lambda 写在变量里 / 用在函数调用中与函数声明两组对照示例。简洁表达式体返回类型推断为auto// 变量中 let lambda: auto fn T.Make(); // C23 等价const auto lambda [] { return T::Make(); }; // 函数调用中 Foo(10, 20, fn T.Make()); // C23 等价Foo(10, 20, [] { return T::Make(); });显式返回类型 语句体// 变量中 let lambda: auto fn - T { return T.Make(); }; // C23 等价const auto lambda [] - T { return T::Make(); }; // 函数调用中 PushBack(my_list, fn T.Make()); // C23 等价PushBack(my_list, [] { return T::Make(); });函数声明同样支持上述两种形式fn FunctionDeclaration T.Make(); // C23 等价auto FunctionDeclaration() { return T.Make(); } fn FunctionDeclaration - T { return T.Make(); } // C23 等价auto FunctionDeclaration() - T { return T::Make(); }提案强调返回表达式的函数其返回类型为auto。以下组合全部成立fn T.Make()/fn FunctionDeclaration T.Make()—— 返回表达式返回类型autofn - T { return T.Make(); }/fn FunctionDeclaration - T { return T.Make(); }—— 显式返回类型 语句体fn { Print(T.Make()); }/fn FunctionDeclaration { Print(T.Make()); }—— 有语句体但无返回值相当于 C23 的[] - void { ... }。在函数体内部lambda 与函数声明还可以使用方括号承载隐式参数支持捕获captures、函数字段fields与推导参数deduced parameters。对位于类或接口定义内部的函数声明方括号中还可以加入self: Self或addr self: Self*fn Foo(x: i32) { // 变量中 let lambda: auto fn [var x, var y: i32 0] { Print(x, y); }; // C23 等价const auto lambda [x, y int32_t{0}] mutable - void { Print(x, y); }; // 函数调用中 Foo(fn [var x, var y: i32 0] { Print(x, y); }); fn FunctionDeclaration[var x, var y: i32 0] { Print(x, y); } }函数还支持在使用点定义的位置参数positional parameters用美元符号加非负整数表示如$0隐式类型为autofn Foo() { let lambda: auto fn { Print($0); }; // C23 等价auto lambda [](auto _0, auto...) - void { Print(_0); }; // Swift 等价let lambda { Print($0) }; fn FunctionDeclaration { Print($0); } }函数当然也可以使用具名参数但同一函数不能同时混用具名参数与位置参数fn Foo() { // 变量中 let lambda: auto fn (v: auto) { Print(v); }; // 函数调用中 Foo(fn (v: auto) { Print(v); }); fn FunctionDeclaration(v: auto) { Print(v); } }在具名参数或位置参数之外推导参数deduced parameters始终允许fn Foo() { let lambda: auto fn T:! Printable { Print(t); }; fn FunctionDeclarationT:! Printable { Print(t); } }语法正式定义函数定义与 lambda 表达式具有以下两种句法形式方括号内的项均为可选项且彼此独立fn [name] [implicit-parameters] [tuple-pattern] expression [;] fn [name] [implicit-parameters] [tuple-pattern] [- return-type] { statements }其中第一种形式是第二种的简写 expression ;等价于- auto { return expression; }。关于各组成部分的语义implicit-parameters由方括号括起内部为可选的默认捕获模式以及任意数量的显式捕获、函数字段与推导参数全部以逗号分隔。默认捕获模式如有必须位于首位其余项顺序不限。若省略implicit-parameters等价于空[]。name是否出现决定这是函数定义还是 lambda 表达式。第一种形式中函数定义必须带尾部;而该分号不属于 lambda 表达式语法的一部分。tuple-pattern是否出现决定函数体使用具名参数还是位置参数。- return-type是否出现决定函数体能否且必须返回值。为了让连续语法直观可读提案给出了一张句法位置对照表将两个语法簇的每种合法组合一一列出句法位置该位置允许的语法除特别说明外均为可选A1必需返回表达式允许位置参数A2必需返回表达式禁止位置参数B默认捕获模式C显式捕获、函数字段与推导参数任意顺序D显式参数E1语句体无返回值允许位置参数E2语句体有返回值允许位置参数E3语句体无返回值禁止位置参数E4语句体有返回值禁止位置参数F必需返回类型G函数声明名称对应的lambda 合法形态全部出现在表达式上下文中自身即为表达式fn A1 fn [B, C] A1 fn (D) A2 fn B, C A2 fn { E1; } fn - F { E2; } fn [B, C] { E1; } fn [B, C] - F { E2; } fn (D) { E3; } fn (D) - F { E4; } fn B, C { E3; } fn B, C - F { E4; }对应的函数声明合法形态允许作为函数体中的语句或其他作用域中的声明fn G A1; fn G[B, C] A1; fn G(D) A2; fn GB, C A2; fn G { E1; } fn G - F { E2; } fn G[B, C] { E1; } fn G[B, C] - F { E2; } fn G(D) { E3; } fn G(D) - F { E4; } fn GB, C { E3; } fn GB, C - F { E4; }可见除名字 G 尾部;之外lambda 与函数声明共享完全相同的句法骨架这正是连续语法的含义所在。引入符统一使用fn提案规定lambda 与函数声明统一用fn关键字引入以镜像函数声明。判定规则为若一条语句或声明以fn开头则必须提供名字成为函数声明否则若处于表达式上下文fn即引入一个 lambda。let lambda1: auto fn T.Make(); let lambda2: auto fn - T { return T.Make(); }; fn FunctionDeclaration1 T.Make(); fn FunctionDeclaration2 - T { return T.Make(); }这一设计在编译器前端已经落地解析器中LambdaIntroducer节点对应的正是fn词元见 toolchain/parse/handle_lambda.cpp 中的HandleLambdaIntroducer。解析状态机依次经历LambdaAfterIntroducer→可选LambdaAfterImplicitParams→LambdaAfterParams→LambdaBody/LambdaBodyFinish最终产出NodeKind::Lambda节点。测试数据 toolchain/parse/testdata/lambda/lambda.carbon 中的basic.carbon用例展示了三种基础形态的解析树输出var a: auto fn [T: type] (x: T) - T { return x; }; var b: auto fn (x: i32) x; var c: auto fn { };其中var b一行在解析树中同时出现LambdaIntroducer fn与TerseBodyArrow 节点直观印证了简洁体在实现层面是独立语法单元。同时测试还覆盖了错误恢复路径当 lambda 缺少函数体var x: auto fn ;或返回类型之后缺少/{var x: auto fn - i32 ;时解析器分别报告ExpectedLambdaBody与ExpectedLambdaBodyAfterReturnType诊断并插入InvalidParse节点以保持 lambda 是合法表达式避免崩溃。位置参数Positional Parameters位置参数在函数体内通过美元符号加非负的位置整数引入如$3它们是所在函数的auto参数灵感来自 Swift 的 Shorthand Argument Names。要点如下可被用于**任何缺少显式参数列表圆括号**的 lambda 或函数声明天然可变参variadic缺少显式参数列表的函数可以接收任意数量的实参函数体只读取其命名的参数最高编号的参数决定了所需的最少实参数函数体可以自由省略低编号的参数例如fn { Print($10); }。典型用法是作为比较器// 接收两个位置参数、用作比较器的 lambda Sort(my_list, fn $0.val $1.val); // Swift 等价{ $0.val $1.val }位置参数的限制提案对带位置参数的函数施加两条限制函数声明的定义必须附着在声明上即不能前置声明后再另行定义位置参数只能用于恰好只有一个无显式参数列表的外层函数的上下文中。以下示例逐条演示了合法性判定fn Foo1 { fn Bar1 {} // ❌ 无效Foo1 已在使用位置参数 } fn Foo2 { Print($0); fn Bar2 {} // ❌ 无效Foo2 已在使用位置参数 } fn Foo3 { fn Bar3 { Print($0); // ❌ 无效Foo3 已在使用位置参数 } } fn Foo4() { fn Bar4 { Print($0); // ✅ 有效Foo4 有显式参数 } } fn Foo5 { fn Bar5() {} // ✅ 有效Bar5 有显式参数 } fn Foo6() { my_list.Sort( fn $0 $1 // ✅ 有效Foo6 有显式参数 ); }函数捕获Function Captures函数捕获镜像 C 的非初始化捕获non-init captures。一条捕获声明由捕获模式针对var捕获后跟外层作用域中某个绑定的名字组成使该标识符在内部函数体中可用捕获的生命周期等于其所处函数的生命周期。例如捕获外层let handle后传给线程fn Foo() { let handle: Handle Handle.Get(); var thread: Thread Thread.Make(fn [var handle] { handle.Process(); }); thread.Join(); }也可以用带名字的函数声明版本fn Foo() { let handle: Handle Handle.Get(); fn MyThread[handle]() { handle.Process(); } var thread: Thread Thread.Make(MyThread); thread.Join(); }捕获模式Capture Modeslet与var都可以作为捕获模式出现其行为与普通绑定一致let捕获按值捕获捕获副本不可变var捕获按对象捕获by-object捕获副本可变修改作用于副本。为防止歧义捕获只能存在于定义附着于声明的函数上lambda始终在表达式上下文以及直接定义在另一函数体内的函数声明语句上下文可以捕获前置声明的函数不支持捕获类成员允许self: Self之处也不支持捕获。捕获模式既可用作默认捕获模式说明符也可用于显式捕获。显式捕获示例fn Example { var a: i32 0; var b: i32 0; let lambda: auto fn [a, var b] { a 1; // ❌ 无效按值捕获不可变 b 1; // ✅ 有效修改按对象捕获的副本 }; lambda(); }生命周期约束的典型案例——被返回的 lambda 不能引用已死亡的变量fn Example { fn Invalid() - auto { var s: String Hello world; return fn [s]() s; } // ❌ 无效返回的 lambda 引用的 s 在 lambda 被调用时已不再存活。 Print(Invalid()()); }提案还特别说明了一个可变状态调用约定如果函数对象 F 具有可变状态无论是按对象捕获还是按对象函数字段那么对 F 的调用要求被调用方是引用表达式而非值表达式——因为我们需要一个可变句柄才能修改 F 的可变状态。默认捕获模式Default Capture Mode默认情况下函数不做任何捕获没有方括号等价于空方括号[]。用户通过显式捕获或更简洁的默认捕获模式选择捕获行为。默认捕获模式大致对应 C 的[]与[]在方括号内作为首个元素出现fn Foo1() { let handle: Handle Handle.Get(); fn MyThread[var]() { handle.Process(); // 由于默认捕获模式为 varhandle 被按对象捕获 } var thread: Thread Thread.Make(MyThread); thread.Join(); } fn Foo2() { let handle: Handle Handle.Get(); fn MyThread[let]() { handle.Process(); // 由于默认捕获模式为 lethandle 被按值捕获 } var thread: Thread Thread.Make(MyThread); thread.Join(); }函数字段Function Fields函数字段镜像 C 的初始化捕获init captures。一条函数字段定义由不可反驳模式irrefutable pattern、与初始化器组成函数定义被求值时模式与初始化器进行匹配。模式中的绑定与函数具有相同的生命周期其作用域延伸到函数体末尾。与捕获相同函数字段也只能存在于定义附着于声明的函数上lambda、函数体内的函数声明不支持前置声明与类成员。fn Foo() { var h1: Handle Handle.Get(); var h2: Handle Handle.Get(); var thread: Thread Thread.Make(fn [a: auto h1, var b: auto h2] { a.Process(); b.Process(); }); thread.Join(); }拷贝语义Copy Semantics为镜像 C 行为函数声明与 lambda 的可拷贝性由其所包含的函数字段与函数捕获决定若函数持有按对象by-object函数字段且该字段类型可拷贝则包含它的函数也可拷贝——对捕获同理另一类是按值by-value函数字段类比 C 中 const 引用作为类字段会阻止该类进行拷贝赋值按值函数字段会阻止其所处函数进行拷贝赋值。Self 与递归镜像 C 通过捕获this的做法self应始终作为来自外层作用域的捕获出现lambda 上永远不允许self: Self函数声明仅在作为类类型或接口的成员时允许self: Self此时它指向类/接口而非函数自身。提案还引用了一个相关方向PR #3720的推论形如x.(F)的表达式——其中 F 是带self或addr self参数、绑定为x的函数——产生的可调用对象持有x的值而非F的值。因此捕获和函数字段无法与self参数组合使用。设计理由RationaleCarbon 的 lambda 服务于两个目的支持代码易于阅读、理解和编写目标为此才引入返回表达式语法以及以省略显式参数元组 函数体内使用$N为特征的位置参数支持与现有 C 代码互操作并迁移目标C lambda 在使用点定义、通常匿名若迁移时只允许替换为具名函数声明迁移工具就不得不为每个 lambda 取名产生可用性负担。备选方案与取舍备选一简洁 vs 详尽Terse vs Elaborated此前的讨论曾考虑将函数分为三类简洁 lambdaterse lambdas、详尽 lambdaelaborated lambdas与函数声明。但分类带来了句法悬崖syntactic cliff简洁 lambda 计划最小化语法授予隐式默认捕获模式——没有方括号就允许按值捕获配合 sigil 引入符形成let zero: i32 0; let list_all: List(i32) GetAllValues(); let list_positive: List(i32) list_all.Filter( $0 zero );但详尽 lambda 为引入方括号与显式参数付出了更多语法代价还制造了悬崖既然期望空方括号 禁用捕获而无方括号形态又需要支持捕获详尽 lambda 就必须同时加上方括号和显式默认捕获模式才能维持既有捕获行为结果代码变成let list_positive: List(i32) list_all.Filter( let x zero );若再把 lambda 升级为函数声明又会遇到从 sigil 切换到fn关键字 添加名字的又一处悬崖。最终结论连续语法是更优路径即便最短的 lambda 拼写会比备选方案略长。备选二Sigil 引入符曾考虑用$或等 sigil 引入 lambda。由于引入符标点本身就是稀缺资源、对选择哪个 sigil 缺乏共识且希望 lambda 与函数声明保持连续语法该方案被否决。若采用会形如let lambda1: auto T.Make(); let lambda2: auto []() - T { return T.Make(); };备选三额外位置参数限制除上述两条限制外还考虑过将带位置参数的函数可见性限制在非公共接口内。该方案经由 leads 问题讨论后被否决推测此类限制更适合以 HOAhead of agreement/规则约定方式约束而非由编译器报错强制。备选四递归 Self为支持递归曾考虑在所有函数与 lambda 上允许self: Self并指向函数自身。但这会在类成员与非类成员之间制造不连续性故被否决。未来工作引用捕获Reference Captures关于按引用捕获的语义影响已有大量讨论。目前这类行为并非通过捕获实现而是借助由外层作用域中对象的地址构造的函数字段来间接支持。提案强调必须在该领域投入更多工作以解决当前方案的可用性痛点。附编译器实现现状从源码结构看lambda 语法已在前端解析阶段完整落地而语义检查阶段仍为待办解析实现位于 toolchain/parse/handle_lambda.cpp状态机覆盖fn引入、隐式/显式参数列表、-返回类型、简洁体与{ }语句体并在缺体场景下产生ExpectedLambdaBody/ExpectedLambdaBodyAfterReturnType诊断解析测试数据位于 toolchain/parse/testdata/lambda/lambda.carbon包含正常形态的解析树断言如LambdaIntroducer、TerseBodyArrow节点与错误恢复断言可用bazel test //toolchain/testing:file_test --test_arg--file_teststoolchain/parse/testdata/lambda/lambda.carbon单独运行验证检查阶段 toolchain/check/handle_lambda.cpp 中HandleLambdaIntroducer、HandleLambda、HandleTerseBodyArrow目前均返回context.TODO(...)说明本提案所描述的捕获、函数字段、位置参数、拷贝语义等语义检查语义尚未在toolchain/check中实现本文的语义描述以设计提案为准。需要说明的是上述结论均为对当前仓库快照的分析lambda 的解析语法已可验证而语义层行为仍停留在设计层面阅读源码时请注意这一阶段性差异。【免费下载链接】carbon-langCarbon Languages 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),仅供参考