
我第一次看到“Mojo语言”这四个字的时候其实有点兴奋。作为每天在 Python 里搬数据的常年用户我对“新语言”这件事的态度已经从见怪不怪变成了“除非真的能解决痛点否则别来打扰我”。但 Mojo 的首次亮相确实不一样一门号称“Python 的超集”、能把 Python 的易用性和 C/C 级别性能结合在一起的编译器语言来自 LLVM 和 Swift 作者 Chris Lattner 参与创立的 Modular 团队。这让我没法不亲自试一试。这篇文章不是什么权威评测也不是完整教程而是一个老程序员在周末第一次动手体验 Mojo 语言的完整记录。我会把从环境安装、打印第一行 “Hello, Mojo”到做性能测试和调用 Python 库的过程全写下来也会说清楚哪些地方惊艳到我、哪些地方目前还让我不太舒服。如果你也在纠结要不要学 Mojo或者只是好奇它跟 Python 到底有什么区别那这篇内容应该比较适合你。1. 我为什么会对 Mojo 产生兴趣性能焦虑与“更好的 Python”1.1 一个多年的痛CPython 的慢不只是解释器的问题我在不少项目里都遇到过同样的场景业务逻辑用 Python 写得很舒服但一到数据量上来整个程序就肉眼可见地变慢。表面上看大家第一反应是“Python 太慢了”可真正做过性能分析的人都知道瓶颈往往不是解释器本身而是你不得不用循环去处理那些本应该底层化的操作。比如一个简单的数值累加纯 Python 的for循环要走字节码解释流程每个整数都要创建对象哪怕只是从 1 加到一亿也要耗费好几秒。而如果用 C 写同样的循环编译器可以直接把它优化成寄存器上的加法指令执行时间会缩短几个数量级。这也是为什么科学计算领域会搞出 NumPy、SciPy、PyTorch 这一类东西——不是 Python 开发者不想写循环而是纯 Python 的循环真的撑不住。Mojo 出现的时候正好切中这个矛盾。它想让你在写“像 Python 一样的代码”的同时获得接近 C/C 的执行效率。换句话说它尝试把 Python 生态的舒适区保留下来同时引入现代编译器技术和系统编程能力。对我来说这个选题本身就是有吸引力的。1.2 “Python 的超集”这句话到底意味着什么Mojo 对自己的定位是 Python 的超集。这意味着从语法层面出发大部分 Python 代码应该可以直接在 Mojo 中运行不需要大改。但这个“超集”并不只是把解释器换成编译器那么简单它背后还有几个关键设计Mojo 基于 MLIR 编译基础设施构建能够在编译阶段做大量优化而不是像 CPython 那样逐行解释。它提出了def和fn两套函数体系前者是 Python 式的动态风格后者是强类型、高性能的风格。它引入了let/var变量声明、结构体struct、所有权和借用机制这些在 Python 里根本不存在。所以 Mojo 给人的第一感觉是表面长得很像 Python内里却更像 Swift 或 Rust 那一派。它不是“Python 换了个编译器跑得快一点”而是“在 Python 的皮囊下面塞了一套现代化系统编程语言的内核”。这一点在刚开始上手时感受尤其明显。1.3 适合谁接着看下去如果你属于下面几类人这篇体验记录应该对你有参考价值长期写 Python被性能问题困扰但又不想整个项目改用 C/Rust 的开发者。对编译原理、MLIR、编程语言设计感兴趣的爱好者。在 AI 或数据处理方向工作想了解 Mojo 能不能改善日常推理、预处理或服务部署效率的人。单纯像我一样看到新语言就手痒想站在 2023 年到 2024 年这个时间节点看它到底发展到什么程度的人。当然如果你完全没写过 Python直接上手 Mojo 可能会有点懵。因为它的很多设计都会拿“Python 习惯”来对比后面我写到的内容也默认你对 Python 基础语法有一定了解。2. 从零开始装环境安装 Mojo SDK 的完整记录与踩坑过程2.1 先搞清楚能跑在什么系统上第一次尝试 Mojo首先要面对的不是语言本身而是环境限制。我最初是在一台 Windows 笔记本上想直接装结果发现官方 SDK 早期只对 Linux 和 macOS 提供比较完善的体验Windows 用户一般需要通过 WSL 2 来跑。这点和很多开发者工具链刚发布时的情况类似不算稀奇但的确会拦住一部分想快速上手的人。我当时的选择是开一台 Ubuntu 22.04 的云主机来体验。之所以这么做一方面是为了避开 Windows 环境的不确定性另一方面是云主机配置比较干净安装过程可控后面跑性能测试也不容易受到其他后台程序干扰。如果你也准备认真搞一下我建议优先用 Linux 环境而不是在 Windows 原生环境下硬折腾。macOS 用户会舒服一点尤其是 Apple Silicon 的机器官方对 M 系列芯片的支持比较早。但要注意早期版本可能只支持 arm64如果你的 Mac 还是 Intel 芯片可能也会碰到一些障碍。2.2 安装官方 SDK 的关键命令和步骤我按照官方文档走了一遍安装流程过程其实不复杂核心就是把 Modular 的 CLI 工具装好再通过它安装 Mojo SDK。这里记录一下我当时执行的步骤# 1. 下载并运行 Modular 安装脚本 curl -ssf https://get.modular.com | sh - # 2. 登录 Modular 账户会打开浏览器授权 modular auth # 3. 安装 Mojo SDK modular install mojo # 4. 确认安装结果 mojo --version第一步的脚本会往~/.modular目录写入一堆文件同时会提示你把相关的bin路径加到 PATH 环境变量里。如果 shell 配置文件比如.bashrc、.zshrc里没有自动加上就需要手动执行类似这样的命令export MODULAR_HOME$HOME/.modular export PATH$MODULAR_HOME/pkg/packages.modular.com_mojo/bin:$PATH我遇到的第一个坑就出在这里执行完安装脚本后新开的终端并没有自动找到mojo命令我还以为是安装失败折腾了一会儿才发现是 PATH 没生效。这种问题在装很多 CLI 工具时都会遇到算是个常规操作但新手很容易被卡住。安装完成后命令行工具mojo主要有几个常用用法mojo --version查看版本。mojo main.mojo直接以 JIT 方式运行一个 Mojo 文件。mojo build main.mojo -o hello编译出独立可执行文件。在终端直接输入mojo回车可以进入 REPL 交互环境适合快速验证小语法片段。2.3 不装 SDK 也能体验Mojo Playground 和 VS Code 插件如果你暂时不想把整个 SDK 装到本地官方还有一个在线环境叫 Mojo Playground可以直接在浏览器里写代码运行。我后来在路上的时候用 Playground 做过一些快速验证体验类似绝大多数在线代码编辑器不用配置任何环境对第一次接触 Mojo 的人来说非常友好。缺点是它毕竟是远程环境跑大规模性能测试不够真实而且有些需要读取本地文件、调用本地库的功能没法在 Playground 上完整体验。编辑器方面Modular 官方提供了 VS Code 插件搜索 “Mojo“ 就能找到。装上之后有语法高亮、代码补全还能直接在 VS Code 里运行当前文件。对我来说没有语法高亮写新语言就像摸着黑走路插件虽然早期功能不算丰富但已经足够支撑日常试验了。2.4 安装过程中的一点心得整体来说Mojo SDK 的安装比我想象中顺利主要是 Linux 环境下一条命令走到底。但有几个细节值得注意安装过程依赖网络环境如果网络不稳定下载可能会中断最好在稳定的网络环境下操作。官方文档更新速度很快不同版本的安装命令可能有细微差异碰到问题先看一眼官方文档永远是最靠谱的。如果你用的是 Windows WSL 2文件系统读写性能和 Linux 原生环境会有差异但跑小规模的编译和测试问题不大。我第一次完整跑通 “mojo hello.mojo” 的时候内心有个声音是“这不是和 Python 一模一样吗”但紧接着我又知道事情没那么简单。3. 打印出 “Hello, Mojo” 之后一个 Python 程序员的第一个小时3.1 第一段代码引发的思考fn main()和def main()差在哪不管学什么语言第一件事总是打印 “Hello, Mojo”。我当时的初版代码非常简单fn main(): print(Hello, Mojo!)保存成hello.mojo然后在终端执行mojo hello.mojo输出内容和我预期的一样Hello, Mojo!但问题也来了为什么官方示例里用的是fn而不是 Python 用户更熟悉的def这里的区别其实是 Mojo 的核心设计之一。def是“Python 式函数”参数类型和返回类型都可以不写行为上更接近动态语言而fn是“Mojo 原生函数”要求显式类型标注也更适合编译器优化和系统编程。换句话说def是给你兼容 Python 习惯用的fn才是 Mojo 真正推荐的性能路径。我一开始写这个main函数时甚至想过直接写def main():行不行。答案是行但你在 Mojo 里写main入口函数时官方示例几乎都使用fn。因为程序的入口函数承担着类型安全和性能优化的职责本就该按“性能模式”来写。后来我又试了试 REPL 环境在终端输入mojo回车后直接输入print(Hello, Mojo!)也能立刻看到输出。对于随手验证语法来说REPL 的体验和 Python 很像几乎是无缝迁移。3.2 变量声明let和var带来的差异感在 Python 里你永远不会看到let和var这两个关键字因为它们根本不是 Python 风格的语法。但 Mojo 里变量声明被明确分成了两种let x 20 # 不可变绑定之后不能重新赋值 var y 30 # 可变绑定可以后续修改 y 40 # 合法 x 50 # 编译错误不能给不可变绑定重新赋值这个设计思路在 Swift、Kotlin 里很常见先区分“不可变引用”和“可变引用”能帮助编译器做更强的优化。也让我在写代码时不得不去思考一个问题这个变量到底应不应该被修改在 Python 里你随手就写了但在 Mojo 里编译器会逼你先把这个意图想清楚。如果你写了类型标注语法长这样var count: Int 0 let name: String Mojo这个设计对动态语言背景的人来说会有一点点约束感但它带来的好处是编译器能更快地理解你的代码错误也能在编译期暴露出来而不是等到运行到某个边角情况才崩溃。3.3struct代替class轻量级对象和高性能的基础Python 里的类class是个“活盒子”你可以在运行时往实例上挂新属性也可以动态改方法。这种灵活性让 Python 很好写但也让解释器很难优化。Mojo 里的基本对象类型是struct它走的是“值语义”路线。我第一版测试代码大概是这样的struct Point: var x: Int var y: Int fn __init__(inout self, x: Int, y: Int): self.x x self.y y fn sum(self) - Int: return self.x self.y fn main(): let p Point(3, 4) print(p.sum())初看这跟 Python 的类很像但内部逻辑是完全不同的。struct没有 Python 式的动态属性注入也不依赖引用计数做内存管理它更像 C 里的结构体可以明确控制数据的内存布局和访问方式。这带来的直接好处是当你在循环里大量创建这类对象时编译器可以做更多优化不会像 Python 对象那样频繁产生堆内存分配。当然代价是它没那么“随意”。你不能在struct里随便加一个新的属性所有字段必须在定义时写清楚。这对我这种习惯了动态语言的人来说是一次思维模式的转换从“先跑起来再说”变成“先想清楚数据长什么样”。3.4 所有权、借用和inout从“随便改”到“按规矩来”第一次在 Mojo 里写一个修改结构体字段的函数时我被它的参数传递机制教育了。在 Python 里你把对象传进函数然后直接改它的内部状态这再寻常不过。但在 Mojo 的函数参数默认是“借用”方式传递的你只能读不能改。要修改结构体需要在函数声明里显式加上inoutfn set_zero(inout p: Point): p.x 0 p.y 0这种设计其实和 Rust 的所有权与借用体系很像。如果你之前写过 Rust会觉得很自然如果你只写过 Python会觉得“怎么连改个参数都要打招呼”。但这种显式表达是有好处的编译器清楚知道哪个函数能修改数据哪个函数不能从而做更激进的优化。代码的调用关系更明确不容易出现“某个函数偷偷改了全局状态”这种隐蔽问题。在多线程并行场景下这种约束能有效避免数据竞争。我花了一点时间才适应这个机制但适应之后反而开始在 Python 里怀念这种明确性——至少不会出现某个对象被十几个地方轮番修改最后不知道谁动过的问题。4. 第一次性能测试相同累加循环Mojo 到底比 Python 快多少4.1 设计一个公平的对比实验新语言不能只靠“文档上说快”来说服我得自己跑一个测试。我选择了一个很基础的微基准对一个整数序列累加求和。这不算什么高难度测试但它能非常直观地反映纯语言的循环、整数运算和类型处理能力。Python 这边我用的是很普通的写法total 0 for i in range(100_000_000): total i print(total)Mojo 这边我一开始没有做任何高级优化直接照着 Python 的风格写fn main(): var total 0 for i in range(100_000_000): total i print(total)这里我没有给total和i写类型标注看看 Mojo 的类型推断能做好多少。运行方式分别是python3 test.py和mojo test.mojo用系统的time命令记录耗时。4.2 让我有点惊讶的首轮结果实测结果出来后差距确实非常大。在我那台 Ubuntu 云主机上Python 3.11 版本跑一亿次加法循环大约花了 4 到 5 秒而 Mojo 的 JIT 版本大概只用了 0.08 秒左右。也就是说在最简单的场景下Mojo 已经比纯 Python 快了几十倍。虽然这个结果符合预期但我还是有点意外因为 Mojo 在这个测试里其实还有很大的优化空间——我没给变量写类型标注循环也被编译器优化过吗带着这个疑问我把代码改得更“Mojo 一点”fn main(): var total: Int 0 for i in range(100_000_000): total i print(total)加了类型标注之后耗时进一步下降大概到了 0.06 秒左右。虽然数据不大但很明显看出显式类型信息对编译器有帮助。不过我也得诚实地说这种“快几十倍”的对比核心原因是 Python 的纯 Python 循环本来就不适合作为公平的对比标的。现实项目中没人会真用纯 Python 循环去处理上亿次加法大家会用 NumPy、用 C 扩展。所以这个数据的意义不是“Mojo 秒杀 Python”而是“当你需要写裸循环时Mojo 能给你接近 C 的性能”。4.3 尝试把循环并行化Mojo 在并发上的野心跑完单线程测试后我又想看看 Mojo 的并行能力。Mojo 官方提供了一些并行工具比如parallelize可以把一个任务分割到多个 worker 上执行。我当时写了一个简单的并行示例from algorithm import parallelize fn worker(index: Int): print(index) fn main(): parallelize[worker](4)这个例子本身很简单只是打印四个索引但它背后的含义更值得注意Mojo 把并行抽象进了语言和标准库而不是像 Python 那样主要靠threading或multiprocessing模块去补足。在支持多核的计算场景里这种设计会有很大想象空间。不过我也要提醒一句Mojo 的 API 还在快速变化中不同版本的parallelize等接口细节都可能调整。写这类代码时最好以官方示例为准不要完全照搬网上的老文章。4.4 性能测试后的冷静解读做了这些简单测试之后我对 Mojo 的性能有一个比较清晰的认知在纯计算型循环场景下Mojo 比 CPython 快几十倍甚至更多这是实实在在的。但如果你的性能敏感部分已经用上了 NumPy 或调用了 C 库那 Mojo 的替换优势不会那么夸张。Mojo 的真正价值是让你可以脱离 Python 的运行时限制自己去写高性能的内核逻辑同时不需要换用一门完全不同的语言。所以它更适合的场景是你想保留 Python 的开发体验但需要在底层实现某些计算密集的模块那 Mojo 会是一个非常不错的中间选项。5. 第一次尝试 Python 互操作在 Mojo 里调用 NumPy 是什么样的体验5.1 为什么互操作是决定成败的一环一门新语言如果能跟 Python 生态无缝衔接那它的推广难度会小很多。毕竟数据科学和 AI 领域的大量现成工具都是 Python 的如果你要让用户完全抛弃 NumPy、Pandas、PyTorch 重新造一套轮子那基本等于劝退。Mojo 的官方路线是提供 Python 互操作能力让 Mojo 代码可以直接导入 Python 模块。我第一时间就想试试这个能力到底靠不靠谱。5.2 在 Mojo 代码中直接调用 NumPy我的测试代码很简单from python import Python fn main(): let np Python.import_module(numpy) let arr np.array([1, 2, 3, 4, 5]) print(arr.sum())运行结果15整个过程顺利得不像话。np.array创建了一个 NumPy 数组arr.sum()调用了 NumPy 的方法结果也能直接打印出来。这意味着在 Mojo 里你不需要重写数据处理逻辑可以直接借用 Python 生态里最成熟的那些库。我还试了更复杂一点的用法比如创建多维数组、调用一些基础数学函数表现都比较稳定。从体验上看Mojo 和 Python 之间像是开了一扇门数据可以在两种语言之间来回传递而不需要手动做格式转换。5.3 互操作的一些边界和注意事项不过在玩了一阵后我也发现几个需要留意的点互操作调用 Python 对象时性能不会比你直接在 Python 里调用更好。因为最终它走的还是 CPython 的运行时。Mojo 的好处在于你可以把 Python 对象当成“高层接口”而把热点计算切到 Mojo 原生代码里。不同版本 Mojo 对 Python 互操作的接口可能会有调整早期版本里Python.import_module这类写法已经比较稳定但更复杂的对象转换仍可能出现 API 变化。在编译成独立可执行文件时Python 互操作部分通常还需要依赖本机的 Python 环境不能完全做到“静态编译零依赖”。所以如果你的目标是构建一个完全脱离 Python 环境的工具还得再仔细评估。对我的个人体验来说互操作功能让 Mojo 的“上手曲线”变得非常平滑。我不需要从一个空白世界开始前面三年积累的 Python 脚本、依赖和思路至少有一部分可以直接拿过来用。5.4 编译成可执行文件另一个层级的体验除了直接以脚本方式运行Mojo 还能把源码编译成原生可执行文件。我可以把前面那个 hello 程序编译一下mojo build hello.mojo -o hello ./hello看到Hello, Mojo!从一个真正的二进制文件里打印出来感觉还是很不一样的。这意味着在需要交付给别人的场景里你不再需要对方先装 Python 环境和一堆依赖直接丢一个编译产物过去就行。这种“既能当脚本跑又能做成原生程序”的能力在实际工程里非常实用。写工具脚本时可以用 JIT 模式快速迭代到了发布阶段再用build命令生成发布版。6. 体验过后的冷静判断哪些场景适合 Mojo哪些暂时不适合6.1 让我下定决心继续用 Mojo 的场景经过这次体验我认为 Mojo 在下面这些场景里已经有比较明确的优势需要手写数值计算内核的 Python 项目。比如自定义优化器、图像处理算法、模拟仿真不想再用 C/C 写扩展又嫌纯 Python 太慢。对多核并行有需求的 CPU 密集型任务。Mojo 的内置并行原语比 Python 的multiprocessing更底层可控性更高。希望用“类 Python 语法”做系统级编程的开发者。虽然它比 Python 多了一些条条框框但比 Rust 和 C 对普通开发者友好得多。对编译期优化、MLIR 基础设施感兴趣的研究者。Mojo 本身就是一套很典型的现代编译栈光研究它的设计思路就很有价值。6.2 目前还不太让我满意的几个短板我不会只夸不骂。作为一个刚体验完的新用户我明显感受到 Mojo 还处于快速成长期有几个问题非常现实生态还不够成熟。库的数量、第三方工具链、网上可查的实战案例都还很少遇到问题更多时候只能翻官方文档和 GitHub。文档和 API 仍在变化。我在网上看到的不少代码示例在新版本里可能已经跑不通了。由于 Mojo 版本迭代快你必须习惯“以官方最新文档为准”。编译错误信息对新手不算友好。尤其是所有权、借用这类机制出错时如果不了解底层的值语义模型可能要看很久才明白问题在哪。在 Windows 上的体验依然不够原生。虽然 WSL 2 可以跑但这仍然会劝退一批普通用户。如果你的核心工作环境就是 Windows初次尝试成本会比其他语言更高一些。6.3 我接下来想尝试的方向和给你的建议体验完这一轮之后我并不会急着把现有项目全部迁移到 Mojo但我已经把它列入正式的观察清单。接下来我打算做这几件事用 Mojo 重写一个我以前用 Python 写的性能敏感模块对比真实的工程收益。深入了解一下它的编译过程和 MLIR 中间表示看看优化到底发生在哪些环节。观察社区和生态的成长速度如果第三方库数量开始指数级增长那它会成为一个值得长期投入的方向。如果你现在还处于“要不要学 Mojo”的观望期我的建议很直接把它当成一个周末实验项目来玩写几个小的性能测试和互操作测试自己判断它是否符合你的技术路线。不用急着全面转向也不用因为“新语言不成熟”而完全无视它。像 Mojo 这种背靠现代编译基础设施、又踩在 Python 生态肩膀上出生的语言无论最终能不能成为主流都值得你花一个下午去理解它的设计思路。