blockの巣

const fnで使用可能なVec3を定義する

2022/01/27 13:25 公開
Rust

この記事はRust v1.60.0-nightlyで動作確認しています。


今私はRustでコンパイル時レイトレーシングという試みをしているのですが、そこで必要になったconst fnの中で使用できるVec3を定義するためのに必要なことを書いていきます。
都度Rust Playgroundへのリンクを張っていますが、コンパイルに時間がかかることがありRust Playgroundではタイムアウトするかもしれません。

struct Vec3 {
  x: f64,
  y: f64,
  z: f64,
}

このようなVec3があるとしてconst fnの中で

const fn const_function() {
  let mut v1: Vec3 = Vec3 { x: 1.0, y: 2.0, z: 3.0 };
  let v2: Vec3 = Vec3 { x: 2.0, y: 3.0, z: 4.0 };

  let _ = v1 + v2; // Add
  v += Vec3 { x: 2.0, y: 3.0, z: 4.0 }; // AddAssign
  v[0] = 0.0; // IndexMut
}

上記のようなことができるようにします。

Add

まずconst fnの中で動くAddを定義してみます。

下記のように通常のAddを定義してconst fnの中で使ってみたものをコンパイルしてみると

use std::ops::*;

struct Vec3 {
    x: f64,
    y: f64,
    z: f64,
}

impl Add<Vec3> for Vec3 {
    type Output = Self;

    fn add(self, rhs: Vec3) -> Self::Output {
        Vec3 {
            x: self.x + rhs.x,
            y: self.y + rhs.y,
            z: self.z + rhs.z,
        }
    }
}

const fn const_function() {
  let v1: Vec3 = Vec3 { x: 1.0, y: 2.0, z: 3.0 };
  let v2: Vec3 = Vec3 { x: 2.0, y: 3.0, z: 4.0 };

  let _ = v1 + v2; // Add
}
   Compiling playground v0.0.1 (/playground)
error[E0015]: calls in constant functions are limited to constant functions, tuple structs and tuple variants
  --> src/lib.rs:25:11
   |
25 |   let _ = v1 + v2; // Add
   |           ^^^^^^^

For more information about this error, try `rustc --explain E0015`.
error: could not compile `playground` due to previous error

のようなコンパイルエラーが発生します。
Rust Playgroundで確認

まずAdd Traitの実装をconst fnとしてできるように#![feature(const_trait_impl)]を追記し、
impl Add<Vec3> for Vec3impl const Add<Vec3> for Vec3に書き換えます。

コード全文
#![feature(const_trait_impl)]
use std::ops::*;

struct Vec3 {
    x: f64,
    y: f64,
    z: f64,
}

impl const Add<Vec3> for Vec3 {
    type Output = Self;

    fn add(self, rhs: Vec3) -> Self::Output {
        Vec3 {
            x: self.x + rhs.x,
            y: self.y + rhs.y,
            z: self.z + rhs.z,
        }
    }
}

const fn const_function() {
  let v1: Vec3 = Vec3 { x: 1.0, y: 2.0, z: 3.0 };
  let v2: Vec3 = Vec3 { x: 2.0, y: 3.0, z: 4.0 };

  let _ = v1 + v2; // Add
}

すると今度はこのようなエラーが出ます。

   Compiling playground v0.0.1 (/playground)
error[E0658]: floating point arithmetic is not allowed in constant functions
  --> src/lib.rs:15:16
   |
15 |             x: self.x + rhs.x,
   |                ^^^^^^^^^^^^^^
   |
   = note: see issue #57241 <https://github.com/rust-lang/rust/issues/57241> for more information
   = help: add `#![feature(const_fn_floating_point_arithmetic)]` to the crate attributes to enable

error[E0658]: floating point arithmetic is not allowed in constant functions
  --> src/lib.rs:16:16
   |
16 |             y: self.y + rhs.y,
   |                ^^^^^^^^^^^^^^
   |
   = note: see issue #57241 <https://github.com/rust-lang/rust/issues/57241> for more information
   = help: add `#![feature(const_fn_floating_point_arithmetic)]` to the crate attributes to enable

