以前の記事でRustのconst fn
の制限が厳しいという話を書いた。
正直使い所がわからないが悔しかったのでいろいろ試していたら、条件によって返す値を変えることくらいはできた。
Rust 1.31.0で確認しています。
追記: 2019/11/06
Rust 1.39.0で標準のabs
がconst fn
になりました。
https://qiita.com/block/items/646c725289a8431383f9
追記: 2020/08/29
Rust 1.46.0でif
などが使用できるようになりabs
は通常の関数と同じように書けるようになったため、おまけ3として追加しました。
緩和された内容はRustのconst fnの制限がさらに緩和された(Rust 1.46.0)に書きました。
absの実装
const fn abs(v: i32) -> i32 {
[-v, v][(v >= 0) as usize]
}
解説
[-v, v]
符号を反転した値ともとの値の配列を作る。
[(v >= 0) as usize]
v
が0以上かどうかで配列のインデックスを変更する。
(v >= 0)
の結果がbool
なのでusize
にキャストするとtrue
なら1
を、false
なら0
を返す。
つまりtrue
であれば[-v, v][1]
となりv
を返し、false
であれば[-v, v][0]
となり-v
を返す。
何故こんな実装にしなければならないのか
絶対値を返すだけで何故こんなにも分かりづらい形にしなければならないのか。
まずconst fn
ではif
が使えない。
そのため下記はコンパイルエラーになる
const fn abs(v: i32) -> i32 {
if v >= 0 {
v
} else {
-v
}
}
インデックスだけでも変数に代入すれば多少はわかりやすくなりそうだが変数が使えないので下記はコンパイルエラー
const fn _abs(v: i32) -> i32 {
let index: usize = (v >= 0) as usize; // 変数は使えないのでコンパイルエラー
[-v, v][index]
}
const fn
の中でも定数の定義はできるが変数を使用して定数の定義はできないので下記もコンパイルエラー
const fn _abs(v: i32) -> i32 {
const INDEX: usize = (v >= 0) as usize; // 変数を使用して定数の定義はできないのでコンパイルエラー
[-v, v][INDEX]
}
変数を作ることなくアクセスする返す値を変更したいため、今回の形になった。
ビット演算で符号を変更すればいいのでは?
そうですね。多分できると思います。
追記: 2019/11/06
標準のabs
はビット演算してる。
https://qiita.com/block/items/646c725289a8431383f9
まとめ
絶対値を返すだけでとても苦労する。
条件によって返す値は変えられたのでもしかしたら多少複雑な処理はできるかもしれない。
可読性は犠牲になりそう。
おまけ
別パターン。
配列を作らなくても良い方法。
const fn abs(v: i32) -> i32 {
v * (((0 <= v) as i32) * 2 - 1)
}
おまけ2
1.33.0でconst fn
の制限が緩和されたので可読性を上げる。
途中で書いていたindexを変数に代入するやつです。
const fn _abs(v: i32) -> i32 {
let index = (v >= 0) as usize;
[-v, v][index]
}
おまけ3
1.46.0でif
が使えるようになったので通常の関数のように書くことができます。
const fn abs(value: i32) -> i32 {
if value >= 0 {
value
} else {
-value
}
}