函数

语法
函数 :
   FunctionQualifiers fn 标识符 泛型参数?
      ( 函数参数? )
      函数返回类型? WhereClause?
      ( 块表达式 | ; )

FunctionQualifiers (函数限定符) :
   const? async1? ItemSafety?2 (extern Abi?)?

ItemSafety (条目安全) :
   safe3 | unsafe

Abi (应用程序二进制接口) :
   字符串字面量 | 原始字符串字面量

FunctionParameters (函数参数) :
      SelfParam ,?
   | (SelfParam ,)? FunctionParam (, FunctionParam)* ,?

SelfParam (Self 参数) :
   外部属性* ( ShorthandSelf | TypedSelf )

ShorthandSelf (简写 Self) :
   (& | & 生命周期)? mut? self

TypedSelf (类型化 Self) :
   mut? self : 类型

FunctionParam (函数参数) :
   外部属性* ( FunctionParamPattern | ... | 类型 4 )

FunctionParamPattern (函数参数模式) :
   PatternNoTopAlt : ( 类型 | ... )

FunctionReturnType (函数返回类型) :
   -> 类型

1

async 限定符在 2015 edition 中是不允许的。

3

safe 函数限定符在语义上仅允许在 extern 代码块中使用。

2

与早于 Rust 2024 edition 的版本相关:在 extern 代码块中,只有当 extern 被限定为 unsafe 时,才允许使用 safeunsafe 函数限定符。

4

仅具有类型的函数参数仅在 2015 edition 的 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 }
}

如果第一个参数是 SelfParam,则表明该函数是一个 方法

带有 self 参数的函数只能作为 关联函数 出现在 traitimplementation 中。

带有 ... 标记的参数表示 可变参数函数,并且只能用作 外部代码块 函数的最后一个参数。可变参数可以具有可选的标识符,例如 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;
}

ABI 与 "Rust" 不同的函数不支持与 Rust 完全相同的方式进行展开。因此,在具有此类 ABI 的函数末尾之后展开会导致进程中止。

注意rustc 实现的 LLVM 后端通过执行非法指令来中止进程。

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 被轮询时,它将执行函数的函数体。

一个 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() }
}
}

实际的脱糖 (desugaring) 更为复杂

  • 脱糖中的返回类型被假定为捕获 async fn 声明中的所有生命周期参数。这可以在上面的脱糖示例中看到,该示例显式地 outlives,因此捕获了 'a
  • 函数体中的 async move 代码块 捕获所有函数参数,包括那些未使用的或绑定到 _ 模式的参数。这确保了函数参数的 drop 顺序与函数不是 async 时相同,只是 drop 发生在返回的 future 完全被 await 之后。

有关 async 效果的更多信息,请参阅 async 代码块

Edition 差异:Async 函数仅从 Rust 2018 开始可用。

组合 asyncunsafe

声明一个既是 async 又是 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;
}
}

请注意,此行为是脱糖为返回 impl Future 的函数的结果——在这种情况下,我们脱糖为的函数是一个 unsafe 函数,但返回值保持不变。

Unsafe 在 async 函数上的用法与在其他函数上的用法完全相同:它表明该函数对其调用者施加了一些额外的义务,以确保健全性。与任何其他 unsafe 函数一样,这些条件可能会超出初始调用本身——在上面的代码片段中,例如,unsafe_example 函数接受指针 x 作为参数,然后(在 await 时)解引用该指针。这意味着 x 必须在 future 完成执行之前保持有效,并且调用者有责任确保这一点。

函数上的属性

外部属性 在函数上是允许的。内部属性 允许直接在函数体 代码块 内的 { 之后使用。

此示例显示了函数上的内部属性。该函数仅用单词 “Example” 进行文档注释。

#![allow(unused)]
fn main() {
fn documented() {
    #![doc = "Example"]
}
}

注意:除了 lint 之外,在函数条目上仅使用外部属性是惯用的。

在函数上具有含义的属性是 cfgcfg_attrdeprecateddocexport_namelink_sectionno_manglelint 检查属性must_use过程宏属性测试属性优化提示属性。函数也接受属性宏。

函数参数上的属性

外部属性 在函数参数上是允许的,并且允许的 内置属性 限制为 cfgcfg_attrallowwarndenyforbid

#![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) {
}