函数
语法
函数 :
函数限定符fn
IDENTIFIER 泛型参数?
(
函数参数?)
函数返回类型? Where从句?
( 块表达式 |;
)函数限定符 :
const
?async
1? 项安全性?2 (extern
Abi?)?项安全性 :
safe
3 |unsafe
函数参数 :
Self参数,
?
| (Self参数,
)? 函数参数 (,
函数参数)*,
?Self参数 :
外部属性* ( 简写Self | 带类型Self )简写Self :
(&
|&
生命周期)?mut
?self
带类型Self :
mut
?self
:
类型函数参数 :
外部属性* ( 函数参数模式 |...
| 类型 4 )函数参数模式 :
非顶层Alt模式:
( 类型 |...
)函数返回类型 :
->
类型1
async
限定符在 2015 版中不允许使用。3从语义上讲,
safe
函数仅允许在extern
块中使用。2适用于 Rust 2024 之前的版本:在
extern
块内,safe
或unsafe
函数限定符仅在extern
被限定为unsafe
时允许使用。4仅带有类型的函数参数仅允许在 2015 版的 trait 项的关联函数中使用。
一个函数由一个块(即函数的函数体)组成,以及一个名称、一组参数和一个输出类型。除了名称之外,所有这些都是可选的。
函数使用关键字 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 } }
如果第一个参数是 Self参数,则表示该函数是一个方法。
带有 self 参数的函数只能作为trait或实现中的关联函数出现。
带有 ...
标记的参数表示一个可变参数函数,并且只能用作外部块函数的最后一个参数。可变参数可以有一个可选的标识符,例如 args: ...
。
函数体
函数的函数体块在概念上被另一个块包裹,该块首先绑定实参模式,然后 return
函数体的值。这意味着块的尾表达式,如果被求值,最终会被返回给调用者。像往常一样,函数体内的显式 return 表达式如果执行到,将跳过隐式返回。
例如,上面的函数行为如同写成
// argument_0 is the actual first argument passed from the caller
let (value, _) = argument_0;
return {
value
};
没有函数体块的函数以分号结尾。这种形式只能出现在trait或外部块中。
泛型函数
一个泛型函数允许其签名中出现一个或多个参数化类型。每个类型参数必须在函数名称后显式声明在尖括号括起来并用逗号分隔的列表中。
#![allow(unused)] fn main() { // foo is generic over A and B fn foo<A, B>(x: A, y: B) { } }
在函数签名和函数体内部,类型参数的名称可以用作类型名称。
可以为类型参数指定Trait约束,以允许在该类型的 值上调用具有该 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 { // details elided } foo(&[1, 2]); }
会将类型参数 T
实例化为 i32
。
类型参数也可以在函数名称后的尾部路径组件中显式提供。如果上下文不足以确定类型参数,这可能是必需的。例如,mem::size_of::<u32>() == 4
。
Extern 函数限定符
extern
函数限定符允许提供可以以特定 ABI 调用函数定义
extern "ABI" fn foo() { /* ... */ }
这些通常与外部块项结合使用,后者提供函数声明,可用于调用函数而无需提供其定义
unsafe extern "ABI" {
unsafe fn foo(); /* no body */
safe fn bar(); /* no body */
}
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() { // Declares a function with the "C" ABI extern "C" fn new_i32() -> i32 { 0 } // Declares a function with the "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; }
展开(Unwinding)
大多数 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 | 展开中 | 展开 | 展开 |
panic=unwind | 非展开中 | abort (参见下方注释) | 未定义行为 |
panic=abort | 展开中 | panic 不进行展开就 abort | abort |
panic=abort | 非展开中 | panic 不进行展开就 abort | 未定义行为 |
在使用 panic=unwind
时,当 panic
在非展开的 ABI 边界处被转换为 abort 时,可能不会运行任何析构函数(Drop
调用),或者会运行直到 ABI 边界处的所有析构函数。这两种行为中哪一种会发生是未指定的。
有关跨 FFI 边界展开的其他注意事项和限制,请参见Panic 文档中的相关部分。
Const 函数
用 const
关键字限定的函数是const 函数,元组结构体和元组变体构造函数也是如此。Const 函数可以从const 上下文中调用。
Const 函数可以使用extern
函数限定符。
Const 函数不允许是async 函数。
Async 函数
函数可以被限定为 async,这也可以与 unsafe
限定符结合使用
#![allow(unused)] fn main() { async fn regular_example() { } async unsafe fn unsafe_example() { } }
Async 函数在调用时不做任何工作:相反,它们将其参数捕获到一个 future 中。当 future 被 poll 时,它将执行函数的函数体。
一个 async 函数大致等同于一个返回impl Future
且以一个async move
块作为其函数体的函数
#![allow(unused)] fn main() { // Source async fn example(x: &str) -> usize { x.len() } }
大致等同于
#![allow(unused)] fn main() { use std::future::Future; // Desugared 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
块。
版本差异:Async 函数仅从 Rust 2018 开始可用。
结合使用 async
和 unsafe
声明一个既 async 又 unsafe 的函数是合法的。生成的函数调用起来是 unsafe 的,并且(像任何 async 函数一样)返回一个 future。这个 future 只是一个普通的 future,因此不需要 unsafe
上下文来“await”它
#![allow(unused)] fn main() { // Returns a future that, when awaited, dereferences `x`. // // Soundness condition: `x` must be safe to dereference until // the resulting future is complete. async unsafe fn unsafe_example(x: *const i32) -> i32 { *x } async fn safe_example() { // An `unsafe` block is required to invoke the function initially: let p = 22; let future = unsafe { unsafe_example(&p) }; // But no `unsafe` block required here. This will // read the value of `p`: let q = future.await; } }
请注意,这种行为是将 async 函数语法糖解开为一个返回 impl Future
的函数所带来的结果——在这种情况下,我们语法糖解开的函数是一个 unsafe
函数,但返回值保持不变。
Unsafe 在 async 函数上的用法与在其他函数上的用法完全相同:它表明该函数对其调用者施加了一些额外的义务以确保健全性。与任何其他 unsafe 函数一样,这些条件可能超出初始调用本身——例如,在上面的代码片段中,unsafe_example
函数接受一个指针 x
作为参数,然后(在 awaited 时)解引用该指针。这意味着 x
必须一直有效直到 future 执行完毕,并且确保这一点是调用者的责任。
函数上的属性
外部属性允许用于函数。内部属性允许直接放在其函数体块内的 {
之后。
此示例显示了函数上的内部属性。该函数仅用“Example”一词进行了文档注释。
#![allow(unused)] fn main() { fn documented() { #![doc = "Example"] } }
注意
除了 lint 属性外,习惯上只在函数项上使用外部属性。
在函数上有意义的属性包括 cfg
, cfg_attr
, deprecated
, doc
, export_name
, link_section
, no_mangle
, lint 检查属性, must_use
, 过程宏属性, 测试属性, 和 优化提示属性。函数也接受属性宏。
函数参数上的属性
外部属性允许用于函数参数,允许的内置属性仅限于 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) {
}