Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

常量求值

常量求值是在编译期间计算表达式结果的过程。只有所有表达式的一个子集可以在编译时求值。

常量表达式

某些形式的表达式(称为常量表达式)可以在编译时求值。

const 上下文中的表达式必须是常量表达式。

const 上下文中的表达式始终在编译时求值。

在 const 上下文之外,常量表达式可能在编译时求值,但不保证。

如果值必须在编译时求值(即在 const 上下文中),诸如越界数组索引溢出等行为是编译器错误。否则,这些行为是警告,但在运行时可能会导致 panic。

以下表达式是常量表达式,只要任何操作数也是常量表达式,并且不会导致运行任何 Drop::drop 调用。

  • 指向静态项的路径,有以下限制:

    • 在任何常量求值上下文中不允许写入 static 项。
    • 在任何常量求值上下文中不允许读取 extern 静态项。
    • 如果求值static 项的初始化器中进行,则不允许读取任何可变 static。可变 staticstatic mut 项,或具有内部可变类型的 static 项。

    这些要求仅在常量被求值时检查。换句话说,在 const 上下文中语法上存在此类访问是允许的,只要它们从未被执行。

  • 所有形式的借用,包括原始借用,除了那些临时作用域会被延长到程序结束(见临时生命周期延长)的表达式的借用,并且这些表达式要么是:

    • 可变借用。
    • 对产生具有内部可变性值的表达式的共享借用。
    #![allow(unused)]
    fn main() {
    // 由于处于尾部位置,此借用将临时值的作用域延长到程序结束。
    // 由于借用是可变的,这在 const 表达式中不允许。
    const C: &u8 = &mut 0; // 错误,不允许
    }
    #![allow(unused)]
    fn main() {
    // Const 块类似于 `const` 项的初始化器。
    let _: &u8 = const { &mut 0 }; // 错误,不允许
    }
    #![allow(unused)]
    fn main() {
    use core::sync::atomic::AtomicU8;
    // 这是不允许的,因为 1) 临时作用域被延长到程序结束,
    // 且 2) 临时值具有内部可变性。
    const C: &AtomicU8 = &AtomicU8::new(0); // 错误,不允许
    }
    #![allow(unused)]
    fn main() {
    use core::sync::atomic::AtomicU8;
    // 同上。
    let _: &_ = const { &AtomicU8::new(0) }; // 错误,不允许
    }
    #![allow(unused)]
    fn main() {
    #![allow(static_mut_refs)]
    // 即使此借用是可变的,它也不是临时值的借用,因此允许。
    const C: &u8 = unsafe { static mut S: u8 = 0; &mut S }; // 正确
    }
    #![allow(unused)]
    fn main() {
    use core::sync::atomic::AtomicU8;
    // 即使此借用是对具有内部可变性值的借用,
    // 它不是临时值的借用,因此允许。
    const C: &AtomicU8 = {
        static S: AtomicU8 = AtomicU8::new(0); &S // 正确
    };
    }
    #![allow(unused)]
    fn main() {
    use core::sync::atomic::AtomicU8;
    // 此对内部可变临时值的共享借用是允许的,
    // 因为其作用域未被延长。
    const C: () = { _ = &AtomicU8::new(0); }; // 正确
    }
    #![allow(unused)]
    fn main() {
    // 即使借用是可变的且临时值由于提升而存活到程序结束,这也是允许的,
    // 因为借用不在尾部位置,因此临时值的作用域
    // 不会通过临时生命周期延长而扩展。
    const C: () = { let _: &'static mut [u8] = &mut []; }; // 正确
    //                                              ~~
    //                                     已提升的临时值。
    }

    Note

    换句话说——关注允许什么而不是不允许什么——对内部可变数据的共享借用和可变借用仅在 const 上下文中被借用的位置表达式瞬态的间接的静态的时才允许。

    如果位置表达式是当前 const 上下文局部的变量或其临时作用域包含在当前 const 上下文内的表达式,则该位置表达式是瞬态的

    #![allow(unused)]
    fn main() {
    // 借用是对初始化器局部变量的借用,因此此位置表达式是瞬态的。
    const C: () = { let mut x = 0; _ = &mut x; };
    }
    #![allow(unused)]
    fn main() {
    // 借用是对其作用域未被延长的临时值的借用,因此此位置表达式是瞬态的。
    const C: () = { _ = &mut 0u8; };
    }
    #![allow(unused)]
    fn main() {
    // 当临时值被提升但生命周期未被延长时,其位置表达式仍被视为瞬态的。
    const C: () = { let _: &'static mut [u8] = &mut []; };
    }

    如果位置表达式是解引用表达式,则该位置表达式是间接的

    #![allow(unused)]
    fn main() {
    const C: () = { _ = &mut *(&mut 0); };
    }

    如果位置表达式是 static 项,则该位置表达式是静态的

    #![allow(unused)]
    fn main() {
    #![allow(static_mut_refs)]
    const C: &u8 = unsafe { static mut S: u8 = 0; &mut S };
    }

    Note

    这些规则的一个令人惊讶的后果是我们允许这种写法,

    #![allow(unused)]
    fn main() {
    const C: &[u8] = { let x: &mut [u8] = &mut []; x }; // 正确
    //                                    ~~~~~~~
    // 空数组即使在可变借用之后也会被提升。
    }

    但我们不允许这段类似的代码:

    #![allow(unused)]
    fn main() {
    const C: &[u8] = &mut []; // 错误
    //               ~~~~~~~
    //           尾部表达式。
    }

    这两者之间的区别在于,在第一种情况下,空数组被提升但其作用域不经历临时生命周期延长,因此我们认为位置表达式是瞬态的(即使在提升之后该位置确实存活到程序结束)。在第二种情况下,空数组临时值的作用域确实经历了生命周期延长,因此由于是对生命周期延长临时值的可变借用而被拒绝(因此借用了非瞬态位置表达式)。

    这种效果令人惊讶,因为在这种情况下,临时生命周期延长导致比没有它更少的代码可以编译。

    更多细节请参见 issue #143129

  • 解引用表达式

    #![allow(unused)]
    fn main() {
    use core::cell::UnsafeCell;
    const _: u8 = unsafe {
        let x: *mut u8 = &raw mut *&mut 0;
        //                        ^^^^^^^
        //             可变引用的解引用。
        *x = 1; // 可变指针的解引用。
        *(x as *const u8) // 常量指针的解引用。
    };
    const _: u8 = unsafe {
        let x = &UnsafeCell::new(0);
        *x.get() = 1; // 内部可变值的修改。
        *x.get()
    };
    }
  • 强制转换表达式,除了
    • 指针到地址的强制转换和
    • 函数指针到地址的强制转换。

Const 上下文

const 上下文是以下之一:

数组类型长度表达式、数组重复长度表达式和 const 泛型参数在使用外部泛型参数时受到限制:此类表达式必须是单个 const 泛型参数,或者是不引用任何泛型参数的表达式。

Const 函数

const 函数是可以从 const 上下文调用的函数。它使用 const 限定符定义,也包括元组结构体元组枚举变体构造函数。

Example

#![allow(unused)]
fn main() {
const fn square(x: i32) -> i32 { x * x }

const VALUE: i32 = square(12);
}

从 const 上下文调用时,const 函数由编译器在编译时解释。解释发生在编译目标的环境中,而不是主机环境中。因此,如果你针对 32 位系统编译,usize32 位,无论你是在 64 位还是 32 位系统上构建。

当 const 函数在 const 上下文之外调用时,其行为与没有 const 限定符时相同。

const 函数的主体只能使用常量表达式

const 函数不允许是 async

const 函数的参数和返回类型的类型限制为与 const 上下文兼容的类型。