kubo39's blog

ただの雑記です。

Rustのconst_fnがそれほど使い勝手がよくなさそうだという話

現状ではnightlyでしか使えないが、Rustは部分的にコンパイル時に関数評価を定数畳み込みする const_fn という機能がある。

https://doc.rust-lang.org/beta/unstable-book/language-features/const-fn.html

そこでコンパイル時にバイト列を比較するコードを書いてみたが、これはコンパイルエラーになる。

#![feature(const_fn)]

const fn my_strcmp(s1: &[u8], s2: &[u8]) -> i32 {
    let len = if s1.len() <= s2.len() {
        s1.len()
    } else {
        s2.len()
    };

    for i in 0..len {
        if s1[i] != s2[i] {
            if s1[i] > s2[i] {
                return 1;
            } else {
                return -1;
            }
        }
    };

    if s1.len() < s2.len() {
        -1
    } else if s1.len() > s2.len() {
        1
    } else {
        0
    }
}

const X: i32 = my_strcmp(b"kubo39", b"kubo39");

fn main() {
    println!("{}", X);
    // println!("{}", my_strcmp(b"kubo39", b"kubo39")); // 実行時評価、これは動く
}

なるほど、const_fn は推移的でないとだめそうだ。

$ rustc --version
rustc 1.19.0-nightly (04145943a 2017-06-19)
$ rustc -O strcmp.rs
error[E0015]: calls in constant functions are limited to constant functions, struct and enum constructors
 --> strcmp.rs:4:18
  |
4 |     let len = if s1.len() <= s2.len() {
  |                  ^^^^^^^^

error[E0015]: calls in constant functions are limited to constant functions, struct and enum constructors
 --> strcmp.rs:4:30
  |
4 |     let len = if s1.len() <= s2.len() {
  |                              ^^^^^^^^

error[E0019]: constant function contains unimplemented expression type
 --> strcmp.rs:4:15
  |
4 |       let len = if s1.len() <= s2.len() {
  |  _______________^
5 | |         s1.len()
6 | |     } else {
7 | |         s2.len()
8 | |     };
  | |_____^

error: aborting due to previous error(s)

こういうコードはどうだろうか。

#![feature(const_fn)]

const fn lower_len(s1_len: usize, s2_len: usize) -> usize {
    if s1_len <= s2_len {
        s1_len
    } else {
        s2_len
    }
}

const fn my_strcmp(s1: &'static [u8], s1_len: usize, s2: &'static [u8], s2_len: usize) -> i32 {
    const len: usize = lower_len(s1_len, s2_len);

    for i in 0..len {
        if s1[i] != s2[i] {
            if s1[i] > s2[i] {
                return 1;
            } else {
                return -1;
            }
        }
    };

    if s1_len < s2_len {
        -1
    } else if s1_len > s2_len {
        1
    } else {
        0
    }
}

const S1: &'static [u8; 6] = b"kubo39";
const S1_LEN: usize = 6;
const S2: &'static [u8; 6] = b"kubo39";
const S2_LEN: usize = 6;

const X: i32 = my_strcmp(S1, S1_LEN, S2, S2_LEN);

fn main() {
    println!("{}", X);
    // println!("{}", my_strcmp(b"kubo39", b"kubo39"));
}

だめだ、 error[E0434]: can't capture dynamic environment in a fn item; use the || { ... } closure form instead のように怒られてしまっている。

$ rustc -O strcmp.rs
error[E0434]: can't capture dynamic environment in a fn item; use the || { ... } closure form instead
  --> strcmp.rs:12:34
   |
12 |     const len: usize = lower_len(s1_len, s2_len);
   |                                  ^^^^^^

error[E0434]: can't capture dynamic environment in a fn item; use the || { ... } closure form instead
  --> strcmp.rs:12:42
   |
12 |     const len: usize = lower_len(s1_len, s2_len);
   |                                          ^^^^^^

error: aborting due to previous error(s)

他にもいろいろ試したが、どうも制約が強すぎてそれほど利用できる箇所はなさそうなので現状あまり期待しないほうがよさそうだ。