const genericsの引数が条件を満たすときのみTraitを実装する
| 2022/03/10 22:31 | 公開 |
この記事のコードはv1.61.0-nightlyで確認しています。
またnightlyです。
何の話か
以下のようなArrayWrapperというstructとFooというtraitがあったとして
struct ArrayWrapper<const N: usize>([i32; N]);
trait Foo {
fn foo(&self) {}
}
以下のようにN < 5などの条件に当てはまるときだけFooを実装したい場合にどうすればよいかという話です。
ArrayWrapper([]).foo(); // OK
ArrayWrapper([0]).foo(); // OK
ArrayWrapper([0, 1]).foo(); // OK
ArrayWrapper([0, 1, 2]).foo() // OK
ArrayWrapper([0, 1, 2, 3]).foo() // OK
ArrayWrapper([0, 1, 2, 3, 4]).foo(); // Error
その前に
特定のNに対してFooを実装するには
impl Foo for ArrayWrapper<0> {}
fn main() {
ArrayWrapper([]).foo(); // OK
ArrayWrapper([0]).foo(); // Error
}
のようにしてやればよく、
すべてのNに対してFooを実装するには
impl<const N: usize> Foo for ArrayWrapper<N> {}
fn main() {
ArrayWrapper([]).foo(); // OK
ArrayWrapper([0]).foo(); // OK
ArrayWrapper([0, 1]).foo(); // OK
}
としてやればよいです。
whereで条件を追加してみる
以下のようにwhereでNの条件を追加してみます。
impl<const N: usize> Foo for ArrayWrapper<N>
where N: {N < 5}, {}
するとコンパイルエラーが発生します。
そもそもwhere N: ...みたいな形式は構文として認められていないようです。
error: expected one of `!` or `::`, found `<`
--> src/main.rs:8:13
|
8 | where N: {N < 5}, {}
| - ^ - the item list ends here
| | |
| | expected one of `!` or `::`
| while parsing this item list starting here
error: expected item, found `,`
--> src/main.rs:8:17
|
8 | where N: {N < 5}, {}
| ^ expected item
error: could not compile `impl_trait_conditions_const_generics_argument` due to 2 previous errors
実装条件用のTraitを定義する
where N: ...のように書けないという問題は実装条件用のTraitを定義するという方法で解決できます。
ただし#![feature(generic_const_exprs)]の使用が必要です。
まず以下のようにFooCondition<const C: bool>というTraitを定義します。
trait FooCondition<const C: bool> {}
次にN < 5のときだけArrayWrapper<N>がFooConditionを実装するようにします。
impl<const N: usize> FooCondition<{ N < 5 }> for ArrayWrapper<N> {}
最後にFooCondition<true>のときだけArrayWrapperがFooを実装するようにします。
impl<const N: usize> Foo for ArrayWrapper<N> where ArrayWrapper<N>: FooCondition<true> {}
これでN < 5のときにArrayWrapper<N>がFooを実装するということができました。
コード全文
#![feature(generic_const_exprs)]
struct ArrayWrapper<const N: usize>([i32; N]);
trait Foo {
fn foo(&self) {}
}
trait FooCondition<const C: bool> {}
impl<const N: usize> FooCondition<{ N < 5 }> for ArrayWrapper<N> {}
impl<const N: usize> Foo for ArrayWrapper<N> where ArrayWrapper<N>: FooCondition<true> {}
fn main() {
ArrayWrapper([]).foo(); // OK
ArrayWrapper([0]).foo(); // OK
ArrayWrapper([0, 1]).foo(); // OK
ArrayWrapper([0, 1, 2]).foo(); // OK
ArrayWrapper([0, 1, 2, 3]).foo(); // OK
// ArrayWrapper([0, 1, 2, 3, 4]).foo(); // Error
}
#![feature(generic_const_exprs)]が必要な理由
stableではFooCondition<{ N < 5 }>のような条件式の中などでconst genericsの引数を使用できないという制限があるので
impl<const N: usize> FooCondition<{ N < 5 }> for ArrayWrapper<N> {}の{ N < 5 }の部分のコンパイルを通すのに必要です。
#![feature(generic_const_exprs)]を使用する際の注意
warning: the feature `generic_const_exprs` is incomplete and may not be safe to use and/or cause compiler crashes
--> src/main.rs:1:12
|
1 | #![feature(generic_const_exprs)]
| ^^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(incomplete_features)]` on by default
= note: see issue #76560 <https://github.com/rust-lang/rust/issues/76560> for more information
という警告が出ます。
ざっくり訳すと「generic_const_exprsは不完全で安全ではない可能性がありコンパイラがクラッシュすることがあります」という中々怖い内容です。
使うのであればそれを認識した上で使用しましょう。
あとがき
この機能を早くstableでも使えるようになってほしいです。
今回のコードはGitHubで公開しています。
stableで使えるようになったら更新すると思います。