函数

语法
函数 :
   函数限定符 fn IDENTIFIER 泛型参数?
      ( 函数参数? )
      函数返回类型? Where从句?
      ( 块表达式 | ; )

函数限定符 :
   const? async1? 项安全性?2 (extern Abi?)?

项安全性 :
   safe3 | unsafe

Abi :
   字符串字面量 | 原始字符串字面量

函数参数 :
      Self参数 ,?
   | (Self参数 ,)? 函数参数 (, 函数参数)* ,?

Self参数 :
   外部属性* ( 简写Self | 带类型Self )

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

带类型Self :
   mut? self : 类型

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

函数参数模式 :
   非顶层Alt模式 : ( 类型 | ... )

函数返回类型 :
   -> 类型

1

async 限定符在 2015 版中不允许使用。

3

从语义上讲,safe 函数仅允许在 extern中使用。

2

适用于 Rust 2024 之前的版本:在 extern 块内,safeunsafe 函数限定符仅在 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 上的 longjmpglibc 中的 pthread_exit 就是通过这种方式实现的。强制展开被明确排除在表中的“原生展开”列之外。

panic 运行时ABIpanic-展开原生展开 (非强制)
panic=unwind展开中展开展开
panic=unwind非展开中abort (参见下方注释)未定义行为
panic=abort展开中panic 不进行展开就 abortabort
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 开始可用。

结合使用 asyncunsafe

声明一个既 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) {
}