読者です 読者をやめる 読者になる 読者になる

Rustの多相性にかかわる構文

概要: Rustの型多相性の詳しい挙動を調べる前に、まず構文を調べる。

型仮引数

型仮引数のフォーマットは以下の通りである。 (parse_generics)

  • <> で囲った部分に、型仮引数の名前をカンマ区切りで指定する。
  • 末尾のカンマは省略可能である。
  • 型仮引数は0個でもよいが、0個の場合は < > 自体省略できる。
type A = ();
type A<> = ();
type A<X> = ();
type A<X,> = ();
type A<X, Y> = ();
type A<X, Y,> = ();

型仮引数が指定できるのは以下の位置である。

関数とメソッド

fn f<X>() {}
trait Foo {
    fn f<X>() {}
}
impl A {
    fn f<X>() {}
}
impl Foo for A {
    fn f<X>() {}
}

トレイト

trait Foo<X> {}

実装

impl<X> A {}
impl<X> Foo for A {}

型定義(構造体/列挙体/共用体/型別名)

struct A<X> {}
struct A<X>();
enum A<X> {}
union A<X> {}
type A<X> = ();
// 関連型も型別名と似た構文を持つが、こちらは単相であり型引数をとらない。
trait Foo { type B; }
impl Foo for A { type B = (); }

実装と型定義については、型仮引数が使用されていないとエラーになる。具体的には以下の制約がある。

生存期間仮引数

生存期間仮引数のフォーマットは型仮引数と同じ位置に指定する。ただし以下の違いがある。 (parse_generics)

  • 生存期間のため ' のついた識別子を指定する。
  • 生存期間仮引数は、型仮引数よりも前に書く。
type A<'a, X> = ();

生存期間仮引数は、上記の型仮引数と同様、関数・メソッド・トレイト・実装・型定義のいずれの位置にも書くことができる。それに加えて、以下の3つの位置にも書くことができる。 (parse_late_bound_lifetime_defs) 特に最初の2つは高階トレイト境界(HRTB)と呼ばれている。

where節の述語の量化

fn f<F>(f: F) where for<'a> F: Fn(&'a [u8]) -> &'a u8 {}

トレイト境界の量化

fn f<F>(f: F) where F: for<'a> Fn(&'a [u8]) -> &'a u8 {}
fn f<F: for<'a> Fn(&'a [u8]) -> &'a u8>(f: F) {}

関数ポインタとトレイトオブジェクトの量化

// 高階の関数ポインタ型
let x : for<'a> fn(&'a [u8]) -> &'a u8;
// 高階のトレイトオブジェクト
let x : &for<'a> Fn(&'a [u8]) -> &'a u8;

型仮引数と同様、生存期間仮引数が使用されていないとエラーになる場合がある。具体的には以下の制約がある。

  • 構造体/列挙体/共用体が生存期間仮引数に対して双変である場合はエラーとなる。 (check_variances_for_type_defn)
  • 型別名で生存期間仮引数が使用されていなくても特にエラーにならない。 (1.16.0時点)
  • 実装で生存期間仮引数が使用されていなくても特にエラーにならない。 (1.16.0時点)
  • 高階量化で生存期間仮引数が使用されていなくても特にエラーにならない。 (1.16.0時点)

Self

Self は実装対象の型を示すキーワードであり、以下のように使われる。

  • トレイト宣言内では、型引数と似たような振舞いをする。
  • 実装内では、実装対象の型をあらわす省略記法のように振る舞う。

生存期間実引数と型実引数と型束縛

型実引数と生存期間実引数は、通常 ident<..> または ident::<..> の形で指定する。どちらになるかは文脈による。この形の構文は以下の通りである。 (parse_generic_args)

  • 生存期間実引数 'a, 型実引数 (e.g. Vec<u8>), 型束縛 (e.g. X=Vec<u8>) をカンマ区切りで指定する。
  • 末尾のカンマは省略可能である。
  • 実引数は0個でもよいが、0個の場合は ::<> または <> 自体省略できる。
  • 複数ある場合は、生存期間実引数→型実引数→型束縛 の順に書く。
A;
A<>;
A<'a, Vec<u8>, Y=&str>;
A<'a,>;

実引数の数は、仮引数の数と一致していなければならない。ただし、以下の条件では、実引数が丸ごと省略されたものとみなされる。

  • 指定された生存期間実引数が0個のときは、生存期間実引数が丸ごと省略されたものとみなされる。 (check)
  • 以下のような文脈において、指定された型実引数が0個のときは、型実引数が丸ごと省略されたものとみなされる。 (lowering, check)
    • 関数名
    • 構造体/列挙体/共用体の初期化 (構造体やバリアントの名前)
    • 構造体/列挙体のマッチング (構造体やバリアントの名前)

