结构体(Struct)
在 Mojo 中,除了一些基础类型外,你可以定义高级类型,称为结构体(struct)。
struct Pair:
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)
fn main():
var pair = Pair(2, 4)
pair.dump()
Mojo 中的 struct 这个概念和 Python 中的 class 类似,都支持字段、方法、运算符重载、元编程的装饰器等。但是,Mojo 中的 struct 是完全静态的,它们在编译的时候就已经固定了,因此不允许有动态分派或者在运行时结构的更改,而在Python中是可以动态增加、移除、修改类的字段或者方法的。
Mojo 中的 struct 也不支持继承,也就是不能定义子类,但是 struct 可以通过实现“特性”( trait)来完成一些类似的功能,看上去有点近似于Golang 中只有组合没有继承的做法。
所有Mojo 中的标准类型(Int, String等)其实都是通过struct实现的,而不是硬编码在语言自身的。
结构中除了定义正常的方法外,还可以通过 @staticmethod的装饰器来定义静态方法。调用一个 struct 的静态方法不需要实例化一个结构。这个函数也不需要传入一个 self的参数。
结构中还有很多特殊方法,例如 __init__(创建一个struct 时候会调用,类似C++里的构造函数),__del__ (销毁一个 struct 的时候会调用,类似析构函数)。另外还有一大类特殊方法是用来重载运算符运算的。例如可以定义 __lt__ 函数,当比较两个值的时候就会使用(a < b).
特性(Trait)
特性是指一个结构的模板化的要求。如果你使用一个特性来创建一个结构,那么你必须创建这个特性所要求的内容。特性的每个要求的内容都是必须实现的,当你实现了所有要求的时候,也叫做遵循这个特性。
目前,特性所要求的唯一内容是方法,另外,特性目前也不能定义方法的默认行为。使用特征可以让你写出通用化的函数,能够接受任何参数类型,只要这个类型实现了某一“特性”。
例如:
trait SomeTrait: fn required_method(self, x: Int) :
...
这样,你就可以定义一个“遵循”此特性的结构体:
struct FirstStruct(SomeTrait) :
var base: Int
fn __init__(inout self, base: Int):
self.base = base
fn required_method(self, x: Int):
print("first:", self.base + x)
struct SecondStruct(SomeTrait):
var base: Int
fn init(inout self, base: Int):
self.base = base
fn required_method(self, x: Int):
print("second:", self.base * x)
然后可以写出一个使用这个特性的函数:
fn funcwithtraits[T: SomeTrait](x: T) :
x.required_method(10)
fn main():
var first = FirstStruct(2)
funcwithtraits(first)
var second = SecondStruct(2)
funcwithtraits(second)
如果没有 trait,那么 只能将 x 指定为一个确定的数据类型,而如果有 trait了,x 只要是 “遵循”了someTrait的数据类型就可以。
因此上面程序会输出:
first: 12
second: 20
Parameterization
上面的 [T: SomeTrait] 的语法比较特殊,没在其它语言中见过。这在 Mojo 里面称作 Parameterization (翻译成 参数化?)。在一般的编程语言中,parameter 和 argument 虽然都是参数,在实际应用中也往往混用,如果一定要细致区分一下的话,约定俗成的就是 parameter 是指函数声明时定义的形参,而 argument 是函数调用时传递的实参。但在Mojo中,将 parameter 看做是“编译期”的参数,而 argument 则是在运行时的参数。parameter是定义在中括号里的,而argument是定义在小括号里的。
fn repeat[count: Int](msg: String) :
for i in range(count) :
print(msg)
fn main():
repeat[2]('Hello')
$ mojo 8-2.mojo
Hello
Hello
因为在编译期能确定 parameter 参数,这些参数在运行时不会再发生变化,因此 Mojo 就可以进行更深入的优化。
参数化也可以定义在 struct 上面,一般是用来定义参数类型。最通常的用途就是用来实现通用容器,类似 C++中的STL中的容器。
struct GenericArray[T: AnyRegType] :
var data: Pointer[T]
var size: Int
fn __init__(inout self, *elements: T)
self.size = len(elements)
self.data = Pointer[T].alloc(self.size)
for i in range(self.size):
self.data[i] = elements[i]
fn __del__(owned self):
self.data.free()
fn __getitem__(self, i: Int) raises -> T:
if i < self.size():
return self.data[i]
else:
raise Error("Out of bounds")
fn main():
var array = GenericArray[Int](1, 2, 3, 4)
for i in range(array.size):
print(array[i])