error[E0658]: floating point arithmetic is not allowed in constant functions
  --> src/lib.rs:17:16
   |
17 |             z: self.z + rhs.z,
   |                ^^^^^^^^^^^^^^
   |
   = note: see issue #57241 <https://github.com/rust-lang/rust/issues/57241> for more information
   = help: add `#![feature(const_fn_floating_point_arithmetic)]` to the crate attributes to enable

For more information about this error, try `rustc --explain E0658`.
error: could not compile `playground` due to 3 previous errors

Rust Playgroundで確認

これはconst fnの中で浮動小数点演算が行えないというエラーなので#![feature(const_fn_floating_point_arithmetic)]を書くことで解決できます。

変更後のコードは差分が少ないので載せませんがRust Playgroundで確認できるので気になる方はリンク先を確認してみてください。

AddAssign

次にAddAssignを定義します。

impl const AddAssign<Vec3> for Vec3 {
    fn add_assign(&mut self, rhs: Vec3) {
        *self = Vec3 {
            x: self.x + rhs.x,
            y: self.y + rhs.y,
            z: self.z + rhs.z,
        }
    }
}

const fn const_function() {
  let mut v1: Vec3 = Vec3 { x: 1.0, y: 2.0, z: 3.0 };
  let v2: Vec3 = Vec3 { x: 2.0, y: 3.0, z: 4.0 };
  
  v1 += v2;
}

上記のようにAddと同じようにimpl const AddAssignで定義してconst fnで使用してみると

   Compiling playground v0.0.1 (/playground)
error[E0658]: mutable references are not allowed in constant functions
  --> src/lib.rs:25:19
   |