<>とは別に、丸括弧を使って型実引数と型束縛を指定する構文糖衣がある。これは現在の安定板では FnOnce, FnMut, Fn の3つのトレイトに対してのみ使える。詳しくは過去の記事を参照。

  • X(A, B, C) のように書くと、 X<(A, B, C), Output=()> として解釈される。
  • X(A, B, C) -> D のように書くと、 X<(A, B, C), Output=D> として解釈される。
  • ただし、生存期間が省略されている場合、この部分で局所的に解決されるという違いがある。

修飾パス

Self はトレイトにとっては暗黙の型引数のように振る舞う。これに対する実引数を指定するのが <SelfType as Trait> 記法である。

また同種の記法で、Self だけを指定してトレイト側を省略することもできる。 <SelfType> 記法がこれにあたる。これについては過去の記事を参照。

射影

多相なアイテムの中では型仮引数の関連型を参照することができる。これを射影といい、パスと同様に Trait::AssocType, <X as Trait>::AssocType, X::AssocType のように表記する。

関連型自身は単相であるが、これを含んでいるトレイトの型仮引数と Self に応じて変化する。したがて、射影は、トレイトの実引数と Self を受け取り、型を返す部分関数とみなせる。

型の境界指定

型に対して T: Trait + 'a のようにトレイトや生存期間による制約を加えることができる。この構文は以下の通りである。 (parse_ty_param_bounds)

  • 型のあとにコロンを指定し、その先に0個以上の(高階)トレイト境界または生存期間を + で繋いで指定する。末尾の + は省略できる。
  • (高階)トレイト境界には、 for による量化と ? による修飾を加えることができる。順番はこの順だが、同時に指定する意味はない。
  • for による量化は、前述のとおり生存期間のみ指定できる。
  • ?Sized のための特殊な構文である。 ?Sized と書くことで暗黙の Sized 指定を外すことができる。詳しくは過去の記事を参照。

型の境界指定は以下の場面で使うことができる。

型仮引数

fn f<X: Foo>() {}

where節

fn f<X>() where X: Foo {}

トレイト宣言 (スーパートレイト境界)

trait Foo : Bar {}

トレイトオブジェクト。ただし、「先頭はトレイトでなければならない」「先頭以外に指定できるトレイトは Send/Sync のみ」という制約がある。

fn f(_: &(Foo + Send)) {}

where節とスーパートレイト境界

where節は where の後にカンマ区切りで0個以上の型や生存期間の境界指定を書く。末尾のカンマは省略できる。1つも指定するものがないときは where ごと省略できる。

where が書けるのは以下の位置である。

関数とメソッド: 戻り値型の直後

fn f() -> () where {}
trait Foo {
    fn f() -> () where;
}
impl Foo for A {
    fn f() -> () where {}
}

トレイト: スーパートレイト境界の直後

trait Foo : Bar where {}

実装: トレイト名とSelf型の直後

impl A where {}
impl Foo for A where {}

タプル構造体: メンバ一覧の後

struct A where;
struct A(u32) where;

構造体(タプル構造体以外)/列挙体/共用体/型別名: 型仮引数リストの後

struct A where {}
enum A where {}
union A where {}
type A where = ();

前述のように、 where 節の各述語を for で量化できる。

fn f<F>(f: F) where for<'a> F: Fn(&'a [u8]) -> &'a u8 {}

型仮引数とスーパートレイト境界に指定されている型境界は where の構文糖衣とみなせる。

fn f<X: Foo>() {}
fn f<X>() where X: Foo {}
trait Foo : Bar {}
trait Foo where Self: Bar {}

生存期間の境界指定

型と同様に、生存期間にも境界指定がある。 'a: 'b + 'c のように、0個以上の生存期間を + で繋げて書く。末尾の + は省略できる。 (parse_lt_param_bounds)

fn f<'a, 'b: 'a>() {}
fn f<'a, 'b>() where 'b: 'a {}

1.16.0では末尾に + をつけられるが、その部分のコメントと矛盾しているためおそらくバグである。

生存期間の境界指定は、生存期間仮引数の位置か、 where 節に書くことができる。

等式述語

where 節には等式制約を書くための構文があるが、パーサーによる実装のみで他は全く未サポートである。2つの型を =/== で結べばよいが、 === のどちらが採用されるかも不明のため、現在のパーサーはいずれもサポートしている。

fn f<X: Foo>() where X::X = () {} // unsupported, but parsable
fn f<X: Foo>() where X::X == () {} // unsupported, but parsable

まとめ

Rustの多相性を正確に理解するにはさまざまな要素を把握する必要がある。今回はまず、「何が書かれうるか」を全体的に把握するために、関係する構文を洗い出してみた。