一架梯子,一头程序猿,仰望星空!
Mojo教程 > 内容正文

Mojo语言基础


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()函数不会修改xy,它只是读取这些值。事实上,就代码本身而言,该函数无法修改它们,因为默认情况下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库的路径。有关如何查找和设置此路径的说明,请参阅此相关问题