25 |     fn add_assign(&mut self, rhs: Vec3) {
   |                   ^^^^^^^^^
   |
   = note: see issue #57349 <https://github.com/rust-lang/rust/issues/57349> for more information
   = help: add `#![feature(const_mut_refs)]` to the crate attributes to enable

error[E0658]: mutable references are not allowed in constant functions
  --> src/lib.rs:38:3
   |
38 |   v1 += v2;
   |   ^^
   |
   = note: see issue #57349 <https://github.com/rust-lang/rust/issues/57349> for more information
   = help: add `#![feature(const_mut_refs)]` to the crate attributes to enable

For more information about this error, try `rustc --explain E0658`.
error: could not compile `playground` due to 2 previous errors

というエラーが出ます。
Rust Playgroundで確認

これは&mutな変数を引数にする関数がconst fnの中で使えないという内容で#![feature(const_mut_refs)]を追加してやることで解決できます。

コード全文
#![feature(const_trait_impl)]
#![feature(const_fn_floating_point_arithmetic)]
#![feature(const_mut_refs)]

use std::ops::*;

struct Vec3 {
    x: f64,
    y: f64,
    z: f64,
}

impl const Add<Vec3> for Vec3 {
    type Output = Self;

    fn add(self, rhs: Vec3) -> Self::Output {
        Vec3 {
            x: self.x + rhs.x,
            y: self.y + rhs.y,
            z: self.z + rhs.z,
        }
    }
}

impl const AddAssign<Vec3> for Vec3 {
    fn add_assign(&mut self, rhs: Vec3) {
        *self = Vec3 {
            x: self.x + rhs.x,
            y: self.y + rhs.y,
            z: self.z + rhs.z,
        }
    }
}

const fn const_function() {
    let mut v1: Vec3 = Vec3 { x: 1.0, y: 2.0, z: 3.0 };
    let v2: Vec3 = Vec3 { x: 2.0, y: 3.0, z: 4.0 };
    
    v1 += v2;
}

Rust Playgroundで確認

IndexMut

続いてIndexMutを定義します。
IndexMutの定義はIndexの定義が必要なので両方定義します。

impl const Index<usize> for Vec3 {
    type Output = f64;
    
    fn index(&self, i: usize) -> &Self::Output {
        match i {
            0 => &self.x,
            1 => &self.y,
            2 => &self.z,
            _ => panic!("out of range."),
        }
    }
}

impl const IndexMut<usize> for Vec3 {
    fn index_mut(&mut self, i: usize) -> &mut Self::Output {
        match i {
            0 => &mut self.x,
            1 => &mut self.y,
            2 => &mut self.z,
            _ => panic!("out of range."),
        }
    }
}

ここまでに #![feature(const_trait_impl)], #![feature(const_fn_floating_point_arithmetic)], #![feature(const_mut_refs)]を有効にしているため、特別なことをする必要はありません。
indexindex_mutの中でpanic!を使用していますが、これは何もせずともconst fnの中で使用できます。

これで

const fn const_function() {
    let mut v: Vec3 = Vec3 { x: 1.0, y: 2.0, z: 3.0 };
    v[0] = 0.0;
}

という処理のコンパイルが通るようになりました。

コード全文
#![feature(const_trait_impl)]
#![feature(const_fn_floating_point_arithmetic)]
#![feature(const_mut_refs)]

use std::ops::*;

struct Vec3 {
    x: f64,
    y: f64,
    z: f64,
}

impl const Add<Vec3> for Vec3 {
    type Output = Self;

    fn add(self, rhs: Vec3) -> Self::Output {
        Vec3 {
            x: self.x + rhs.x,
            y: self.y + rhs.y,
            z: self.z + rhs.z,
        }
    }
}

impl const AddAssign<Vec3> for Vec3 {
    fn add_assign(&mut self, rhs: Vec3) {
        *self = Vec3 {
            x: self.x + rhs.x,
            y: self.y + rhs.y,
            z: self.z + rhs.z,
        }
    }
}

impl const Index<usize> for Vec3 {
    type Output = f64;

    fn index(&self, i: usize) -> &Self::Output {
        match i {
            0 => &self.x,
            1 => &self.y,
            2 => &self.z,
            _ => panic!("out of range."),
        }
    }
}

impl const IndexMut<usize> for Vec3 {
    fn index_mut(&mut self, i: usize) -> &mut Self::Output {
        match i {
            0 => &mut self.x,
            1 => &mut self.y,
            2 => &mut self.z,
            _ => panic!("out of range."),
        }
    }
}

const fn const_function() {
    let mut v: Vec3 = Vec3 { x: 1.0, y: 2.0, z: 3.0 };
    v[0] = 0.0;
}

Rust Playgroundで確認
コンパイルに時間がかかることがありRust Playgroundではタイムアウトするかもしれません。

panic!内での変数埋め込みはできない

panic!が使えるならpanic!("out of range. index = {i}")のようにどの値が渡されたことで範囲外アクセスが発生したのかをコンパイルエラーのメッセージに表示したくなると思います。
しかし普通に書いても下記のようなコンパイルエラーが発生してしまいます。

   Compiling playground v0.0.1 (/playground)
error[E0658]: function pointer casts are not allowed in constant functions
  --> src/lib.rs:43:48
   |
43 |             _ => panic!("out of range. index = {i}"),
   |                                                ^^^
   |
   = note: see issue #57563 <https://github.com/rust-lang/rust/issues/57563> for more information
   = help: add `#![feature(const_fn_fn_ptr_basics)]` to the crate attributes to enable
   = note: this error originates in the macro `$crate::const_format_args` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0015]: calls in constant functions are limited to constant functions, tuple structs and tuple variants
  --> src/lib.rs:43:18
   |
43 |             _ => panic!("out of range. index = {i}"),
   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: this error originates in the macro `$crate::const_format_args` (in Nightly builds, run with -Z macro-backtrace for more info)

Some errors have detailed explanations: E0015, E0658.
For more information about an error, try `rustc --explain E0015`.
error: could not compile `playground` due to 2 previous errors

これはコンパイルエラーにあるように#![feature(const_fn_fn_ptr_basics)]を追記してもコンパイルは通りません。
ビルド時の引数次第でなんとかなりそうなエラーは出ていましたがそこまで確認はしていないです。

まとめ

#![feature(const_trait_impl)], #![feature(const_fn_floating_point_arithmetic)], #![feature(const_mut_refs)]を追加しTraitの実装時にimpl constと書いてやれば普通に定義するのと変わらないように書くことができます。