Mojo编程七:函数、参数约定与高阶编程

第 7 章 函数、参数约定与高阶编程

本章目标:把 Mojo 的函数体系一次讲透------实参约定(argument conventions) 、六个标记(/、*、//) 、变长参数、命名结果、重载决议、raises 效果,以及最容易被低估的部分:高阶编程(把函数当值传递的四种正确写法、lambda、推导式)。

参数约定是 Mojo 与 Python/C++ 差异最大、也最能体现"系统编程语言"底色的地方。第 12 章会从所有权角度再讲一遍,本章从"怎么写函数"的角度讲。所有代码在 Mojo 1.2.0 实测。


7.1 函数剖析

一个 Mojo 函数声明可以包含以下部分:

text 复制代码
def 函数名[参数(编译期)...](实参(运行时)...) 效果 -> 返回类型:
    函数体
  • 参数(parameters) :方括号里的编译期值,用于元编程(第 17 章)。
  • 实参(arguments) :圆括号里的运行时值,就是其他语言说的"函数参数"。
  • 效果(effects) :raises、abi("C") 等,写在右括号之后、-> 之前。
  • 返回值 :-> 类型,可省略(等价于 -> None)。
  • 函数体 :必须有,什么都不做就写 pass。

最小函数:

mojo 复制代码
def do_nothing():
    pass

def main():
    do_nothing()
    print("ok")
output 复制代码
ok

术语提醒 :Mojo 官方文档里 "parameter" 专指编译期参数,"argument" 专指运行时实参。这与 Python/C++ 里两者混用不同。本书沿用官方术语:参数 = [...],实参 = (...)。混淆这两个词是读 Mojo 文档时最大的障碍。

函数的硬性要求:

  1. 每个实参都必须声明类型(没有动态类型)。
  2. 不返回值时可以省略 -> ...,或写 -> None(两者等价)。
  3. 返回值必须写 -> 类型,或者用命名结果 (out 实参,见 7.7)。
  4. 函数体不能省略,pass 也算体。

函数可以在定义之前被调用(不需要前向声明):

mojo 复制代码
def main():
    print(later(2))

def later(x: Int) -> Int:
    return x * 10
output 复制代码
20

7.2 调用形式:位置、关键字、混合

默认情况下,实参既可以按位置传,也可以按名字传,还能混用:

mojo 复制代码
def max2(a: Int, b: Int) -> Int:
    return a if a > b else b

def main():
    print(max2(5, 7))          # 位置:a=5, b=7
    print(max2(b=3, a=9))      # 关键字,顺序可颠倒
    print(max2(5, b=7))        # 混合
output 复制代码
7
9
7

默认值与顺序规则

带默认值的实参是"可选实参":

mojo 复制代码
def my_pow(base: Int, exp: Int = 2) -> Int:
    return base ** exp

def main():
    print(my_pow(3))              # 用默认 exp=2
    print(my_pow(exp=3, base=2))  # 全用关键字
    print(my_pow(2, 10))          # 2**10
output 复制代码
9
8
1024

可选实参必须排在必需实参之后,否则:

mojo 复制代码
def wrong(x: Int, y: Int = 0, z: Int) -> Int:
    return x + y + z

def main():
    print(wrong(1, 2, 3))
output 复制代码
error: required positional argument follows optional positional argument; change the ordering

默认值可以是运行时表达式(每次调用时求值),不限于字面量:

mojo 复制代码
def now_ms() -> Int:
    return 42

def f(x: Int = now_ms()) -> Int:
    return x

def main():
    print(f(), f(7))
output 复制代码
42 7

7.3 三个标记:/、*、//

Mojo 用三个"标记"把参数/实参列表切成不同区域。它们必须按 // → / → * 的顺序出现,各最多一次;/ 不能是列表第一个,* 不能是最后一个。

标记 作用于实参 作用于参数 含义
// 不支持 仅推断 标记之前的参数只能由编译器从实参推断,调用方不能按位置指定
/ 仅位置 仅位置 之前的实参只能按位置传
* 仅关键字 仅关键字 之后的实参只能按名字传

/:仅位置实参

mojo 复制代码
def div(a: Int, b: Int, /) -> Int:
    return a // b

def main():
    print(div(10, 3))
output 复制代码
3

用关键字调用会被拒:

mojo 复制代码
def div(a: Int, b: Int, /) -> Int:
    return a // b

def main():
    print(div(a=10, b=3))
output 复制代码
error: invalid call to 'div': missing required argument: 'a'

什么时候用 /?① 实参名对调用者没有意义(min(a, b));② 你想保留将来改名的自由而不破坏兼容。

*:仅关键字实参

mojo 复制代码
def configure(*, verbose: Bool, retries: Int = 3):
    print("verbose:", verbose, "retries:", retries)

def main():
    configure(verbose=True, retries=5)
    configure(verbose=False)
output 复制代码
verbose: True retries: 5
verbose: False retries: 3

按位置传会报错:

mojo 复制代码
def configure(*, verbose: Bool):
    print(verbose)

def main():
    configure(True)
output 复制代码
error: invalid call to 'configure': missing required argument: 'verbose'

仅关键字实参不受"可选必须在必需之后"的顺序限制,可以自由混排必需与可选:

mojo 复制代码
def configure2(*, retries: Int = 3, verbose: Bool):
    print(verbose, retries)

def main():
    configure2(verbose=True)
output 复制代码
True 3

//:仅推断参数

// 只作用于编译期参数:它之前的参数只能由编译器从实参推断出来,调用方不能按位置显式指定。

mojo 复制代码
from std.format import Writable

def inferred_type[T: Writable, //](value: T):
    print(t"Value is {value}. Type is {reflect[T].name()}.")

def main():
    inferred_type(5)
    inferred_type("Hello")
output 复制代码
Value is 5. Type is SIMD[DType.int, 1].
Value is Hello. Type is String.

想显式指定 T,按位置传是错的:

mojo 复制代码
from std.format import Writable

def f[T: Writable, //](value: T):
    print(value)

def main():
    f[Int](5)
output 复制代码
error: invalid call to 'f': unexpected parameter

必须用关键字形式 绕过限制:f[T=Int](5)(合法,但会失去推断的便利)。

为什么高阶函数离不开 // :7.11 会看到,接受"函数值"的泛型函数标准写法是 def transform[T: Copyable, U: Copyable, F: def(T) -> U, //](f: F, list: List[T])。 F 是函数类型参数,只能从 f 推断,绝不该让调用方写 transform[Int, Int, ???](...)。

实参顺序的完整规则

一个函数签名里,各类实参的出现顺序必须是:

  1. 必需的位置实参
  2. 可选的位置实参
  3. 变长实参 *args
  4. 必需的仅关键字实参
  5. 可选的仅关键字实参
  6. 变长关键字实参 **kwargs

7.4 六种实参约定(argument conventions)

这是 Mojo 函数体系的核心。约定决定"函数拿到的是引用还是所有权,能不能改",写在实参名前面:

约定 函数拿到什么 能改吗 改动对调用者可见吗 调用方需要做什么
默认(无关键字) 不可变引用 否 --- 无
mut 可变引用 是 是 传入的值本身必须是可变的
var 所有权(一个值) 是 否(函数拥有它) 可选加 ^ 转移所有权
out 未初始化的输出位 必须初始化 是(作为返回值) 不传 (用于命名结果/__init__ 的 self)
deinit 一个即将被销毁的值 是(可搬空字段) --- 通常配合 ^
ref 参数化可变性的引用 跟随被引用值 是 高级用法,第 13 章

默认:不可变引用

mojo 复制代码
def print_list(list: List[Int]):
    print(list)

def main():
    var values: List[Int] = [1, 2, 3, 4]
    print_list(values)
    print("caller still owns:", values)
output 复制代码
[1, 2, 3, 4]
caller still owns: [1, 2, 3, 4]

函数内可以读 list,不能改。它不是副本------大对象按默认约定传递是零成本的(不调用拷贝构造和析构)。

与 C++/Rust 对比:

Mojo 默认约定 C++ const& Rust &T
是否拷贝 否 否 否
生命周期检查 有(origin/lifetime) 无 有(借用检查器)
小值(Int/SIMD)传递 走寄存器 通常也走寄存器 默认是 move
调用方需要符号吗 不需要 不需要 需要 &

小值走寄存器这一点很重要:def f(x: Int) 不会有"取地址"的开销,这让 Mojo 的默认约定既是安全的又是快的。

mut:可变引用

mojo 复制代码
def mutate(mut l: List[Int]):
    l.append(5)

def main():
    var values: List[Int] = [1, 2, 3, 4]
    mutate(values)
    print(values)
output 复制代码
[1, 2, 3, 4, 5]

记忆法:mut 意味着"函数内的改动,函数外看得见"。

等价的"拷贝---修改---返回"写法(更耗内存):

mojo 复制代码
def mutate_copy(l: List[Int]) -> List[Int]:
    var copy = l.copy()
    copy.append(5)
    return copy^

def main():
    var values: List[Int] = [1, 2, 3, 4]
    values = mutate_copy(values)
    print(values)
output 复制代码
[1, 2, 3, 4, 5]

两条限制:

  1. 传给 mut 的实参本身必须可变 。把不可变引用再当 mut 传下去会报错------Mojo 不能从不可变引用造出可变引用。
  2. mut 实参不能有默认值:
mojo 复制代码
def f(mut x: Int = 3):
    x += 1

def main():
    f()
output 复制代码
error: 'mut' arguments must not have defaults

参数排他性(argument exclusivity)

如果函数拿到了某个值的可变引用,它就不能同时拿到同一个值的任何其它引用(可变或不可变)。这条规则既防 bug 又利于优化:

mojo 复制代码
def append_twice(mut s: String, other: String):
    s += other
    s += other

def main():
    var my_string = String("o")
    append_twice(my_string, my_string)
    print(my_string)
output 复制代码
error: aliasing values passed mutably to 's' argument and passed immutably to
       'other' argument in 'append_twice' call
note: 'origin_of(my_string)' value is passed through aliasing 'imm' argument 'other'

如果没有这条规则,append_twice(my_string, my_string) 的结果会很难推理:第一次 s += other 同时改了 s 和 other(它们是同一个值),于是结果是 "oooo" 而不是直觉上的 "ooo"。

需要同时传同一个值时,先拷贝一份:

mojo 复制代码
def append_twice(mut s: String, other: String):
    s += other
    s += other

def main():
    var my_string = String("o")
    var other_string = my_string.copy()
    append_twice(my_string, other_string)
    print(my_string)
output 复制代码
ooo

排他性对寄存器传递的平凡类型 (Int、Bool、小 SIMD)不强制------它们总是按值拷贝,传两次就是两份副本。

var:接管所有权

var 表示"函数拥有这个值"。注意它不保证函数拿到的是"原值",只保证拿到"某个值的所有权",来源有三种:

  1. 转移 :调用方写 ^,原变量随之失效;
  2. 拷贝 :调用方不写 ^,且类型可拷贝 → 编译器拷一份;
  3. 新建值:调用方传的是函数调用结果或字面量 → 直接交给被调方。
mojo 复制代码
def take_text(var text: String):
    text += "!"
    print("inside:", text)

def main():
    var message = String("Hello")
    take_text(message)          # 不写 ^ → 拷贝一份进去
    print("outside:", message)
    take_text(message^)         # 写 ^ → 转移所有权
    print("transferred")
output 复制代码
inside: Hello!
outside: Hello
inside: Hello!
transferred

转移之后原变量就"未初始化"了,再用会报错:

mojo 复制代码
def take_text(var text: String):
    print(text)

def main():
    var message = String("Hello")
    take_text(message^)
    print(message)
output 复制代码
error: use of uninitialized value 'message'

函数拥有值之后,值在函数退出时销毁------除非再转移出去:

mojo 复制代码
def add_to_list(var name: String, mut list: List[String]):
    list.append(name^)     # 所有权转给 list,name 变为未初始化
                           # 函数结束时没有东西需要销毁

def consume_string(var s: String):
    print("consuming:", s)
    # s 在函数结束时销毁

def main():
    var names: List[String] = []
    add_to_list("Ada", names)
    consume_string("Bye")
    print(names)
output 复制代码
consuming: Bye
[Ada]

out:命名结果与初始化位

out 实参在函数开始时未初始化 ,函数返回前必须初始化 。调用方不传 这个实参------它是返回值。最常见的用途是 __init__(out self, ...),其次是命名结果(7.7)。

deinit:接收一个将被销毁的值

deinit 实参在函数开始时已初始化,函数结束时被视为已销毁,因此可以安全地把字段从中搬走:

mojo 复制代码
from std.format import Writable

struct Pair(Writable):
    var a: String
    var b: String

    def __init__(out self, a: String, b: String):
        self.a = a
        self.b = b

    def destroy_other(self, deinit other: Self):
        print("took:", other.a)
        var stolen = other.b^      # 从即将销毁的值里搬走字段
        print("stolen:", stolen)

def main():
    var p = Pair("x", "y")
    var q = Pair("m", "n")
    p.destroy_other(q^)
output 复制代码
took: m
stolen: n

deinit 不只用于 self------任何实参都可以标 deinit,用来写"消费型"API(比如对象池回收、资源移交)。

约定选择速查

你想做什么 用哪个
只读一个大对象 默认(不可变引用)
就地修改调用者的值 mut
拿走并消耗一个值(如存入容器) var + 调用方 ^
返回一个不可移动/不可拷贝的值 out(命名结果)
拆解一个即将销毁的值 deinit
写泛型容器、可变性跟随调用方 ref(第 13 章)

7.5 变长参数

同质变长实参 *name: T

所有实参类型相同:

mojo 复制代码
def sum_all(*values: Int) -> Int:
    var total = 0
    for v in values:
        total += v
    return total

def main():
    print(sum_all())
    print(sum_all(1, 2, 3, 4))
output 复制代码
0
10

函数体里 values 是一个 VariadicList (可迭代、可 len()、可下标):

mojo 复制代码
def inspect(*values: Int):
    print("type:", reflect[type_of(values)].name())
    print("len:", len(values))
    print("values[0]:", values[0], " values[2]:", values[2])

def main():
    inspect(10, 20, 30)
output 复制代码
type: std.builtin.variadics.VariadicList[False, {}, {}, SIMD[DType.int, 1], False]
len: 3
values[0]: 10  values[2]: 30

加 mut 就能改调用方的值(配合 for ref):

mojo 复制代码
def make_worldly(mut *strs: String):
    for ref s in strs:
        s += " world"

def main():
    var a = String("hello")
    var b = String("goodbye")
    make_worldly(a, b)
    print(a, "|", b)
output 复制代码
hello world | goodbye world

传给 mut *strs 的实参必须是可变的 :make_worldly("a", "b") 会报 error: invalid call to 'make_worldly': value passed to mutable argument 'strs' must be mutable。

也可以用下标改:

mojo 复制代码
def make_worldly2(mut *strs: String):
    for i in range(len(strs)):
        strs[i] += "!"

def main():
    var a = String("x")
    make_worldly2(a)
    print(a)
output 复制代码
x!

异质变长实参(variadic pack)*args: *Ts

每个实参类型可以不同。这需要变长类型参数 :* 同时出现在名字和类型标注前。

mojo 复制代码
from std.format import Writable

def print_all[*Ts: Writable](*args: *Ts):
    comptime for idx in range(args.__len__()):
        print(args[idx], end=" ")
    print()

def main():
    print_all("Hello", 42, 3.14)
    print_all(1)
    print_all()
output 复制代码
Hello 42 3.14 
1

要点:

  • [*Ts: Writable] 声明"一个类型列表,每个都符合 Writable";调用时不需要显式传类型列表,编译器从实参生成。
  • args 的类型是 VariadicPack(每个元素类型不同、大小不同)。
  • 遍历必须用 comptime for ,长度必须用 args.__len__()。

为什么不能用 len(args)? VariadicPack.__len__() 是 @staticmethod,所以 args.__len__() 等价于 type_of(args).__len__(),是编译期常量 ;而 len(args) 是运行时表达式。comptime for 的迭代次数必须是编译期值,所以只能写 args.__len__()。

统计任意个可转整数的值:

mojo 复制代码
def count_many_things[*ArgTypes: Intable](*args: *ArgTypes) -> Int:
    var total = 0
    comptime for i in range(args.__len__()):
        total += Int(args[i])
    return total

def main():
    print(count_many_things(5, 11.7, 12))
output 复制代码
28

11.7 被 Int() 截断成 11,所以 5 + 11 + 12 = 28(第 5 章讲过浮点转整数向零截断)。

变长实参的限制:

mojo 复制代码
def wrong(*args: Int = 0):
    pass

def main():
    wrong()
output 复制代码
error: variadic arguments must not have defaults

另外:一个函数最多一个 *args;out 实参不能是变长的(def wrong(out *results: Int) 非法)。

变长关键字实参 var **kwargs: T

mojo 复制代码
def print_nicely(var **kwargs: Int):
    for item in kwargs.items():
        print("  ", item.key, "=", item.value)

def main():
    print_nicely(a=7, y=8)
output 复制代码
   a = 7
   y = 8

函数体里 kwargs 是一个 StringDict[T] (键是 String 的字典)。当前限制:

  • 必须声明为 var **kwargs(函数拥有这个字典),其它约定不支持;
  • 所有 kwargs 必须同类型,该类型必须 Copyable;
  • 不支持字典解包 :print_nicely(**some_dict) 还不行;
  • 不支持变长关键字参数 :def f[**kwparams: Int]() 还不行。

*args 之后的实参自动变成仅关键字

mojo 复制代码
def sum_named(*values: Int, name: String) -> Int:
    print(name, end=": ")
    var total = 0
    for v in values:
        total += v
    return total

def main():
    print(sum_named(1, 2, 3, name="total"))
output 复制代码
total: 6

sum_named(1, 2, 3, "subtotal") 会报 missing required keyword argument。


7.6 重载与重载决议

同名不同签名的函数构成一个重载集(overload set):

mojo 复制代码
def add(x: Int, y: Int) -> Int:
    return x + y

def add(x: String, y: String) -> String:
    return x + y

def main():
    print(add(1, 2))
    print(add("x", "y"))
    print(add("lit", "eral"))
output 复制代码
3
xy
literal

add("lit", "eral") 能匹配 add(x: String, y: String),是因为 String 有一个 @implicit 初始化器,允许 StringLiteral 隐式转换。

决议依据与优先级

编译器挑选候选时看:实参的个数、位置、关键字 ;每个实参与参数的类型 ;实参约定 ;是否静态方法;以及是否需要隐式转换。

筛选优先级(从高到低):

  1. 需要最少隐式转换的候选(实参与参数都算);
  2. 没有变长实参的候选;
  3. 没有变长参数的候选;
  4. 参数签名最短的候选;
  5. 非 @staticmethod 候选优先于 @staticmethod。

编译器不看 返回类型、raises 效果、调用上下文。因此:

mojo 复制代码
def f(x: Int) -> Int:
    return x

def f(mut x: Int) -> Int:
    return x + 1

def main():
    print(f(1))
output 复制代码
error: redefinition of 'f', cannot overload on argument conventions

只有返回类型不同、或只有 raises 不同的两个函数是重复定义,编译器直接拒绝第二个。

歧义与显式转换

当两个候选都需要一次隐式转换时,调用是歧义的:

mojo 复制代码
struct A:
    @implicit
    def __init__(out self, s: String):
        pass

struct B:
    @implicit
    def __init__(out self, s: String):
        pass

def foo(x: A):
    print("A")

def foo(x: B):
    print("B")

def main():
    foo("hello")
output 复制代码
error: ambiguous call to 'foo', each candidate requires 1 implicit conversion,
       disambiguate with an explicit cast
note: candidate declared here  (def foo(x: A))
note: candidate declared here  (def foo(x: B))

解决办法:显式构造你想要的类型 ------ foo(A("hello"))。

重载集不能被 import/alias/参数扩展 。如果本地函数和导入函数同名会冲突,用别名导入:from package import foo as imported_foo。


7.7 返回值

普通返回

mojo 复制代码
def get_greeting() -> String:
    return "Hello"

def main():
    print(get_greeting())
output 复制代码
Hello

返回值默认是被调用方拥有的值 ;return 的字面量会按声明的返回类型隐式转换(上例把 StringLiteral 转成 String)。

多值返回:用元组

mojo 复制代码
def minmax(xs: List[Int]) -> Tuple[Int, Int]:
    var lo = xs[0]
    var hi = xs[0]
    for i in range(len(xs)):
        if xs[i] < lo:
            lo = xs[i]
        if xs[i] > hi:
            hi = xs[i]
    return (lo, hi)

def main():
    var lo, hi = minmax([3, 1, 9, 4])
    print("min:", lo, "max:", hi)
output 复制代码
min: 1 max: 9

返回类型必须写成 Tuple[A, B];调用方可以直接解构(var lo, hi = ...)。

命名结果(named results):out 实参

命名结果让函数能返回不可移动、不可拷贝 的值。语法是把返回值声明成一个 out 实参:

mojo 复制代码
def incr(a: Int, out b: Int):
    b = a + 1

def main():
    var r = incr(41)
    print(r)
output 复制代码
42

对调用方来说,def incr(a: Int) -> Int 和 def incr(a: Int, out b: Int) 完全等价 ------命名结果是实现细节。约定上 out 实参放最后,但语法上可以放任何位置:

mojo 复制代码
from std.format import Writable

struct Tag(Writable):
    var text: String

    def __init__(out self, t: String):
        self.text = t

def make(var name: String, out tag: Tag, suffix: String):
    tag = Tag(name)
    tag.text += suffix

def main():
    var t = make("Bob", "!")
    print(t.text)
output 复制代码
Bob!

带命名结果的函数不需要显式 return :函数正常结束(或 return 不带值)时,out 实参的值就是返回值;如果写了 return 值,则返回那个值。

为什么需要它 :普通 return 要求值能被移动或拷贝到调用方。对于 not Movable 的类型,这条路走不通:

mojo 复制代码
struct Immovable(not Movable):
    var name: String

    def __init__(out self, var name: String):
        self.name = name^

def make(var n: String) -> Immovable:
    var obj = Immovable(n^)
    obj.name += "!"
    return obj^

def main():
    var o = make("Blob")
    print(o.name)
output 复制代码
error: cannot transfer value into destination, because 'Immovable' doesn't conform to 'Movable'

用命名结果就行------对象直接在调用方的存储里构造,从不移动:

mojo 复制代码
struct Immovable(not Movable):
    var name: String

    def __init__(out self, var name: String):
        self.name = name^

def make(var n: String, out obj: Immovable):
    obj = Immovable(n^)
    obj.name += "!"

def main():
    var o = make("Blob")
    print(o.name)
output 复制代码
Blob!

如果值立即 返回(不经过局部变量),普通 -> T 也可以,因为没有中间存储需要移动:

mojo 复制代码
struct Immovable(not Movable):
    var name: String

    def __init__(out self, var name: String):
        self.name = name^

def make_direct(var n: String) -> Immovable:
    return Immovable(n^)

def main():
    print(make_direct("Direct").name)
output 复制代码
Direct

main() 不能有返回类型

mojo 复制代码
def main() -> Int:
    return 3
output 复制代码
error: 'main()' does not return a value; remove the return type

想返回进程退出码,用 std.process.exit()(第 3、21 章)。

返回引用

函数可以返回 ref 引用(-> ref[origin] T),但必须标注 origin,这是第 13 章的主题:

mojo 复制代码
def first(ref xs: List[Int]) -> ref Int:
    return xs[0]

def main():
    var xs: List[Int] = [1, 2, 3]
    print(first(xs))
output 复制代码
error: 'ref' result requires an origin specifier

7.8 泛型函数入门

方括号里声明参数,用 trait 约束类型(第 17、18 章系统化,这里给出日常够用的部分):

mojo 复制代码
def compare[T: AnyType](x: T, y: T) -> Int32 where conforms_to(T, Comparable):
    if x < y:
        return -1
    elif x > y:
        return 1
    else:
        return 0

def main():
    print(compare(5, 10))
    print(compare(7, 7))
    print(compare("Z", "A"))
output 复制代码
-1
0
1

where 约束写在末尾

where 子句出现在声明最后(返回类型之后;没有返回类型就在实参列表之后):

mojo 复制代码
def process[n: Int](data: SIMD[.float32, n]) -> Float32 where (
    n == 1 or n == 2 or n == 4 or n == 8 or n == 16 or n == 32
):
    var sum: Float32 = 0.0
    for i in range(n):
        sum += data[i]
    return sum

def main():
    var data = SIMD[.float32, 4](1.0, 2.0, 3.0, 4.0)
    print("sum:", process[n=4](data))
output 复制代码
sum: 10.0

where 写在参数列表里面是非法的(1.x 起):

mojo 复制代码
def wrong[n: Int where n > 0]():
    pass

def main():
    wrong[1]()
output 复制代码
error: 'where' clauses inside parameter lists are no longer supported
note: use a trailing 'where' clause after the signature instead

where 里可以带自定义错误消息,让约束失败时更好读:

mojo 复制代码
def process[n: Int](data: SIMD[.float32, n]) -> Float32 where (
    n > 0, "width must be positive"
):
    ...

参数推断

有足够上下文时,编译器能推断出参数,不必显式写:

mojo 复制代码
def double_it[T: Intable](x: T) -> Int:
    return Int(x) * 2

def main():
    print(double_it[Int](21))   # 显式
    print(double_it(21))        # 推断
    print(double_it(1.5))       # 推断 T = Float64
output 复制代码
42
42
3

Int(1.5) 向零截断成 1,所以最后一个输出 3(第 5 章的锋利边缘)。


7.9 raises:可失败函数

Mojo 函数默认不抛错 。要传播错误,加 raises:

mojo 复制代码
def boom() raises:
    raise Error("There was an error.")

def handle_it():
    try:
        boom()
    except e:
        print("Handled an error:", e)

def main():
    handle_it()
output 复制代码
Handled an error: There was an error.

非 raises 函数调用 raises 函数必须处理或声明传播:

mojo 复制代码
def boom() raises:
    raise Error("bad")

def main():
    boom()
output 复制代码
error: cannot call function that may raise in a context that cannot raise
note: try surrounding the call in a 'try' block
note: or mark surrounding function as 'raises'

两条出路:try/except 就地处理,或者把自己也标成 raises 往上抛:

mojo 复制代码
def boom() raises:
    raise Error("bad")

def propagate() raises:
    boom()          # 直接传播

def main() raises:
    try:
        propagate()
    except e:
        print("caught at top:", e)
output 复制代码
caught at top: bad

try 支持 except / else:

mojo 复制代码
struct ValidationError(Writable):
    var msg: String

    def __init__(out self, msg: String):
        self.msg = msg

def validate(v: Int) raises ValidationError -> Int:
    if v < 0:
        raise ValidationError("negative value")
    return v

def main() raises:
    print(validate(3))
    try:
        print(validate(-1))
    except e:
        print("caught:", e)
    try:
        validate(0)
    except e:
        print("caught:", e)
    else:
        print("no error for 0")
output 复制代码
3
caught: ValidationError(msg=negative value)
no error for 0

要点:

  • raises ValidationError 是类型化错误:签名里声明具体错误类型(一个函数最多声明一个)。
  • 自定义错误类型就是普通 struct (不需要继承 Error),实现 Writable 就能被 print/except 打印出来(上面 ValidationError(msg=negative value) 是 Writable 的反射默认实现)。
  • else 子句在 try 体没有抛错时执行。

第 10 章会系统讲错误处理(Error vs 类型化错误、try 表达式、错误上下文、assert_raises 测试)。


7.10 高阶编程(一):函数是值

函数值与 thin

一个不捕获环境的函数值叫 thin(瘦函数指针),它是编译期可确定的纯代码地址:

mojo 复制代码
def square(x: Int) -> Int:
    return x * x

def main():
    var f = square
    print("f(5) =", f(5))

    var g: def(Int) thin -> Int = square
    print("g(6) =", g(6))
output 复制代码
f(5) = 25
g(6) = 36

函数类型的写法:

写法 含义
def(Int) -> Int 闭包类型(可能带捕获状态)
def(Int) thin -> Int 瘦函数指针(不带状态)
def(Int) thin abi("C") -> Int C 调用约定的函数指针
def[w: Int](Int) thin -> None 带编译期参数的函数类型
Some[def(Int) -> Int] "某个符合该签名的可调用对象"(存在类型)

别指望能打印函数类型 :reflect[type_of(f)].name() 对函数值返回 std.builtin._stubs.__MLIRType[<unprintable>]。调试时用 --emit asm 看符号名更实在。

关键陷阱 :把函数赋值给一个非 thin 的闭包类型变量会失败:

mojo 复制代码
def square(x: Int) -> Int:
    return x * x

def main():
    var g: def(Int) -> Int = square
    print(g(6))
output 复制代码
error: cannot implicitly convert 'def square(x: Int) thin -> Int' value to 'def(Int) -> Int'

thin → closure 不做隐式转换。要存函数指针就写 thin;要接受"任意可调用对象"就用 7.11 的四种写法。

C 调用约定

mojo 复制代码
def c_add(a: Int32, b: Int32) abi("C") -> Int32:
    return a + b

def main():
    var fp: def(Int32, Int32) thin abi("C") -> Int32 = c_add
    print("via C-ABI pointer:", fp(1, 2))
output 复制代码
via C-ABI pointer: 3

abi("C") 让函数使用 C 调用约定,可以被 C 调用或存进 C 函数指针(第 29 章 C 互操作、@export 导出)。

不要 把非 Mojo 的 abi() 与 raises 组合:def (String) abi("Mojo") raises 可以,def (String) abi("C") raises 会被拒绝------抛错函数的调用约定有额外机制。abi("Mojo") 是默认值,不用写。

受约束的函数类型

thin 函数类型可以带自己的 where 约束,约束成为类型的一部分:

mojo 复制代码
comptime Kernel = def[w: Int](Int) thin -> None where (
    w > 0, "width must be positive"
)

def apply[F: Kernel](x: Int):
    F[4](x)     # ok
    F[0](x)     # error: 违反约束

约束是逆变 的:函数自身的 where 比类型承诺的更弱(或没有)时才满足该类型。只有 thin 函数类型接受 where;如果声明级 where 跟在函数类型返回值后面,需要用括号区分:

mojo 复制代码
# where 约束"返回的函数类型"
def inner_constraint[n: Int]() -> def() thin -> None where n > 0: ...

# where 约束 inner_constraint 自己
def outer_constraint[n: Int]() -> (def() thin -> None) where n > 0: ...

7.11 高阶编程(二):接受可调用对象的四种写法

这是 Mojo 里最容易写错的一块。直接把闭包类型当实参类型(f: def(Int) -> Int)通常行不通------先看失败案例:

mojo 复制代码
def apply(f: def(Int) -> Int, x: Int) -> Int:
    return f(x)

def main():
    print(apply(lambda (x: Int) -> Int: x * 3, 4))
output 复制代码
error: invalid call to '__call__': value cannot be converted from type value '$0'
       to an instance of '$0'; did you mean to instantiate '$0'?
error: invalid call to 'apply': value passed to 'f' cannot be converted from
       'def `lambda_0(x: Int) thin -> Int' to 'def(Int) -> Int'

正确写法有四种,按推荐顺序:

写法 A:仅推断的函数类型参数(最常用)

把函数类型声明成参数 F,放在 // 之前,然后实参类型是 F:

mojo 复制代码
def transform[T: Copyable, U: Copyable, F: def(T) -> U, //](f: F, list: List[T]) -> List[U]:
    return [f(item) for item in list]

def main() raises:
    var numbers: List[Int] = [2, 4, 6]
    print(transform(lambda (x: Int) -> Int: x * 10, numbers))
output 复制代码
[20, 40, 60]

这种写法接受内联 lambda、捕获型 lambda 变量、以及普通命名函数:

mojo 复制代码
def transform[T: Copyable, U: Copyable, F: def(T) -> U, //](f: F, list: List[T]) -> List[U]:
    return [f(item) for item in list]

def counter(x: String) -> Int:
    return x.count_codepoints()

def main() raises:
    var numbers: List[Int] = [2, 4, 6, 8, 10]

    # ① 内联的捕获型闭包 lambda
    var factor = 3
    print(transform(lambda (x: Int) -> Int: x ** factor, numbers))
    factor = 2
    print(transform(lambda (x: Int) -> Int: x ** factor, numbers))

    # ② 普通命名函数
    var words: List[String] = ["Lorem", "ipsum", "dolor", "sit", "amet"]
    print(transform(counter, words))

    # ③ 存进变量的捕获型 lambda
    var base = 100
    var adder = lambda (x: Int) {imm base} -> Int: x + base
    print(transform(adder, [1, 2, 3]))
output 复制代码
[8, 64, 216, 512, 1000]
[4, 16, 36, 64, 100]
[5, 5, 5, 3, 4]
[101, 102, 103]

但有一个陷阱 :把不捕获任何东西 的 lambda 存进变量,它的类型会变成 thin 函数指针,与闭包类型的参数 F 不兼容:

mojo 复制代码
def transform[T: Copyable, U: Copyable, F: def(T) -> U, //](f: F, list: List[T]) -> List[U]:
    return [f(item) for item in list]

def main() raises:
    var counter = lambda (x: String) -> Int: x.count_codepoints()
    var words: List[String] = ["ab", "cde"]
    print(transform(counter, words))
output 复制代码
error: invalid call to 'transform': value passed to 'f' cannot be converted from
       'def(x: String) thin -> Int' to 'F'

注意上面 ① 的内联写法能过、③ 的变量写法也能过(因为它捕获了 base),唯独"无捕获的 lambda 变量"过不去。原因:无捕获的 lambda 是 thin 的(编译期已知的纯代码地址),而 F: def(T) -> U 描述的是闭包类型;Mojo 1.2 不做 thin → closure 的隐式转换。

实践准则 :传可调用对象时,① 直接内联写 lambda;② 或写成命名 def;③ 若一定要存变量,让它捕获点东西,或者把被调函数的参数类型改成 def(T) thin -> U(写法 B/D)。

写法 B:thin 函数指针作为编译期参数

如果确定不需要捕获(纯函数),可以让函数指针本身成为参数,编译期完全内联,零调用开销:

mojo 复制代码
def inplace_transform[T: ImplicitlyCopyable & Deinitable, //, f: def(T) thin -> T](
    mut list: List[T]
):
    for index in range(len(list)):
        list[index] = f(list[index])

def main() raises:
    var numbers: List[Int] = [1, 2, 3, 4, 5]
    inplace_transform[lambda (x: Int) -> Int: x * 2](numbers)
    print("transformed:", numbers)
output 复制代码
transformed: [2, 4, 6, 8, 10]

注意调用形式:lambda 写在方括号里(它是编译期参数)。传捕获环境的 lambda 会失败,因为闭包不是编译期常量:

mojo 复制代码
var factor = 3
inplace_transform[lambda (x: Int) -> Int: x ** factor](numbers)
# error: 闭包不能用作编译期函数指针参数

写法 C:Some[def(...) -> ...] 存在类型

不想引入参数、也不想推断时,用 Some[...]:

mojo 复制代码
def apply_some(f: Some[def(Int) -> Int], x: Int) -> Int:
    return f(x)

def square(x: Int) -> Int:
    return x * x

def main():
    print(apply_some(square, 4))
    print(apply_some(lambda (y: Int) -> Int: y + 1, 4))
output 复制代码
16
5

Some[Trait] 表示"某个符合该签名的具体类型",由编译器在调用点单态化。

Some[def(Int) thin -> Int] 不合法 :Some[] 需要一个 trait,而 thin 函数类型不是 trait。

output 复制代码
error: parameter 'Trait' has 'AnyTrait[AnyType]' type, but value has type
       '__mlir_type.`!kgen.non_struct_type`'

写法 D:普通 thin 实参

只要传的是命名函数(thin),直接声明 thin 实参类型即可:

mojo 复制代码
def map_thin[T: Copyable, U: Copyable](f: def(T) thin -> U, input: List[T]) -> List[U]:
    var result: List[U] = []
    for i in range(len(input)):
        result.append(f(input[i]))
    return result^

def square(x: Int) -> Int:
    return x * x

def main() raises:
    var nums: List[Int] = [1, 2, 3]
    print(map_thin(square, nums))
output 复制代码
[1, 4, 9]

传捕获型 lambda 会被拒:

output 复制代码
error: invalid call to 'callit': value passed to 'f' cannot be converted from
       'def(x: Int) -> Int' to 'def(Int) thin -> Int'

四种写法怎么选

写法 接受命名函数 接受 thin lambda 接受捕获型闭包 运行期开销 何时用
A:F: def(T)->U, // + f: F ✅ ✅(内联写) ✅ 通常被内联 默认选择,标准库风格
B://, f: def(T) thin -> U ✅ ✅ ❌ 零(编译期绑定) 纯函数、追求极致内联
C:Some[def(T)->U] ✅ ✅ ✅ 单态化后同 A 不想写参数列表时
D:f: def(T) thin -> U ✅ ✅ ❌ 间接调用 需要把函数存起来/传 C 边界

7.12 lambda 表达式

语法:必须带括号和类型

mojo 复制代码
def main():
    var inc = lambda (x: Int) -> Int: x + 1
    print(inc(41))

    var add = lambda (x: Int, y: Int) -> Int: x + y
    print(add(2, 3))

    var no_arg = lambda -> String: "hi"
    print(no_arg())
output 复制代码
42
5
hi

Python 风格的 lambda x: x + 1 不被支持:

mojo 复制代码
def main():
    var inc = lambda x: x + 1
    print(inc(1))
output 复制代码
error: unparenthesized lambda arguments are not supported; write them in parentheses,
       with types, e.g. `lambda (x: Int, y: Int) -> Int: x + y`

lambda 体永远是单个表达式 (要多语句就用嵌套 def,见 7.13)。

带捕获列表的 lambda

lambda 也可以写捕获列表,语法与嵌套函数相同(第 11 章详解):

mojo 复制代码
def increment[Key: ImplicitlyCopyable & Hashable & Equatable & Deinitable](
    mut d: Dict[Key, Int], key: Key
):
    d[key] = d.get(key, 0) + 1

def apply[T: Copyable, F: def(T) -> None, //](f: F, items: List[T]):
    for i in range(len(items)):
        f(items[i])

def main() raises:
    var histogram: Dict[Int, Int] = {}
    var collector = lambda (n: Int) {mut histogram}: increment(histogram, n)
    apply(collector, [5, 5, 5, 3, 4, 11, 10, 4, 3, 9, 4, 6, 4, 8])
    print("histogram:", histogram)

    var stars = lambda (n: Int) -> String: "*" * n
    for key in histogram.keys():
        print(t"{key}: {stars(histogram.get(key, 0))}")
output 复制代码
histogram: {5: 3, 3: 2, 4: 4, 11: 1, 10: 1, 9: 1, 6: 1, 8: 1}
5: ***
3: **
4: ****
11: *
10: *
9: *
6: *
8: *

(Dict 的迭代顺序按内部哈希布局,不保证与插入顺序一致,别依赖它。)

{mut histogram} 声明"以可变引用方式捕获 histogram",所以 lambda 里的自增会反映到外面。不写捕获列表的 lambda 默认按不可变引用(imm)捕获。

推导式(comprehension)

Mojo 支持列表推导式和字典推导式,语法与 Python 一致:

mojo 复制代码
def main() raises:
    var xs: List[Int] = [1, 2, 3, 4, 5, 6]

    var squares = [x * x for x in xs]
    print("squares:", squares)

    var evens = [x for x in xs if x % 2 == 0]
    print("evens:", evens)

    var pairs = [(x, y) for x in range(2) for y in range(2)]
    print("pairs:", pairs)

    var grid = [[i * j for j in range(3)] for i in range(3)]
    print("grid:", grid)

    var squares_by_index = {i: i * i for i in range(4)}
    print("dict:", squares_by_index)
output 复制代码
squares: [1, 4, 9, 16, 25, 36]
evens: [2, 4, 6]
pairs: [(0, 0), (0, 1), (1, 0), (1, 1)]
grid: [[0, 0, 0], [0, 1, 2], [0, 2, 4]]
dict: {0: 0, 1: 1, 2: 4, 3: 9}

7.11 的 transform 就是靠 [f(item) for item in list] 一行实现的------推导式是 Mojo 里最简洁的"map"。


7.13 嵌套函数

函数里可以定义函数。嵌套函数默认看不到外层变量,必须用捕获列表显式声明:

mojo 复制代码
def outer(x: Int) -> Int:
    def inner() {imm x} -> Int:
        return x + 1
    return inner()

def counters() -> Int:
    var count = 0
    def bump() {mut count}:
        count += 1
    bump()
    bump()
    return count

def main():
    print(outer(41))
    print(counters())
output 复制代码
42
2
捕获写法 含义
{} 不捕获任何东西(等价于普通函数)
{imm x} 按只读引用捕获 x
{mut x} 按可变引用捕获,改动写回外层
{var x} 捕获 x 的副本
{var x^} 转移 x 的所有权进闭包
{imm} / {mut} 隐式捕获所有用到的外层值(按该约定)

没有捕获列表的嵌套函数就是普通局部函数;有捕获列表的嵌套函数就是闭包 。编译器会立即解析嵌套函数体,因此捕获绑定总是正确的。第 11 章专讲闭包的六种捕获约定、Copyable 闭包与 @__copy_capture。


7.14 装饰器与内联控制

Mojo 的装饰器全部是编译器内建的 (不支持自定义装饰器),写在声明上一行,可以叠加,自下而上应用。

装饰器 适用目标 作用
@inline(level) def / 方法 内联提示:.always / .automatic / .never / .nodebug
@always_inline def / 方法 旧写法,等价 @inline(.always)(新代码用 @inline)
@no_inline def / 方法 旧写法,等价 @inline(.never)
@implicit 方法(初始化器) 允许从该类型隐式转换
@staticmethod 方法 不需要实例即可调用
@export(name) def 导出符号(配合 abi("C") 做 C 互操作)
@deprecated(msg) 几乎一切 使用时产生警告
@doc_hidden 几乎一切 不出现在生成的文档里
@fieldwise_init struct 自动生成按字段初始化器
@align(N) struct 指定对齐字节数
@__parameter 嵌套函数 标记为编译期函数(元编程)
@__copy_capture 嵌套函数 让闭包可拷贝

@inline

mojo 复制代码
from std.builtin.inline_level import InlineLevel

@inline(.always)
def always_inlined(x: Int) -> Int:
    return x + 1

@inline(.never)
def never_inlined(x: Int) -> Int:
    return x + 2

def main():
    print(always_inlined(1), never_inlined(1))
output 复制代码
2 3

@inline 必须带一个参数(@inline 光秃秃会报 error: '@inline' decorator takes exactly 1 argument, found 0)。四个级别:

级别 含义
.automatic 交给优化器决定(默认)
.always 强制内联(递归函数会被拒)
.never 禁止内联(便于 profiling 时看清边界)
.nodebug 内联但不带调试信息

@implicit:隐式转换

mojo 复制代码
struct MyInt:
    var v: Int

    @implicit
    def __init__(out self, v: Int):
        self.v = v

def show(m: MyInt):
    print("MyInt:", m.v)

def main():
    show(7)          # Int → MyInt 隐式转换
    show(MyInt(8))   # 显式
output 复制代码
MyInt: 7
MyInt: 8

@implicit 要慎用:它是重载歧义的主要来源(7.6 的例子)。

@staticmethod 与编译期常量

mojo 复制代码
struct MathUtils:
    comptime pi: Float64 = 3.141592653589793

    @staticmethod
    def square(x: Int) -> Int:
        return x * x

def main():
    print(MathUtils.square(5))
    print(MathUtils.pi)
output 复制代码
25
3.141592653589793

@deprecated

mojo 复制代码
@deprecated("use new_f() instead")
def old_f() -> Int:
    return 1

def main():
    print(old_f())
output 复制代码
warning: use new_f() instead
note: 'old_f' declared here
1

在 --Werror 下这会变成错误------迁移 API 时很好用。

@fieldwise_init 与 @align

mojo 复制代码
from std.sys import size_of

@align(64)
@fieldwise_init
struct CacheLine:
    var data: SIMD[DType.float32, 16]

def main():
    var line = CacheLine(SIMD[DType.float32, 16](1.0))
    print("size:", size_of[CacheLine](), "first:", line.data[0])
output 复制代码
size: 64 first: 1.0

@fieldwise_init 自动生成"按字段顺序初始化"的初始化器(不用手写 __init__);@align(64) 把结构体对齐到 64 字节(缓存行对齐,第 20 章性能调优会用到)。装饰器自下而上应用,所以上面先应用 @fieldwise_init 再应用 @align。

import 语句可以写在函数体内(如上例的 from std.sys import size_of),作用域是该函数。文件顶层的 import 对全文件可见,与位置无关。


7.15 实战:手写一套高阶函数库

把本章所有构件用起来,实现 map / filter / reduce / sort_by 四件套。标准库有 std.algorithm(第 21 章),但自己写一遍才能理解那些 trait 约束为什么必要。

mojo 复制代码
# map:对每个元素应用 f,产出新列表
def my_map[
    T: ImplicitlyCopyable & Deinitable,
    U: ImplicitlyCopyable & Deinitable,
    F: def(T) -> U,
    //,
](f: F, xs: List[T]) -> List[U]:
    var out: List[U] = []
    for i in range(len(xs)):
        out.append(f(xs[i]))
    return out^


# filter:保留谓词为真的元素
def my_filter[
    T: ImplicitlyCopyable & Deinitable,
    F: def(T) -> Bool,
    //,
](pred: F, xs: List[T]) -> List[T]:
    var out: List[T] = []
    for i in range(len(xs)):
        if pred(xs[i]):
            out.append(xs[i])
    return out^


# reduce:从左到右折叠
def my_reduce[
    T: ImplicitlyCopyable & Deinitable,
    U: ImplicitlyCopyable & Deinitable,
    F: def(U, T) -> U,
    //,
](f: F, init: U, xs: List[T]) -> U:
    var acc = init
    for i in range(len(xs)):
        acc = f(acc, xs[i])
    return acc


# sort_by:按比较器就地排序(冒泡,仅为演示;生产用 std 的 sort)
def sort_by[
    T: ImplicitlyCopyable & Deinitable,
    F: def(T, T) -> Bool,
    //,
](less: F, mut xs: List[T]):
    for end in reversed(range(len(xs))):
        for i in range(end):
            if less(xs[i + 1], xs[i]):
                xs[i], xs[i + 1] = xs[i + 1], xs[i]


def main() raises:
    var xs: List[Int] = [5, 3, 9, 1, 7]
    print("map     :", my_map(lambda (x: Int) -> Int: x * 2, xs))
    print("filter  :", my_filter(lambda (x: Int) -> Bool: x > 4, xs))
    print("reduce  :", my_reduce(lambda (a: Int, b: Int) -> Int: a + b, 0, xs))

    sort_by(lambda (a: Int, b: Int) -> Bool: a < b, xs)
    print("asc     :", xs)
    sort_by(lambda (a: Int, b: Int) -> Bool: a > b, xs)
    print("desc    :", xs)

    var words: List[String] = ["mojo", "ai", "systems"]
    print("lengths :", my_map(lambda (w: String) -> Int: w.count_codepoints(), words))
    print("joined  :", my_reduce(
        lambda (a: String, b: String) -> String: a + "," + b, String(""), words
    ))
output 复制代码
map     : [10, 6, 18, 2, 14]
filter  : [5, 9, 7]
reduce  : 25
asc     : [1, 3, 5, 7, 9]
desc    : [9, 7, 5, 3, 1]
lengths : [4, 2, 7]
joined  : ,mojo,ai,systems

那些 trait 约束为什么是这样

写这个库时,约束不是随便加的。真实的编译器反馈:

你写的约束 编译器抱怨 原因
U: Deinitable error: invalid call to '__init__': ... needs evidence for 'conforms_to(U, Movable)' var out: List[U] = [] 要求元素可移动
T: Copyable error: value of type 'T' cannot be implicitly copied, it does not conform to 'ImplicitlyCopyable' Copyable 只保证"能显式 .copy()",不保证隐式拷贝
U: ImplicitlyCopyable error: 'acc' abandoned without being explicitly destroyed ... consider adding trait conformance to Deinitable 返回一个值要求它可析构

所以最终约束是 ImplicitlyCopyable & Deinitable。这正是 Mojo 泛型编程的日常:编译器不猜,你要把每个能力说清楚 (第 9 章讲 trait,第 18 章讲 conforms_to)。

为什么用下标而不是 for x in xs

for x in xs 要求 List 的元素 Copyable,而且拿到的是引用;for i in range(len(xs)) + xs[i] 更直白,也避免了在泛型上下文里推断元素引用的可变性。生产代码里两者都可以,选一致的即可。


7.16 锋利边缘清单

# 坑 结论
① lambda x: x+1 不支持;必须 lambda (x: Int) -> Int: x + 1
② var g: def(Int) -> Int = square 失败;函数指针要写 def(Int) thin -> Int
③ f: def(Int) -> Int 当实参类型 调用时转换失败;用 F: def(T)->U, // + f: F,或 Some[...]
④ 不捕获的 lambda 存进变量再传 变成 thin 类型,与闭包类型参数不兼容;内联传或写成命名 def
⑤ Some[def(Int) thin -> Int] 非法:Some[] 需要 trait,thin 函数类型不是 trait
⑥ List[def(Int) -> Int] 非法:List 的元素必须是 AnyType,闭包类型是 AnyTrait
⑦ mut 实参带默认值 非法:'mut' arguments must not have defaults
⑧ 按约定重载 非法:cannot overload on argument conventions
⑨ 只按返回类型/raises 重载 非法:视为重复定义
⑩ where 写在参数列表里 1.x 起非法,必须写在签名末尾
⑪ def main() -> Int 非法;退出码用 std.process.exit()
⑫ -> ref T 不写 origin 非法:'ref' result requires an origin specifier
⑬ List.__str__() 不存在(文档过时);直接 print(list)(Writable)
⑭ len(args) 于 variadic pack 不行;用 args.__len__()(编译期常量)
⑮ **d 字典解包 尚不支持
⑯ @inline 不带参数 报错,必须 @inline(.always) 之类
⑰ 非 Mojo abi() + raises 被拒绝
⑱ 泛型容器约束不足 缺 Movable/Deinitable/ImplicitlyCopyable 时报"lacking evidence"/"abandoned without being explicitly destroyed"

7.17 本章小结

  • Mojo 严格区分参数 ([...],编译期)与实参 ((...),运行时);这个区分贯穿全书。
  • 调用形式灵活:位置、关键字、混合;默认值必须排在必需实参之后(仅关键字实参除外);默认值可以是运行时表达式。
  • 三个标记:/(仅位置实参)、*(仅关键字实参)、//(仅推断参数)。高阶函数依赖 //。
  • 六种实参约定 决定"引用还是所有权、能不能改":默认不可变引用(零拷贝、寄存器传递小值)、mut 可变引用(改动外显、有排他性规则)、var 接管所有权(配合 ^ 转移)、out 命名结果/初始化位、deinit 接收将销毁的值、ref 参数化可变性。
  • 变长参数三形态:同质 *values: Int(VariadicList,可 len/下标/mut+for ref)、异质 pack [*Ts: Writable](*args: *Ts)(VariadicPack,args.__len__() + comptime for)、变长关键字 var **kwargs: Int(StringDict)。
  • 返回值:-> T、元组多值、命名结果 out (唯一能返回不可移动类型的方式);main() 不能有返回类型;ref 返回需要 origin(第 13 章)。
  • 重载决议按"最少隐式转换 → 无变长实参 → 无变长参数 → 最短参数签名 → 非静态方法"排序;不看 返回类型与 raises;不能按约定重载;歧义时用显式构造解决。
  • raises 让错误传播成为签名的一部分;非 raises 上下文调用 raises 函数必须 try/except 或自己也 raises;类型化错误就是普通 struct + Writable。
  • 高阶编程有四种正确写法 ,默认选 F: def(T) -> U, // + f: F;纯函数追求零开销用 //, f: def(T) thin -> U;不想写参数用 Some[def(T) -> U];只传命名函数可用 f: def(T) thin -> U。
  • lambda 必须写全括号与类型,体是单表达式,可带捕获列表 {mut x};推导式 [f(x) for x in xs if ...] 与 {k: v for ...} 可用。
  • 嵌套函数默认隔离外层作用域,捕获列表声明方式(imm/mut/var/var^/{});有捕获列表即闭包(第 11 章)。
  • 装饰器全是内建的:@inline(.always/.never/.automatic/.nodebug)、@implicit、@staticmethod、@export、@deprecated、@doc_hidden、@fieldwise_init、@align、@__parameter、@__copy_capture;自下而上应用。
  • 函数效果:raises、thin(只用于函数类型)、abi("C");thin 函数类型可带 where 约束(逆变)。

配套可运行代码:code/ch07-functions/functions.mojo、code/ch07-functions/higher_order.mojo。

7.18 动手练习

  1. 运行 code/ch07-functions/higher_order.mojo,然后加上 my_find(pred, xs) -> Optional[T](返回第一个满足谓词的元素)和 my_zip(xs, ys) -> List[Tuple[T, U]]。
  2. 写一个 def connect(host: String = "localhost", port: Int = 8080, *, timeout: Float64, retries: Int = 3),用四种不同方式调用它(全默认、只改 port、全关键字、混用),并解释哪些调用是非法的。
  3. 实现 def printf_like[*Ts: Writable](fmt: String, *args: *Ts):把 fmt 里的每个 {} 依次替换成对应实参(用 comptime for + args.__len__())。测试 printf_like("{} + {} = {}", 1, 2, 3)。
  4. 用命名结果实现一个"不可移动的句柄"类型 struct Handle(not Movable)(持有一个 Int 文件描述符),写 def open_handle(path: String, out h: Handle) raises,并解释为什么普通 -> Handle 版本无法编译。
  5. 复现重载歧义:写两个 struct 各带一个 @implicit 初始化器(都接受 String),再写两个同名函数分别接受它们,调用时传字符串字面量,观察编译器诊断;然后用显式构造消除歧义。
  6. 把 7.15 的 my_map 改成"写法 B"(thin 函数指针作为编译期参数)的版本 my_map_thin[T, U, //, f: def(T) -> U thin],比较两种写法在 -O3 --emit asm 下生成的汇编差异(提示:搜 call 指令数量)。
  7. 实测参数排他性:写 def append_twice(mut s: String, other: String),先传两个不同变量,再传同一个变量,记录编译器给出的完整诊断文本。然后改成拷贝一份再传,验证输出是 ooo。
  8. 写 def retry[T: Copyable & Deinitable, F: def() -> Optional[T], //](f: F, *, attempts: Int = 3) -> Optional[T]:最多调用 f 指定次数,第一次返回 .Some 就返回它。用捕获计数器的闭包测试它。
  9. 阅读题:解释为什么 transform(lambda (x: Int) -> Int: x * 2, xs) 能编译,而先 var f = lambda (x: Int) -> Int: x * 2 再 transform(f, xs) 不能。
  10. 用 @inline(.never) 和 @inline(.always) 分别标注一个递归 fib,观察编译器行为差异并解释。

相关推荐
Thanks_ks5 小时前
【第 003 讲】全面认识 Python:语言起源与设计哲学 | 行业地位与流行趋势 | 技术优势与应用领域 | 技术边界与工程局限
人工智能·python·数据分析·编程语言·python 入门·web 开发·python 之禅
2601_967980113 天前
Python 初学者必备的常用内置函数
python·编程语言·内置函数·初学者·常用函数
挖掘狂人4 天前
Vue nextTick 到底在等什么?一篇讲透原理与那些年踩过的坑
javascript·程序员·编程语言
AI前端工程狮5 天前
Agent 不会报错,只会变笨:给 MCP Server 补上契约测试和回放测试
人工智能·编程语言
codigger6 天前
前端没有被杀死,被杀死的是"只会把设计稿变成页面"
前端·ai·ai编程·编程语言·全栈·#人工智能
全栈弄潮儿7 天前
Python实战第13期:模块与包
后端·python·编程语言
codigger7 天前
别再手动催 AI 干活了:一文读懂 Loop Engineering(循环工程)
ai·程序员·编程语言·#人工智能
晓蛋8 天前
什么是C语言
c语言·跨平台·编程语言·高级语言·低级语言
计算机魔术师9 天前
做前端 UI 的人,接下来得先过一道开源技能的"设计质检"
编程语言