函数
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
函数由一个块(即函数的主体)以及一个名称、一组参数和输出类型组成。除名称外,所有这些都是可选的。
函数使用 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 参数的函数只能作为 trait 或实现中的关联函数出现。
带有 ... 标记的参数表示可变参数函数,并且只能用作外部块函数的最后一个参数。可变参数可以有可选的标识符,例如 args: ...。
函数体
函数的主体块在概念上被包裹在另一个块中,该块首先绑定参数模式,然后 return 函数主体的值。这意味着块的尾部表达式(如果被求值)最终会被返回给调用者。像往常一样,函数体内的显式 return 表达式会短路该隐式返回(如果被执行到的话)。
例如,上述函数的行为就像是这样写的:
// argument_0 是调用者实际传入的第一个参数
let (value, _) = argument_0;
return {
value
};
没有主体块的函数以分号结尾。这种形式只能出现在 trait 或外部块中。
泛型函数
泛型函数允许一个或多个参数化类型出现在其签名中。每个类型参数必须在函数名之后、用尖括号括起来的逗号分隔列表中显式声明。
#![allow(unused)]
fn main() {
// foo 对 A 和 B 是泛型的
fn foo<A, B>(x: A, y: B) {
}
}
在函数签名和函数体内,类型参数的名称可以用作类型名。
可以为类型参数指定 trait 约束,以允许调用该类型的值上的方法。这通过 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();
当 "extern" Abi?* 在函数项中从 FunctionQualifiers 中省略时,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 的选择与运行时的 panic 处理器一起决定了展开出函数时的行为。
下表指示了展开操作到达每种 ABI 边界(使用相应 ABI 字符串的函数声明或定义)时的行为。请注意,Rust 运行时不受且无法影响完全发生在其他语言运行时内部的任何展开操作的影响,即那些在不触及 Rust ABI 边界的情况下抛出和捕获的展开。
panic-展开列指的是通过 panic! 宏及类似的标准库机制进行panic,以及任何其他导致 panic 的 Rust 操作,例如数组越界索引或整数溢出。
“unwinding” ABI 类别指的是 "Rust"(未标记 extern 的 Rust 函数的隐式 ABI)、"C-unwind" 以及任何其他名称中带有 -unwind 的 ABI。“non-unwinding” ABI 类别指的是所有其他 ABI 字符串,包括 "C" 和 "stdcall"。
原生展开按目标平台定义。在支持抛出和捕获 C++ 异常的目标平台上,它指的是用于实现此功能的机制。某些平台实现了一种称为“强制展开”的展开形式;Windows 上的 longjmp 和 glibc 中的 pthread_exit 以这种方式实现。强制展开被明确排除在下表的“原生展开“列之外。
| panic 运行时 | ABI | panic-展开 | 原生展开(非强制) |
|---|---|---|---|
panic=unwind | unwinding | 展开 | 展开 |
panic=unwind | non-unwinding | 中止(见下文注释) | 未定义行为 |
panic=abort | unwinding | panic 中止而不展开 | 中止 |
panic=abort | non-unwinding | panic 中止而不展开 | 未定义行为 |
当 panic=unwind 时,如果 panic 因非展开 ABI 边界而转为中止,则要么没有析构函数(Drop 调用)会运行,要么直到 ABI 边界的所有析构函数都会运行。这两种行为中具体发生哪一种未指定。
有关跨 FFI 边界展开的其他考虑和限制,请参阅 panic 文档中的相关章节。
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。
- 主体中的
async move块 捕获所有函数参数,包括那些未使用或绑定到_模式的参数。这确保了函数参数的释放顺序与函数不是 async 时相同,只是释放发生在返回的 future 被完全 await 之后。
关于 async 效果的更多信息,请参见 async 块。
2018 Edition differences
Async 函数仅从 Rust 2018 开始可用。
结合 async 和 unsafe
声明一个既是 async 又是 unsafe 的函数是合法的。生成的函数调用时是不安全的,并且(像任何 async 函数一样)返回一个 future。这个 future 只是一个普通的 future,因此不需要 unsafe 上下文来“await“它:
#![allow(unused)]
fn main() {
// 返回一个 future,当被 await 时会解引用 `x`。
//
// 安全性条件:`x` 必须安全可解引用,直到
// 返回的 future 完成。
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"]
}
}
Note
除了 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 过程宏负责检测其存在并将其从输出 token 流中移除。
#[some_proc_macro_attribute]
fn foo_oof(#[some_inert_attribute] arg: u8) {
}