Solidity 语言设计溯源:来自 C++、JavaScript 与 Python 的影响
【免费下载链接】soliditySolidity, the Smart Contract Programming Language项目地址: https://gitcode.com/GitHub_Trending/so/solidity
导读
Solidity 是一门面向以太坊智能合约的静态类型编程语言,其语法与语义并非凭空诞生,而是从 C++、JavaScript、Python 等成熟语言中借鉴与演化而来。本文以官方文档 docs/language-influences.rst 为骨架,系统梳理 Solidity 从三种语言继承的核心设计:C++ 带来的语法与类型系统、JavaScript 的历史痕迹及其在 0.4.0 之后的逐步退场、Python 贡献的修饰符(modifier)、多重继承与 C3 线性化。读完本文,你将理解 Solidity 中众多"似曾相识"的语法背后的设计动机,并能在编写合约时更准确地把握继承顺序、super调用与类型转换的行为。
一、总览:一门"花括号语言"的基因图谱
Solidity 在官方文档中被明确归类为"花括号语言"(curly-bracket language),即使用{}界定代码块的语法族。在这个家族中,它最深刻地受到 C++ 的影响,同时从 Python 与 JavaScript 中借用了一部分高层概念。三个语言的影响叠加,最终形成了今天 Solidity 的独特面貌:
| 影响来源 | 核心贡献 | 当前状态 |
|---|---|---|
| C++ | 变量声明、for 循环、函数重载、类型转换、静态类型体系 | 持续存在,构成语法主干 |
| JavaScript | 函数级作用域、var关键字、function关键字、import 语法 | 自 0.4.0 起大幅弱化,仅保留少量痕迹 |
| Python | modifier(模仿装饰器)、多重继承、C3 线性化、super、赋值/拷贝语义 | 持续存在,构成继承与语义模型 |
需要注意的是,文档所描述的"语言影响"属于设计层面的渊源,与编译器的具体实现是两个层面:前者回答"语法为什么长这样",后者回答"编译器如何把语法变成 EVM 字节码"。本文在介绍渊源的同时,也会结合仓库源码说明对应机制的落地位置。
二、C++:塑造语法主干的最大影响源
文档明确指出,Solidity 受 C++ 的影响最为深远,这种影响直接体现在日常写合约时最常用的语法元素上:
- 变量声明:
uint256 x = 1;这种"类型在前、名字在后"的声明方式与 C/C++ 一致,与 Go、Rust 等"名字在前"的语言形成对比。 - for 循环:
for (uint256 i = 0; i < n; i++)的三段式结构完全沿袭 C 家族,包括初始化、条件、迭代表达式三个部分。 - 函数重载(overloading):允许在同一合约中定义多个同名函数,只要参数类型或数量不同即可区分。这与 C++ 的重载规则一脉相承。
- 隐式与显式类型转换:Solidity 支持在兼容类型之间的隐式转换(如从
uint8提升到uint16),也支持通过显式转换语法uint8(x)进行窄化转换。这套"隐式宽化、显式窄化"的规则设计明显带有 C++ 类型系统的影子。
从当前仓库源码看,类型转换的完整规则集中在 libsolidity/ast/Types.cpp 中实现,函数重载的分辨逻辑则由 libsolidity/analysis/TypeChecker.cpp 负责。可以说,C++ 的影响不仅停留在语法表面,也渗透进了编译器的类型检查管线。
此外,C++ 的影响还体现在更底层的细节上:Solidity 的整数类型(uint8到uint256、int8到int256)与 C++ 的固定宽度整数类型在思路上同源,只是受 EVM 256 位字长的约束做了扩展。花括号作用域、else if分支链、三目运算符等语法细节也都直接继承自 C 家族。
三、JavaScript:从"深度借鉴"到"仅存痕迹"
3.1 历史渊源与 0.4.0 的转折
文档揭示了 Solidity 的一段重要历史:在语言早期,Solidity 曾部分受 JavaScript 影响,具体体现在两点:
- 函数级作用域(function-level scoping):变量在整个函数体内可见,而非像现代语言那样局限于声明所在的代码块;
var关键字:用var声明变量,由编译器推断类型。
从 0.4.0 版本开始,官方有意识地削减了 JavaScript 的影响。这一决策在仓库的版本迁移文档中留下了明确的证据:
- docs/050-breaking-changes.rst 记载:"The
varkeyword is now disallowed to favor explicitness."(var关键字现已被禁止,以提倡显式性),并在示例代码中展示了旧版本中var z = someInteger();的写法; - docs/070-breaking-changes.rst 进一步确认:"The keyword
varcannot be used anymore."(var关键字已不可再使用)。
也就是说,var在 0.5.0 中被正式禁用,到 0.7.0 后彻底从语言中消失,今天写合约必须为每个变量显式声明类型。
3.2 今天留下的两大痕迹
文档指出,当前 Solidity 与 JavaScript 的主要相似点仅剩两处:
function关键字:定义函数时使用function,例如function transfer(address to, uint256 amount) public。这与 JavaScript 的函数声明语法在关键字层面一致,但语义完全不同——Solidity 的函数有可见性(public/private/internal/external)和状态可变性(view/pure/payable)修饰,属于编译期静态检查,而 JavaScript 的函数是动态的。import 语法与语义:Solidity 支持
import "./other.sol";、import {A, B} from "./lib.sol";等形式,命名导入、别名导入(as)等写法与 ES Module 高度相似。具体的解析与导入重映射逻辑可以在 libsolidity/interface/ImportRemapper.cpp 与 libsolidity/interface/FileReader.cpp 中看到。
除了这两点之外,Solidity 已经回归到与其他花括号语言基本一致的形态,不再保有显著的 JavaScript 影响。文档对此的总结很直白:"Besides those points, Solidity looks like most other curly-bracket languages and has no major JavaScript influence anymore."
四、Python:修饰符、继承与语义模型
如果说 C++ 提供了 Solidity 的"骨架",Python 则提供了相当一部分"血肉"——尤其是合约的组织与复用机制。文档明确列出的 Python 影响包括以下四点。
4.1 Modifier:以更受限的方式模仿装饰器
Solidity 的修饰符(modifier)是在尝试以"功能大幅受限"的方式模仿 Python 的装饰器(decorator)。Python 装饰器通过@decorator语法包裹函数,可以在函数执行前后注入逻辑;Solidity 的 modifier 则在函数声明时追加onlyOwner之类的修饰符,并在函数体中以_占位符标记原始函数体的插入点:
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.0 <0.9.0; contract Owned { address public owner; constructor() { owner = msg.sender; } modifier onlyOwner() { require(msg.sender == owner, "Not the owner"); _; // 原始函数体在此处执行 } function changeOwner(address newOwner) public onlyOwner { owner = newOwner; } }对比 Python:
def only_owner(func): def wrapper(*args, **kwargs): if args[0].owner != ...: raise PermissionError return func(*args, **kwargs) return wrapper class Owned: @only_owner def change_owner(self, new_owner): ...两者的共同点是"在函数定义处声明横切逻辑";区别在于 Python 装饰器是运行时的动态包装,可以任意组合与反射,而 Solidity 的 modifier 是编译期展开的静态机制,功能刻意保持简单。modifier 的解析与展开发生在 libsolidity/analysis/ContractLevelChecker.cpp 等分析阶段,最终在代码生成阶段被内联进函数体。
4.2 多重继承与 C3 线性化
Python 是少数原生支持多重继承的主流语言,Sol 引入 C3 线性化(C3 Linearization)来消除菱形继承的歧义。C3 线性化算法最早由 Python 2.3 引入,用于决定 MRO(方法解析顺序)。
在编译器中落地:仓库源码 libsolidity/analysis/NameAndTypeResolver.h 明确写着"Computes 'C3-Linearization' of base contracts",其实现位于 libsolidity/analysis/NameAndTypeResolver.cpp 的linearizeBaseContracts函数中,核心的 C3 合并算法由cThreeMerge实现(同文件第 449 行起)。算法要点包括:
- 输入是按"从派生到基类"顺序排列的基类列表的列表;
- 候选契约必须"仅出现在各列表头部"(
appearsOnlyAtHead,见 NameAndTypeResolver.cpp); - 合并失败时返回空列表,进而触发编译错误
"Linearization of inheritance graph impossible"(错误码 5005)。
在语言文档中的解释:docs/contracts/inheritance.rst 的 "Multiple Inheritance and Linearization" 一节详细说明了这一机制,并给出一个典型的不合法示例:
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.4.0 <0.9.0; contract X {} contract A is X {} // 这将无法编译 contract C is A, X {}原因正如文档所述:C要求X覆盖A(因为按A, X的顺序列出),而A本身又要求覆盖X,形成无法化解的矛盾。从源码结构看,这正是cThreeMerge找不到"仅出现在头部"的候选者时返回空列表的场景。
一个与 Python 的重要差异值得注意:基类在is指令中的排列顺序是反过来的——Solidity 要求按"最基类化到最派生"排列,而 Python 正好相反。搜索时 Solidity 从右往左进行深度优先查找(Python 是从左往右),且已搜索过的基类会被跳过。
4.3super关键字
super同样取自 Python。在多重继承中,super用于沿线性化顺序调用下一个基类的同名函数,而不是直接指定某个具体基类。这在"钻石继承"场景中尤为关键——它保证了每个基类函数在整条继承链中只被调用一次。
Solidity 的super在编译器中体现为一种特殊的合约类型。在 libsolidity/ast/Types.cpp 中可以看到类型名为"t_super"的表示;而 docs/contracts/inheritance.rst 中的示例表明,Final合约调用super时,实际执行的是线性化顺序中的下一个实现,并且super调用使用 JUMP 而非消息调用(见该文档第 26 行),因此成本更低。super的语义实现依赖linearizedBaseContracts中存储的线性化顺序,这正是上一小节 C3 算法的直接产出。
4.4 值类型与引用类型的赋值/拷贝语义
文档提到的第四项 Python 影响是"值类型和引用类型的一般赋值与拷贝语义"。这一点的通俗理解是:
- 值类型(如
uint、bool、address)在赋值与传参时按值拷贝,修改副本不影响原变量; - 引用类型(如
array、struct、mapping)则根据存储位置(storage/memory/calldata)的不同,表现出引用共享或拷贝的语义差异。
这种"值类型天然拷贝、引用类型需显式声明数据位置"的模型,与 Python 中"不可变对象按值语义、可变对象按引用语义"的心智模型相通。Solidity 的具体规则(例如storage赋值是引用、memory之间赋值是拷贝)在 docs/types/reference-types.rst 中有完整阐述,编译器侧的实现则在 libsolidity/codegen/CompilerUtils.cpp 与 libsolidity/codegen/ArrayUtils.cpp 中。
五、对合约开发者的实践启示
理解了三种语言的基因图谱后,可以在日常开发中做出更符合语言设计意图的选择:
- 显式胜过隐式:既然
var因"提倡显式性"而被移除,就应始终为变量写出完整类型,避免依赖类型推断带来的可读性损失。 - 掌握继承顺序规则:在
is指令中必须按"最基类化到最派生"排列直接基类;一旦写出contract C is A, X {}(其中A is X)这样的矛盾继承图,编译器会直接报 "Linearization of inheritance graph impossible"。可以用contract C is X, A之类顺序修复。 - 善用
super而非硬编码基类名:在多重继承链中,super.f()会沿线性化顺序找到下一个实现,语义上比BaseName.f()更稳健,且super调用走 JUMP 而非消息调用,Gas 成本更低。 - 区分拷贝与引用:赋值
storage结构体给另一个storage变量是引用共享,修改会互相影响;复制到memory才是真正的拷贝。在函数参数传递时注意memory/calldata的选择,这直接关系到 Gas 消耗与数据安全。
六、总结
Solidity 是一门"站在巨人肩膀上"的语言:C++ 提供了类型系统、控制流与重载等语法主干;JavaScript 的影响在 0.4.0 之后被系统性地削减,仅保留function与 import 语法;Python 则贡献了修饰符(以更受限的 modifier 形式)、C3 线性化多重继承、super与拷贝语义模型。官方文档 docs/language-influences.rst 是这一结论的第一手来源,而本仓库的编译器源码(尤其是 libsolidity/analysis/NameAndTypeResolver.cpp 中的 C3 实现、libsolidity/ast/Types.cpp 中的类型系统)则为这些渊源提供了工程层面的印证。理解这些设计选择,不仅能解释 Solidity 语法"为什么长这样",也能帮助你在面对继承、重载与类型转换时做出更正确的合约设计决策。
【免费下载链接】soliditySolidity, the Smart Contract Programming Language项目地址: https://gitcode.com/GitHub_Trending/so/solidity
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考