函数
Syntax
Function →
FunctionQualifiers fn IDENTIFIER GenericParams?
( FunctionParameters? )
FunctionReturnType? WhereClause?
( BlockExpression | ; )
FunctionQualifiers → const? async?1 ItemSafety?2 ( extern Abi? )?
ItemSafety → safe3 | unsafe
Abi → STRING_LITERAL | RAW_STRING_LITERAL
FunctionParameters →
SelfParam ,?
| ( SelfParam , )? FunctionParam ( , FunctionParam )* ,?
SelfParam → OuterAttribute* ( ShorthandSelf | TypedSelf )
ShorthandSelf → ( & | & Lifetime )? mut? self
FunctionParam → OuterAttribute* ( FunctionParamPattern | ... | Type4 )
FunctionParamPattern → PatternNoTopAlt : ( Type | ... )
FunctionReturnType → -> Type
一个 函数 (function) 由一个 块(即函数的 主体 (body) )、一个名称、一组参数以及一个输出类型组成。除了名称以外,这些都是可选的。
函数使用关键字 fn 声明,它在所在的模块或块的 值命名空间 中定义给定的名称。
函数可以通过参数声明一组 输入 变量,调用者通过这些参数将实参传递给函数;函数还可以声明在完成后返回给调用者的值的 输出 类型。
如果未显式说明输出类型,则默认为 单元类型。
当被引用时,一个 函数 会产生一个对应的零大小的 函数项类型 的一等 值 ,当它被调用时,会执行对该函数的直接调用。
例如,这是一个简单的函数:
#![allow(unused)]
fn main() {
fn answer_to_life_the_universe_and_everything() -> i32 {
return 42;
}
}
safe 函数在语义上仅允许在 extern 块 中使用。
函数参数
函数参数是不可反驳的 模式,因此任何在没有 else 的 let 绑定中有效的模式作为参数也同样有效:
#![allow(unused)]
fn main() {
fn first((value, _): (i32, i32)) -> i32 { value }
}
如果第一个参数是一个 SelfParam,这表明该函数是一个 方法。
带有 self 参数的函数只能作为 特型 或 实现 中的 关联函数 出现。
带有 ... 词法单元 的参数表示 变参函数,且只能用作 外部块 函数的最后一个参数。变参参数可以有一个可选的标识符,例如 args: ...。
函数主体
函数的主体块在概念上被包装在另一个块中,该块首先绑定参数模式,然后 return 函数主体的值。这意味着如果块的尾随表达式被求值,它最终会返回给调用者。像往常一样,函数主体内显式的返回表达式如果被执行,将短路该隐式返回。
例如,上述函数的行为就像它被写成:
// argument_0 是从调用者传递的实际第一个参数
let (value, _) = argument_0;
return {
value
};
没有主体块的函数以分号结尾。这种形式只能出现在 特型 或 外部块 中。
泛型函数
一个 泛型函数 允许在其签名中出现一个或多个 参数化类型 。每个类型参数必须在函数名之后、由尖括号包围且以逗号分隔的列表中显式声明。
#![allow(unused)]
fn main() {
// foo 对 A 和 B 是泛型的
fn foo<A, B>(x: A, y: B) {
}
}
在函数签名和主体内部,类型参数的名称可以用作类型名称。
可以为类型参数指定 特型 界限,以允许在该类型的值上调用具有该 特型 的方法。这是使用 where 语法指定的:
#![allow(unused)]
fn main() {
use std::fmt::Debug;
fn foo<T>(x: T) where T: Debug {
}
}
当引用泛型函数时,其类型会根据引用的上下文进行实例化。例如,在这里调用 foo 函数:
#![allow(unused)]
fn main() {
use std::fmt::Debug;
fn foo<T>(x: &[T]) where T: Debug {
// 细节已省略
}
foo(&[1, 2]);
}
将使用 i32 实例化类型参数 T。
类型参数也可以在函数名之后的尾随 路径 组件中显式提供。如果没有足够的上下文来确定类型参数,这可能是必要的。例如,mem::size_of::<u32>() == 4。
Extern函数限定符
extern 函数限定符允许提供可以使用特定 ABI 调用的函数 定义 :
extern "ABI" fn foo() { /* ... */ }
这些通常与 外部块 项结合使用,后者提供函数 声明 ,可用于调用函数而无需提供其 定义 :
unsafe extern "ABI" {
unsafe fn foo(); /* 没有主体 */
safe fn bar(); /* 没有主体 */
}
unsafe { foo() };
bar();
当函数 项 的 FunctionQualifiers 中省略了 "extern" Abi?* 时,将分配 ABI "Rust"。例如:
#![allow(unused)]
fn main() {
fn foo() {}
}
等同于:
#![allow(unused)]
fn main() {
extern "Rust" fn foo() {}
}
函数可以被外部代码调用,并且使用与 Rust 不同的 ABI 允许提供可以从其他编程语言(如 C)调用的函数:
#![allow(unused)]
fn main() {
// 声明一个具有 "C" ABI 的函数
extern "C" fn new_i32() -> i32 { 0 }
// 声明一个具有 "stdcall" ABI 的函数
#[cfg(any(windows, target_arch = "x86"))]
extern "stdcall" fn new_i32_stdcall() -> i32 { 0 }
}
就像 外部块 一样,当使用 extern 关键字并省略 "ABI" 时,使用的 ABI 默认为 "C"。也就是说,这段代码:
#![allow(unused)]
fn main() {
extern fn new_i32() -> i32 { 0 }
let fptr: extern fn() -> i32 = new_i32;
}
等同于:
#![allow(unused)]
fn main() {
extern "C" fn new_i32() -> i32 { 0 }
let fptr: extern "C" fn() -> i32 = new_i32;
}
展开
大多数 ABI 字符串有两种变体,一种带有 -unwind 后缀,另一种没有。Rust ABI 总是允许展开,因此没有 Rust-unwind ABI。ABI 的选择与运行时的 恐慌处理器 一起决定了从函数中展开时的行为。
下表说明了展开操作到达每种类型的 ABI 边界(使用相应 ABI 字符串的函数声明或定义)时的行为。请注意,Rust 运行时不受完全发生在另一种语言运行时内部的任何展开的影响,也无法对其产生影响,即在未到达 Rust ABI 边界的情况下抛出和捕获的展开。
panic-unwind 列是指通过 panic! 宏和类似的标准库机制进行的 恐慌,以及任何其他导致恐慌的 Rust 操作,例如数组索引越界或整数溢出。
“展开” ABI 类别是指 "Rust"(未标记为 extern 的 Rust 函数的隐式 ABI)、"C-unwind" 以及任何名称中带有 -unwind 的 ABI。“非展开” ABI 类别是指所有其他 ABI 字符串,包括 "C" 和 "stdcall"。
原生展开是针对每个目标定义的。在支持抛出和捕获 C++ 异常的目标上,它指的是用于实现此功能的机制。某些平台实现了一种被称为 “强制展开” 的展开形式;Windows 上的 longjmp 和 glibc 中的 pthread_exit 就是这样实现的。强制展开被明确排除在表中的“原生展开”列之外。
| 恐慌运行时 | ABI | panic-unwind | 原生展开 (非强制) |
|---|---|---|---|
panic=unwind | 展开型 | 展开 | 展开 |
panic=unwind | 非展开型 | 中止 (见下文注释) | 未定义行为 |
panic=abort | 展开型 | panic 在不展开的情况下中止 | 中止 |
panic=abort | 非展开型 | panic 在不展开的情况下中止 | 未定义行为 |
在 panic=unwind 的情况下,当 恐慌 被非展开 ABI 边界转变为中止时,要么不运行任何析构函数 (Drop 调用),要么运行直到 ABI 边界为止的所有析构函数。具体发生这两种行为中的哪一种是未指定的。
有关跨 FFI 边界展开的其他注意事项和限制,请参阅 恐慌文档中的相关部分。
Const函数
有关 const 函数的定义,请参阅 const 函数。
Async函数
函数可以被限定为 async,这也可以与 unsafe 限定符结合使用:
#![allow(unused)]
fn main() {
async fn regular_example() { }
async unsafe fn unsafe_example() { }
}
Async 函数在调用时不执行任何操作:相反,它们将参数捕获到一个 future 中。当被轮询时,该 future 将执行函数的主体。
一个 async 函数大致相当于一个返回 impl Future 并且以 async move 块 作为其主体的函数:
#![allow(unused)]
fn main() {
// 源码
async fn example(x: &str) -> usize {
x.len()
}
}
大致相当于:
#![allow(unused)]
fn main() {
use std::future::Future;
// 脱糖后
fn example<'a>(x: &'a str) -> impl Future<Output = usize> + 'a {
async move { x.len() }
}
}
实际的脱糖过程更为复杂:
- 脱糖中的返回类型被假定为捕获了
async fn声明中的所有生命周期参数。这可以在上面的脱糖示例中看到,它显式地超过了'a的生命周期,因此捕获了'a。
- 主体中的
async move块 捕获了所有函数参数,包括那些未使用或绑定到_模式的参数。这确保了函数参数的销毁顺序与函数非 async 时相同,不同之处在于销毁发生在返回的 future 被完全 await 之后。
2018 版次差异
Async 函数仅从 Rust 2018 开始可用。
结合async和unsafe
声明一个既是 async 又是 unsafe 的函数是合法的。生成的函数调用时是不安全的,并且(像任何 async 函数一样)返回一个 future。这个 future 只是一个普通的 future,因此 “await” 它不需要 unsafe 上下文:
#![allow(unused)]
fn main() {
// 返回一个在 await 时解引用 `x` 的 future。
//
// 健全性条件:在生成的 future 完成之前,`x` 必须可以安全地解引用。
async unsafe fn unsafe_example(x: *const i32) -> i32 {
*x
}
async fn safe_example() {
// 最初调用该函数需要 `unsafe` 块:
let p = 22;
let future = unsafe { unsafe_example(&p) };
// 但这里不需要 `unsafe` 块。这将
// 读取 `p` 的值:
let q = future.await;
}
}
请注意,这种行为是脱糖为返回 impl Future 的函数的结果 —— 在这种情况下,我们脱糖到的函数是一个 unsafe 函数,但返回值保持不变。
Unsafe 在 async 函数上的使用方式与在其他函数上的使用方式完全相同:它表明该函数对调用者施加了一些额外的义务以确保健全性。与任何其他 unsafe 函数一样,这些条件可能会延伸到初始调用本身之外 —— 例如,在上面的代码片段中,unsafe_example 函数接受一个指针 x 作为参数,然后 (在被 await 时) 解引用该指针。这意味着 x 必须在 future 完成执行之前保持有效,而确保这一点是调用者的责任。
函数上的属性
函数允许使用 外部属性。内部属性 允许直接在函数主体 块 内部的 { 之后使用。
这个例子展示了函数上的内部属性。该函数仅使用单词 “Example” 进行文档说明。
#![allow(unused)]
fn main() {
fn documented() {
#![doc = "Example"]
}
}
注意
除了 lint 之外,在函数 项 上仅使用外部属性是符合习惯的。
对函数有意义的属性有:
cfg_attrcfgcolddeprecateddocexport_nameinlinelink_sectionmust_useno_mangle- Lint 检查属性
- 过程宏属性
- 测试属性
函数参数上的属性
函数参数允许使用 外部属性,允许的 内置属性 限于 cfg、cfg_attr、allow、warn、deny 和 forbid。
#![allow(unused)]
fn main() {
fn len(
#[cfg(windows)] slice: &[u16],
#[cfg(not(windows))] slice: &[u8],
) -> usize {
slice.len()
}
}
应用于 项 的 过程宏 属性所使用的惰性辅助属性也是允许的,但要注意不要在最终的 TokenStream 中包含这些惰性属性。
例如,以下代码定义了一个在任何地方都没有正式定义的惰性 some_inert_attribute 属性,而 some_proc_macro_attribute 过程宏 负责检测它的存在并将其从输出词法单元流中移除。
#[some_proc_macro_attribute]
fn foo_oof(#[some_inert_attribute] arg: u8) {
}