Rustを学び始めると、ジェネリクス(総称型)の概念とその記法に戸惑うことがあります。特に、ジェネリック型パラメータをどこに配置するべきか—impl
ブロックの後ろに置くべきか、それとも個々のメソッド定義に置くべきか—という点は混乱しやすいです。
ジェネリクスの基本的な配置ルール
Rustでジェネリクスを使用する場合、その配置には下記のルールがあることを認識するとよいです:
- 構造体全体で使用するジェネリック型パラメータは、構造体名の後に配置します
- 実装全体(
impl
ブロック内の複数のメソッド)で共有されるジェネリック型パラメータは、impl
キーワードの後に配置します - 特定のメソッドでのみ使用される型パラメータは、そのメソッド名の後に配置します
コード例
例1: 構造体とimplブロックのジェネリクス
2つの値を持つPoint
構造体の例です:
struct Point<X1, Y1> {
x: X1,
y: Y1,
}
impl<X1, Y1> Point<X1, Y1> {
fn new(x: X1, y: Y1) -> Self {
Point { x, y }
}
fn get_x(&self) -> &X1 {
&self.x
}
fn get_y(&self) -> &Y1 {
&self.y
}
}
ここでは、X1
とY1
というジェネリック型パラメータを構造体定義で使用し、同じパラメータをimpl
ブロックでも使用しています。これらのパラメータはimpl
ブロック内のすべてのメソッドで共有され、メソッド間で一貫性のある型として扱われます。
例2: メソッド固有のジェネリクス
次に、特定のメソッドだけで使用するジェネリック型パラメータの例を見てみましょう:
struct Point<X1, Y1> {
x: X1,
y: Y1,
}
impl<X1, Y1> Point<X1, Y1> {
fn mixup<X2, Y2>(self, other: Point<X2, Y2>) -> Point<X1, Y2> {
Point {
x: self.x,
y: other.y,
}
}
}
この例では、mixup
メソッドが別のPoint
インスタンスを引数に取り、新しいPoint
を返します。ここで重要なのは:
-
X1
とY1
は構造体とimplブロック全体で共有される型パラメータ -
X2
とY2
はmixup
メソッド固有の型パラメータで、このメソッドの中でのみ有効
このメソッドは2つの異なるPoint
インスタンスの値を組み合わせて、新しいPoint
インスタンスを作成します。
なぜimplブロックにすべての型パラメータを置けないのか?
どうせならimplブロックで全ての型パラメータを指定しておけばいいと思いませんか?しかし次のようなコードを書くとエラーに遭遇します:
// このコードはコンパイルエラーになります
struct Point<X1, Y1> {
x: X1,
y: Y1,
}
impl<X1, Y1, X2, Y2> Point<X1, Y1> {
fn mixup(self, other: Point<X2, Y2>) -> Point<X1, Y2> {
Point {
x: self.x,
y: other.y,
}
}
}
このコードはコンパイルできません。エラーメッセージは以下のようになります:
error[E0207]: the type parameter `X2` is not constrained by the impl trait, self type, or predicates
--> src/main.rs:6:12
|
6 | impl<X1, Y1, X2, Y2> Point<X1, Y1> {
| ^^ unconstrained type parameter
なぜエラーになるのか?
このエラーが発生する理由は、Rustの型システムの設計に関係しています:
-
impl
ブロックで宣言されたジェネリックパラメータは、「制約される(constrained)」必要があります - 「制約される」とは、そのパラメータが実装対象の型(この場合は
Point<X1, Y1>
)、トレイト境界、またはwhere節のいずれかで使用されることを意味します -
X2
とY2
はこれらのいずれにも登場しないので「制約されていない型パラメータ(unconstrained type parameter)」としてエラーになっています
エラーメッセージが指摘している通り、X2
とY2
はimpl
ブロックのどこにも「束縛」されていません。これらのパラメータは各メソッド呼び出しごとに異なる型を取る可能性があり、impl
ブロックレベルで固定することができないと考えると、理解しやすいかもしれません。
まとめ
Rustにおけるジェネリクスの配置ルールをまとめると:
- 構造体全体で共有される型パラメータは構造体名の後に配置
-
impl
キーワードの後に配置される型パラメータはimpl
ブロック内のすべてのメソッドで共有される - 特定のメソッドでのみ使用される型パラメータは、そのメソッド名の後に配置
この区別を理解することで、Rustのジェネリクスをより効果的に活用できるようになりますし、コードの読解力が向上すると思います!