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で使えるようになったら更新すると思います。