Mojo语言基础知识
Mojo是一种功能强大的编程语言,主要设计用于高性能系统编程,因此与Rust和C++等其他系统语言有很多共同之处。然而,Mojo也被设计为Python的超集,因此您可能熟悉的许多语言特性和概念可以很好地转换为Mojo。
例如,如果您在REPL环境或Jupyter笔记本中(就像本文档中一样),您可以像Python一样运行顶级代码:
print("Hello Mojo!")
Hello Mojo!
其他系统编程语言通常不提供此功能。
Mojo保留了Python的动态特性和语言语法,甚至允许您导入和运行来自Python包的代码。然而,重要的是要知道Mojo是一种全新的语言,而不仅仅是带有语法糖的Python的新实现。Mojo将Python语言提升到了一个全新的水平,具备系统编程功能、强大的类型检查、内存安全性、下一代编译器技术等特性。然而,它仍然被设计为一种简单的语言,适用于通用编程。
本页面对Mojo语言提供了一个初步的介绍,仅需要一点点编程经验。让我们开始吧!
语言基础知识
首先,Mojo是一种编译语言,它的很多性能和内存安全特性都源于这个事实。Mojo代码可以是提前编译(AOT)的,也可以是即时编译(JIT)的。
与其他编译语言一样,Mojo程序(.mojo
或.🔥
文件)需要一个main()
函数作为程序的入口点。例如:
fn main():
var x: Int = 1
x += 1
print(x)
如果您了解Python,您可能期望函数名为def main()
而不是fn main()
。在Mojo中这两种方式都可以工作,但是使用fn
的方式有一些不同之处,我们将在下面讨论。
当然,如果您正在构建一个Mojo模块(API库),而不是Mojo程序,那么您的文件不需要一个main()
函数(因为它将被其他具有main()
函数的程序导入)。
注意: 在.mojo
/.🔥
文件中编写代码时,您不能像本页所示那样运行顶级代码,Mojo程序或模块中的所有代码都必须包含在函数或结构体中。但是,在REPL或Jupyter笔记本中可以运行顶级代码。
现在让我们解释一下这个main()
函数中的代码。
语法和语义
这很简单:Mojo支持(或将支持)Python的所有语法和语义。如果您不熟悉Python语法,有很多在线资源可以教您。
例如,像Python一样,Mojo使用换行和缩进来定义代码块(而不是花括号),Mojo还支持Python的所有控制流语法,如if
条件和for
循环。
然而,Mojo仍然是一个正在进行中的工作,所以还有一些Python中尚未实现的东西。所有缺失的Python特性将逐步实现,但Mojo已经包含了许多在Python中无法实现的特性和功能。
因此,接下来的几节将重点介绍一些与Mojo(相对于Python)独特的语言特性。
函数
Mojo函数可以使用fn
(如上所示)或def
(与Python中一样)声明。fn
声明强制执行强类型和内存安全行为,而def
则能提供Python样式的动态行为。
fn
函数和def
函数都有其价值,重要的是您了解它们。然而,在本介绍中,我们将只关注fn
函数。
在接下来的几节中,您将了解fn
函数如何在代码中实施强类型和内存安全行为。
变量
你可以使用 var
来声明变量(比如在上面的 main()
函数中的 x
),以创建一个可变值,或者使用 let
创建一个不可变值。
如果你将上面的 main()
函数中的 var
改为 let
并运行它,你将得到一个编译器错误,类似于以下内容:
错误:在 [15] 行 [7] 列 [5] 字符处表达式必须为可变的,以用作内存空间的操作数。
x += 1
^
这是因为 let
将值声明为不可变,因此你无法对其进行递增操作。
如果你完全删除 var
,你将得到一个错误,因为 fn
函数需要显式的变量声明(与 Python 风格的 def
函数不同)。
最后,请注意 x
变量有一个显式的 Int
类型规定。在 fn
中声明类型对于变量并不是必需的,但有时是有益的。如果你省略它,Mojo 会根据上下文推断类型,如下所示:
fn do_math():
let x: Int = 1
let y = 2
print(x + y)
do_math()
3
函数参数和返回值
尽管在函数体内声明变量不需要类型,但对于 fn
函数的参数和返回值,类型是必需的。
例如,以下是如何将 Int
声明为函数参数和返回值类型:
fn add(x: Int, y: Int) -> Int:
return x + y
z = add(1, 2)
print(z)
3
参数的可变性和所有权
Mojo支持完全的值语义,并通过强大的值所有权模型(类似于Rust的借用检查器)实现内存安全性。因此,以下是如何通过函数参数共享值的简要介绍。
请注意,上面的add()
函数不会修改x
或y
,它只是读取这些值。事实上,就代码本身而言,该函数无法修改它们,因为默认情况下fn
函数的参数是不可变引用。
从参数约定角度来看,这被称为“借用”,尽管在fn
函数中是默认的,但你也可以通过borrowed
声明来明确表示(与上面的add()
完全相同):
fn add(borrowed x: Int, borrowed y: Int) -> Int:
return x + y
如果你希望参数是可变的,你需要将参数约定声明为inout
。这意味着在函数内部对参数所做的更改在函数之外是可见的。
例如,以下函数能够修改原始变量:
fn add_inout(inout x: Int, inout y: Int) -> Int:
x += 1
y += 1
return x + y
var a = 1
var b = 2
c = add_inout(a, b)
print(a)
print(b)
print(c)
2
3
5
另一种选择是将参数声明为owned
,这样函数就完全拥有该值(它是可变且保证唯一的)。这样,函数可以修改该值而不必担心影响函数外部的变量。例如:
fn set_fire(owned text: String) -> String:
text += "🔥"
return text
fn mojo():
let a: String = "mojo"
let b = set_fire(a)
print(a)
print(b)
mojo()
mojo
mojo🔥
在这种情况下,Mojo会复制a
并将其作为text
参数传递。原始的a
字符串仍然存在并且正常。
但是,如果你想给函数所有权的值且不想进行复制(对某些类型来说这可能是一个昂贵的操作),那么你可以在将a
传递给函数时添加^
“转移”运算符。转移运算符实际上会销毁局部变量名,任何后续尝试调用它都会导致编译错误。
通过将调用set_fire()
的方式更改为以下形式来尝试它:
let b = set_fire(a^)
现在会得到一个错误,因为转移运算符实际上销毁了变量a
,所以当以下的print()
函数尝试使用a
时,该变量已不再初始化。
如果删除print(a)
,则一切正常。
这些参数约定旨在为系统程序员提供内存优化的完全控制,同时确保安全访问和及时释放。Mojo编译器确保任何两个变量不会同时对相同值具有可变访问,并且每个值的生命周期定义明确,严格防止任何内存错误,如“使用后释放”和“二次释放”。
注意: 目前,Mojo在函数返回值时总是进行复制。
结构体
在Mojo中,你可以使用struct
构建类型(或“对象”的)高级抽象。在Mojo中,struct
类似于Python中的class
:它们都支持方法、字段、运算符重载、元编程的装饰器等等。然而,Mojo的结构体是完全静态的,在编译时绑定,因此它们不允许动态分发或对结构的任何运行时更改(Mojo在未来也将支持类)。
例如,下面是一个基本的struct:
struct MyPair:
var first: Int
var second: Int
fn __init__(inout self, first: Int, second: Int):
self.first = first
self.second = second
fn dump(self):
print(self.first, self.second)
使用方法如下:
let mine = MyPair(2, 4)
mine.dump()
输出结果为:
2 4
如果你熟悉Python,那么__init__()
方法和self
参数应该对你很熟悉。如果你对Python不熟悉,那么请注意,当我们调用dump()
时,我们实际上没有为self
参数传递值。self
的值会自动提供当前结构体实例(它类似于其他一些语言中用于引用当前对象/类型实例的this
名称)。
Python集成
尽管Mojo仍在进行中,尚未完全兼容Python,但我们已经构建了一个机制以原样导入Python模块,因此您可以立即利用现有的Python代码。在幕后,这个机制使用CPython解释器运行Python代码,因此它与所有Python模块无缝地工作。
例如,下面演示了如何导入和使用NumPy(您必须安装Python的 numpy
):
from python import Python
let np = Python.import_module("numpy")
ar = np.arange(15).reshape(3, 5)
print(ar)
print(ar.shape)
输出结果为:
[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]]
(3, 5)
注意: Mojo尚未完全兼容Python。因此,您不能总是将Python代码复制到Mojo中运行。有关我们计划的更多细节,请参阅Mojo规划和尖锐的边缘。
注意: 当安装Mojo时,安装程序会在您的系统中搜索一个与Mojo一起使用的Python版本,并将路径添加到modular.cfg
配置文件中。如果更改Python版本或切换虚拟环境,Mojo将查看错误的Python库路径,这可能会导致导入Python包时出现问题(Mojo仅显示“在Python中发生错误”——这是一个独立的已知问题)。当前的解决方案是使用MOJO_PYTHON_LIBRARY
环境变量覆盖Mojo对Python库的路径。有关如何查找和设置此路径的说明,请参阅此相关问题。