第 7 章 函数、参数约定与高阶编程
本章目标:把 Mojo 的函数体系一次讲透——实参约定(argument conventions)、六个标记(
/、*、//)、变长参数、命名结果、重载决议、raises效果,以及最容易被低估的部分:高阶编程(把函数当值传递的四种正确写法、lambda、推导式)。参数约定是 Mojo 与 Python/C++ 差异最大、也最能体现"系统编程语言"底色的地方。第 12 章会从所有权角度再讲一遍,本章从"怎么写函数"的角度讲。所有代码在 Mojo 1.2.0 实测。
7.1 函数剖析
一个 Mojo 函数声明可以包含以下部分:
def 函数名[参数(编译期)...](实参(运行时)...) 效果 -> 返回类型:
函数体
- 参数(parameters):方括号里的编译期值,用于元编程(第 17 章)。
- 实参(arguments):圆括号里的运行时值,就是其他语言说的"函数参数"。
- 效果(effects):
raises、abi("C")等,写在右括号之后、->之前。 - 返回值:
-> 类型,可省略(等价于-> None)。 - 函数体:必须有,什么都不做就写
pass。
最小函数:
def do_nothing():
pass
def main():
do_nothing()
print("ok")
ok
术语提醒:Mojo 官方文档里 "parameter" 专指编译期参数,"argument" 专指运行时实参。这与 Python/C++ 里两者混用不同。本书沿用官方术语:参数 =
[...],实参 =(...)。混淆这两个词是读 Mojo 文档时最大的障碍。
函数的硬性要求:
- 每个实参都必须声明类型(没有动态类型)。
- 不返回值时可以省略
-> ...,或写-> None(两者等价)。 - 返回值必须写
-> 类型,或者用命名结果(out实参,见 7.7)。 - 函数体不能省略,
pass也算体。
函数可以在定义之前被调用(不需要前向声明):
def main():
print(later(2))
def later(x: Int) -> Int:
return x * 10
20
7.2 调用形式:位置、关键字、混合
默认情况下,实参既可以按位置传,也可以按名字传,还能混用:
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)) # 混合
7
9
7
默认值与顺序规则
带默认值的实参是"可选实参":
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
9
8
1024
可选实参必须排在必需实参之后,否则:
def wrong(x: Int, y: Int = 0, z: Int) -> Int:
return x + y + z
def main():
print(wrong(1, 2, 3))
error: required positional argument follows optional positional argument; change the ordering
默认值可以是运行时表达式(每次调用时求值),不限于字面量:
def now_ms() -> Int:
return 42
def f(x: Int = now_ms()) -> Int:
return x
def main():
print(f(), f(7))
42 7
7.3 三个标记:/、*、//
Mojo 用三个"标记"把参数/实参列表切成不同区域。它们必须按 // → / → * 的顺序出现,各最多一次;/ 不能是列表第一个,* 不能是最后一个。
| 标记 | 作用于实参 | 作用于参数 | 含义 |
|---|---|---|---|
// | 不支持 | 仅推断 | 标记之前的参数只能由编译器从实参推断,调用方不能按位置指定 |
/ | 仅位置 | 仅位置 | 之前的实参只能按位置传 |
* | 仅关键字 | 仅关键字 | 之后的实参只能按名字传 |
/:仅位置实参
def div(a: Int, b: Int, /) -> Int:
return a // b
def main():
print(div(10, 3))
3
用关键字调用会被拒:
def div(a: Int, b: Int, /) -> Int:
return a // b
def main():
print(div(a=10, b=3))
error: invalid call to 'div': missing required argument: 'a'
什么时候用 /?① 实参名对调用者没有意义(min(a, b));② 你想保留将来改名的自由而不破坏兼容。
*:仅关键字实参
def configure(*, verbose: Bool, retries: Int = 3):
print("verbose:", verbose, "retries:", retries)
def main():
configure(verbose=True, retries=5)
configure(verbose=False)
verbose: True retries: 5
verbose: False retries: 3
按位置传会报错:
def configure(*, verbose: Bool):
print(verbose)
def main():
configure(True)
error: invalid call to 'configure': missing required argument: 'verbose'
仅关键字实参不受"可选必须在必需之后"的顺序限制,可以自由混排必需与可选:
def configure2(*, retries: Int = 3, verbose: Bool):
print(verbose, retries)
def main():
configure2(verbose=True)
True 3
//:仅推断参数
// 只作用于编译期参数:它之前的参数只能由编译器从实参推断出来,调用方不能按位置显式指定。
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")
Value is 5. Type is SIMD[DType.int, 1].
Value is Hello. Type is String.
想显式指定 T,按位置传是错的:
from std.format import Writable
def f[T: Writable, //](value: T):
print(value)
def main():
f[Int](5)
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, ???](...)。
实参顺序的完整规则
一个函数签名里,各类实参的出现顺序必须是:
- 必需的位置实参
- 可选的位置实参
- 变长实参
*args - 必需的仅关键字实参
- 可选的仅关键字实参
- 变长关键字实参
**kwargs
7.4 六种实参约定(argument conventions)
这是 Mojo 函数体系的核心。约定决定"函数拿到的是引用还是所有权,能不能改",写在实参名前面:
| 约定 | 函数拿到什么 | 能改吗 | 改动对调用者可见吗 | 调用方需要做什么 |
|---|---|---|---|---|
| 默认(无关键字) | 不可变引用 | 否 | — | 无 |
mut | 可变引用 | 是 | 是 | 传入的值本身必须是可变的 |
var | 所有权(一个值) | 是 | 否(函数拥有它) | 可选加 ^ 转移所有权 |
out | 未初始化的输出位 | 必须初始化 | 是(作为返回值) | 不传(用于命名结果/__init__ 的 self) |
deinit | 一个即将被销毁的值 | 是(可搬空字段) | — | 通常配合 ^ |
ref | 参数化可变性的引用 | 跟随被引用值 | 是 | 高级用法,第 13 章 |
默认:不可变引用
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)
[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:可变引用
def mutate(mut l: List[Int]):
l.append(5)
def main():
var values: List[Int] = [1, 2, 3, 4]
mutate(values)
print(values)
[1, 2, 3, 4, 5]
记忆法:mut 意味着"函数内的改动,函数外看得见"。
等价的"拷贝—修改—返回"写法(更耗内存):
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)
[1, 2, 3, 4, 5]
两条限制:
- 传给
mut的实参本身必须可变。把不可变引用再当mut传下去会报错——Mojo 不能从不可变引用造出可变引用。 mut实参不能有默认值:
def f(mut x: Int = 3):
x += 1
def main():
f()
error: 'mut' arguments must not have defaults
参数排他性(argument exclusivity)
如果函数拿到了某个值的可变引用,它就不能同时拿到同一个值的任何其它引用(可变或不可变)。这条规则既防 bug 又利于优化:
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)
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"。
需要同时传同一个值时,先拷贝一份:
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)
ooo
排他性对寄存器传递的平凡类型(
Int、Bool、小SIMD)不强制——它们总是按值拷贝,传两次就是两份副本。
var:接管所有权
var 表示"函数拥有这个值"。注意它不保证函数拿到的是"原值",只保证拿到"某个值的所有权",来源有三种:
- 转移:调用方写
^,原变量随之失效; - 拷贝:调用方不写
^,且类型可拷贝 → 编译器拷一份; - 新建值:调用方传的是函数调用结果或字面量 → 直接交给被调方。
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")
inside: Hello!
outside: Hello
inside: Hello!
transferred
转移之后原变量就"未初始化"了,再用会报错:
def take_text(var text: String):
print(text)
def main():
var message = String("Hello")
take_text(message^)
print(message)
error: use of uninitialized value 'message'
函数拥有值之后,值在函数退出时销毁——除非再转移出去:
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)
consuming: Bye
[Ada]
out:命名结果与初始化位
out 实参在函数开始时未初始化,函数返回前必须初始化。调用方不传这个实参——它是返回值。最常见的用途是 __init__(out self, ...),其次是命名结果(7.7)。
deinit:接收一个将被销毁的值
deinit 实参在函数开始时已初始化,函数结束时被视为已销毁,因此可以安全地把字段从中搬走:
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^)
took: m
stolen: n
deinit 不只用于 self——任何实参都可以标 deinit,用来写"消费型"API(比如对象池回收、资源移交)。
约定选择速查
| 你想做什么 | 用哪个 |
|---|---|
| 只读一个大对象 | 默认(不可变引用) |
| 就地修改调用者的值 | mut |
| 拿走并消耗一个值(如存入容器) | var + 调用方 ^ |
| 返回一个不可移动/不可拷贝的值 | out(命名结果) |
| 拆解一个即将销毁的值 | deinit |
| 写泛型容器、可变性跟随调用方 | ref(第 13 章) |
7.5 变长参数
同质变长实参 *name: T
所有实参类型相同:
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))
0
10
函数体里 values 是一个 VariadicList(可迭代、可 len()、可下标):
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)
type: std.builtin.variadics.VariadicList[False, {}, {}, SIMD[DType.int, 1], False]
len: 3
values[0]: 10 values[2]: 30
加 mut 就能改调用方的值(配合 for ref):
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)
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。
也可以用下标改:
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)
x!
异质变长实参(variadic pack)*args: *Ts
每个实参类型可以不同。这需要变长类型参数:* 同时出现在名字和类型标注前。
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()
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__()。
统计任意个可转整数的值:
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))
28
11.7 被 Int() 截断成 11,所以 5 + 11 + 12 = 28(第 5 章讲过浮点转整数向零截断)。
变长实参的限制:
def wrong(*args: Int = 0):
pass
def main():
wrong()
error: variadic arguments must not have defaults
另外:一个函数最多一个 *args;out 实参不能是变长的(def wrong(out *results: Int) 非法)。
变长关键字实参 var **kwargs: T
def print_nicely(var **kwargs: Int):
for item in kwargs.items():
print(" ", item.key, "=", item.value)
def main():
print_nicely(a=7, y=8)
a = 7
y = 8
函数体里 kwargs 是一个 StringDict[T](键是 String 的字典)。当前限制:
- 必须声明为
var **kwargs(函数拥有这个字典),其它约定不支持; - 所有 kwargs 必须同类型,该类型必须
Copyable; - 不支持字典解包:
print_nicely(**some_dict)还不行; - 不支持变长关键字参数:
def f[**kwparams: Int]()还不行。
*args 之后的实参自动变成仅关键字
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"))
total: 6
sum_named(1, 2, 3, "subtotal") 会报 missing required keyword argument。
7.6 重载与重载决议
同名不同签名的函数构成一个重载集(overload set):
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"))
3
xy
literal
add("lit", "eral") 能匹配 add(x: String, y: String),是因为 String 有一个 @implicit 初始化器,允许 StringLiteral 隐式转换。
决议依据与优先级
编译器挑选候选时看:实参的个数、位置、关键字;每个实参与参数的类型;实参约定;是否静态方法;以及是否需要隐式转换。
筛选优先级(从高到低):
- 需要最少隐式转换的候选(实参与参数都算);
- 没有变长实参的候选;
- 没有变长参数的候选;
- 参数签名最短的候选;
- 非
@staticmethod候选优先于@staticmethod。
编译器不看返回类型、raises 效果、调用上下文。因此:
def f(x: Int) -> Int:
return x
def f(mut x: Int) -> Int:
return x + 1
def main():
print(f(1))
error: redefinition of 'f', cannot overload on argument conventions
只有返回类型不同、或只有 raises 不同的两个函数是重复定义,编译器直接拒绝第二个。
歧义与显式转换
当两个候选都需要一次隐式转换时,调用是歧义的:
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")
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 返回值
普通返回
def get_greeting() -> String:
return "Hello"
def main():
print(get_greeting())
Hello
返回值默认是被调用方拥有的值;return 的字面量会按声明的返回类型隐式转换(上例把 StringLiteral 转成 String)。
多值返回:用元组
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)
min: 1 max: 9
返回类型必须写成 Tuple[A, B];调用方可以直接解构(var lo, hi = ...)。
命名结果(named results):out 实参
命名结果让函数能返回不可移动、不可拷贝的值。语法是把返回值声明成一个 out 实参:
def incr(a: Int, out b: Int):
b = a + 1
def main():
var r = incr(41)
print(r)
42
对调用方来说,def incr(a: Int) -> Int 和 def incr(a: Int, out b: Int) 完全等价——命名结果是实现细节。约定上 out 实参放最后,但语法上可以放任何位置:
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)
Bob!
带命名结果的函数不需要显式 return:函数正常结束(或 return 不带值)时,out 实参的值就是返回值;如果写了 return 值,则返回那个值。
为什么需要它:普通 return 要求值能被移动或拷贝到调用方。对于 not Movable 的类型,这条路走不通:
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)
error: cannot transfer value into destination, because 'Immovable' doesn't conform to 'Movable'
用命名结果就行——对象直接在调用方的存储里构造,从不移动:
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)
Blob!
如果值立即返回(不经过局部变量),普通 -> T 也可以,因为没有中间存储需要移动:
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)
Direct
main() 不能有返回类型
def main() -> Int:
return 3
error: 'main()' does not return a value; remove the return type
想返回进程退出码,用 std.process.exit()(第 3、21 章)。
返回引用
函数可以返回 ref 引用(-> ref[origin] T),但必须标注 origin,这是第 13 章的主题:
def first(ref xs: List[Int]) -> ref Int:
return xs[0]
def main():
var xs: List[Int] = [1, 2, 3]
print(first(xs))
error: 'ref' result requires an origin specifier
7.8 泛型函数入门
方括号里声明参数,用 trait 约束类型(第 17、18 章系统化,这里给出日常够用的部分):
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"))
-1
0
1
where 约束写在末尾
where 子句出现在声明最后(返回类型之后;没有返回类型就在实参列表之后):
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))
sum: 10.0
where 写在参数列表里面是非法的(1.x 起):
def wrong[n: Int where n > 0]():
pass
def main():
wrong[1]()
error: 'where' clauses inside parameter lists are no longer supported
note: use a trailing 'where' clause after the signature instead
where 里可以带自定义错误消息,让约束失败时更好读:
def process[n: Int](data: SIMD[.float32, n]) -> Float32 where (
n > 0, "width must be positive"
):
...
参数推断
有足够上下文时,编译器能推断出参数,不必显式写:
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
42
42
3
Int(1.5) 向零截断成 1,所以最后一个输出 3(第 5 章的锋利边缘)。
7.9 raises:可失败函数
Mojo 函数默认不抛错。要传播错误,加 raises:
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()
Handled an error: There was an error.
非 raises 函数调用 raises 函数必须处理或声明传播:
def boom() raises:
raise Error("bad")
def main():
boom()
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 往上抛:
def boom() raises:
raise Error("bad")
def propagate() raises:
boom() # 直接传播
def main() raises:
try:
propagate()
except e:
print("caught at top:", e)
caught at top: bad
try 支持 except / else:
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")
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(瘦函数指针),它是编译期可确定的纯代码地址:
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))
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 的闭包类型变量会失败:
def square(x: Int) -> Int:
return x * x
def main():
var g: def(Int) -> Int = square
print(g(6))
error: cannot implicitly convert 'def square(x: Int) thin -> Int' value to 'def(Int) -> Int'
thin → closure 不做隐式转换。要存函数指针就写 thin;要接受"任意可调用对象"就用 7.11 的四种写法。
C 调用约定
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))
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 约束,约束成为类型的一部分:
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 跟在函数类型返回值后面,需要用括号区分:
# 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)通常行不通——先看失败案例:
def apply(f: def(Int) -> Int, x: Int) -> Int:
return f(x)
def main():
print(apply(lambda (x: Int) -> Int: x * 3, 4))
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:
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))
[20, 40, 60]
这种写法接受内联 lambda、捕获型 lambda 变量、以及普通命名函数:
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]))
[8, 64, 216, 512, 1000]
[4, 16, 36, 64, 100]
[5, 5, 5, 3, 4]
[101, 102, 103]
但有一个陷阱:把不捕获任何东西的 lambda 存进变量,它的类型会变成 thin 函数指针,与闭包类型的参数 F 不兼容:
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))
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 函数指针作为编译期参数
如果确定不需要捕获(纯函数),可以让函数指针本身成为参数,编译期完全内联,零调用开销:
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)
transformed: [2, 4, 6, 8, 10]
注意调用形式:lambda 写在方括号里(它是编译期参数)。传捕获环境的 lambda 会失败,因为闭包不是编译期常量:
var factor = 3
inplace_transform[lambda (x: Int) -> Int: x ** factor](numbers)
# error: 闭包不能用作编译期函数指针参数
写法 C:Some[def(...) -> ...] 存在类型
不想引入参数、也不想推断时,用 Some[...]:
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))
16
5
Some[Trait] 表示"某个符合该签名的具体类型",由编译器在调用点单态化。
Some[def(Int) thin -> Int]不合法:Some[]需要一个 trait,而 thin 函数类型不是 trait。error: parameter 'Trait' has 'AnyTrait[AnyType]' type, but value has type '__mlir_type.`!kgen.non_struct_type`'
写法 D:普通 thin 实参
只要传的是命名函数(thin),直接声明 thin 实参类型即可:
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))
[1, 4, 9]
传捕获型 lambda 会被拒:
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 表达式
语法:必须带括号和类型
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())
42
5
hi
Python 风格的 lambda x: x + 1 不被支持:
def main():
var inc = lambda x: x + 1
print(inc(1))
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 章详解):
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))}")
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 一致:
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)
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 嵌套函数
函数里可以定义函数。嵌套函数默认看不到外层变量,必须用捕获列表显式声明:
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())
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
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))
2 3
@inline 必须带一个参数(@inline 光秃秃会报 error: '@inline' decorator takes exactly 1 argument, found 0)。四个级别:
| 级别 | 含义 |
|---|---|
.automatic | 交给优化器决定(默认) |
.always | 强制内联(递归函数会被拒) |
.never | 禁止内联(便于 profiling 时看清边界) |
.nodebug | 内联但不带调试信息 |
@implicit:隐式转换
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)) # 显式
MyInt: 7
MyInt: 8
@implicit 要慎用:它是重载歧义的主要来源(7.6 的例子)。
@staticmethod 与编译期常量
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)
25
3.141592653589793
@deprecated
@deprecated("use new_f() instead")
def old_f() -> Int:
return 1
def main():
print(old_f())
warning: use new_f() instead
note: 'old_f' declared here
1
在 --Werror 下这会变成错误——迁移 API 时很好用。
@fieldwise_init 与 @align
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])
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 约束为什么必要。
# 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
))
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 动手练习
- 运行
code/ch07-functions/higher_order.mojo,然后加上my_find(pred, xs) -> Optional[T](返回第一个满足谓词的元素)和my_zip(xs, ys) -> List[Tuple[T, U]]。 - 写一个
def connect(host: String = "localhost", port: Int = 8080, *, timeout: Float64, retries: Int = 3),用四种不同方式调用它(全默认、只改 port、全关键字、混用),并解释哪些调用是非法的。 - 实现
def printf_like[*Ts: Writable](fmt: String, *args: *Ts):把fmt里的每个{}依次替换成对应实参(用comptime for+args.__len__())。测试printf_like("{} + {} = {}", 1, 2, 3)。 - 用命名结果实现一个"不可移动的句柄"类型
struct Handle(not Movable)(持有一个Int文件描述符),写def open_handle(path: String, out h: Handle) raises,并解释为什么普通-> Handle版本无法编译。 - 复现重载歧义:写两个 struct 各带一个
@implicit初始化器(都接受String),再写两个同名函数分别接受它们,调用时传字符串字面量,观察编译器诊断;然后用显式构造消除歧义。 - 把 7.15 的
my_map改成"写法 B"(thin 函数指针作为编译期参数)的版本my_map_thin[T, U, //, f: def(T) -> U thin],比较两种写法在-O3 --emit asm下生成的汇编差异(提示:搜call指令数量)。 - 实测参数排他性:写
def append_twice(mut s: String, other: String),先传两个不同变量,再传同一个变量,记录编译器给出的完整诊断文本。然后改成拷贝一份再传,验证输出是ooo。 - 写
def retry[T: Copyable & Deinitable, F: def() -> Optional[T], //](f: F, *, attempts: Int = 3) -> Optional[T]:最多调用f指定次数,第一次返回.Some就返回它。用捕获计数器的闭包测试它。 - 阅读题:解释为什么
transform(lambda (x: Int) -> Int: x * 2, xs)能编译,而先var f = lambda (x: Int) -> Int: x * 2再transform(f, xs)不能。 - 用
@inline(.never)和@inline(.always)分别标注一个递归fib,观察编译器行为差异并解释。