最先由Liso/cpp语言支持。

CTFE 编译期函数求值 (compile time function evaluation)

主要讲CTFE

Rust支持的两种方式

1.过程宏+build脚本 (比较完善,几乎可以在编译器执行任何代码 如:生成代码,类型计算)

2.类似于cpp中的constexpr的CTFE功能 ( 类似于c++的常量表达式 ,暂时只支持最小化的子集,还在完善过程中)

Rust的CTFE

为什么要学CTFE ,过程宏+build脚本虽然可以在编译器求值 ,但是没有第二种通用方便。

比如:你没有办法在宏代码和普通代码之间进行代码共享。

而第二种常量表达式就是可以的,所以支持常量表达式很重要。

1.26稳定版开始支持编译期计算.

常量函数(const fn)

常量表达式与常量上下文

  1. const:X:T =...; //= 后面的表达式 要求必须是 编译期间可以执行的代码 也就是常量表达式
  1. fn main(){
  2. let an = (42,).0;
  3. const AN:i32 = an;//attempt to use a non-constant value in a constant
  4. //尝试在常量中使用非常量值 这不是一个常量表达式
  5. const AN:i32 = (42,).0; // 直接使用字面量执行赋值 是满足要求的
  6. }

常量上下文包含:

  • 常量值初始化位置
  • 静态数据得长度表达式,[T;N]
  • 重复的长度表达式,类似于:[0;10]
  • 静态变量\枚举判别式的初始化位置

常量上下文是编译器唯一进行编译器求值的地方

常量传播

常量传播和编译器计算是不同的

  1. 常量传播是编译器的一种优化 比如把3+4的地方都变成7 避免运行时再次计算
  2. 常量传播并不能改变程序的任何行为,并且对开发者是隐藏的.
  3. 编译器计算则是值编译时执行的代码,必须知道结果,才能继续执行.
  1. const X: u32 = 3 + 4;// CTFE
  2. let x = 4 + 3;//不是编译器计算,但可能会被常量传播优化,因为他不时一个常量上下文.
  1. Rust里的大部分表达式都可用作常量表达式.
  2. 并不是所有的常量表达式都可以用于常量上下文.
  3. 编译期求值必须得到一个确定性的结果

常量函数

  1. fn main() {
  2. println!("{}", GCD)
  3. }
  4. const GCD: u32 = gcd(21, 7);
  5. const fn gcd(a: u32, b: u32) -> u32 {
  6. match (a, b) {
  7. (x, 0) | (0, x) => x,
  8. (x, y) if x % 2 == 0 && y % 2 == 0 => gcd(x / 2, y),
  9. (x, y) if x < y => gcd((y - x) / 2, x),
  10. (x, y) => gcd((x - y) / 2, y),
  11. }
  12. }
  1. fn main() {
  2. println!("{}", X)
  3. }
  4. const fn fib(n: u128) -> u128 {
  5. const fn helper(n: u128, a: u128, b: u128, i: u128) -> u128 {
  6. if i <= n {
  7. helpr(n, b, a + b, i + 1)
  8. } else {
  9. b
  10. }
  11. }
  12. helper(n, 1, 1, 2)
  13. }
  14. const X: u128 = fib(10);

常量安全子类型系统

  1. 普通的fn关键字定义的函数,是由safe rust主类型系统保证安全.
  2. const fn定义的函数,由safe rust主类型系统下有一个专门用于常量计算的子类型系统来保证常量安全.
  3. unsafe 定义的函数 则是 绕开了safe rust 由用户来保证安全.

常量上下文可以接受的常量表达式

  1. const fn 函数
  2. 元组结构体
  3. 元组的值

反例

  1. const fn hello() -> String {// String 是作用于堆上的 无法在编译期间获取值
  2. "hello".to_string()
  3. }
  4. const S: String = hello();
  5. fn main() {
  6. println!("{:?}", S)
  7. //cannot call non-const fn `<str as ToString>::to_string` in constant functions
  8. //不能在常量函数中调用非常量 fn `<str as ToString>::to_string`
  9. //calls in constant functions are limited to constant functions, tuple structs and tuple variants
  10. //常量函数中的调用仅限于常量函数、元组结构和元组变体
  11. }

元组结构体demo

  1. fn main() {
  2. println!("{:?}", A)
  3. }
  4. #[derive(Debug)]
  5. struct Answer(u32);
  6. const A: Answer = Answer(43);

编译期计算如何实现

MIR(中级中间语言) 他会给转换为控制流图

basic block bb0=> basic bolock bb1=>…=>basic bolock bbn

Rust 种包含了一个miri的解释器 执行中级中间语言代码

编译期计算就是执行中级中间级代码来实现的.

准确的说是执miri种 const上下文的代码.

常量泛型(const generic)

为什么需要常量泛型

Rust中的静态数组一直以来属于[二等公民],不方便使用

在rust中 数组的长度不同就是不同的类型

15.Rust 编译期计算 - 图1

你无法使用泛型来统一定义不同长度的数组

常量泛型是什么

  1. use core::mem::MaybeUninit;
  2. pub struct ArrayVec<T, const N: usize> {
  3. items: [MaybeUninit<T>; N],
  4. length: usize,
  5. }

常量泛型是一种依赖类型.