Skip to content

Mitaka Club

Rust AST 入門

これは、簡単な Rust の AST をざっと読めるようになる、もしくは該当のドキュメントを読めるようになる、といったことを目的としたものである。入門といっても網羅的なものではなく、雰囲気を掴んでほしいという意味合いで書いたものであって細かいところまでは説明しないつもりである。なお Rust には型などより多くの情報も含む HIR などいくつかの AST があるが、ここではコンパイル時に最初に作られる AST についてのみふれる。

プログラミング言語で一般的に述べられる AST 自体についてはここでは説明しないので他を参照するとよい。有用そうなもののリンクは以下の通りである。

また、AST を確認するというと AST explorer も有用だが、 AST explorer では syn が使われているようで、自分はこれをさわったことがないのでここではふれない。似たようなものだとは思うが。

これより以下では全て下記バージョンで確認をしているが、参照先のドキュメントは nightly のもので変わりうることがあるのでそこはごめんなさいということで。

rustc -V
rustc 1.59.0-nightly (efec54529 2021-12-04)

AST を見てみよう

ここでは Rust で生成される AST をどうやって確認するかを説明する。

サンプルコードとして以下を考える。

fn main() {
    let x = 5;
}

rustc には unstable compiler option というのがあって、これは rustc --help で確認できるのだが、unstable compiler option を rustc -Z help でさらに確認すると unpretty というオプションがあることがわかる。

    (snip)
    -Z                                unpretty=val -- present the input source, unstable (and less-pretty) variants;
        `normal`, `identified`,
        `expanded`, `expanded,identified`,
        `expanded,hygiene` (with internal representations),
        `everybody_loops` (all function bodies replaced with `loop {}`),
        `ast-tree` (raw AST before expansion),
        `ast-tree,expanded` (raw AST after expansion),
        `hir` (the HIR), `hir,identified`,
        `hir,typed` (HIR with types for each node),
        `hir-tree` (dump the raw HIR),
        `mir` (the MIR), or `mir-cfg` (graphviz formatted MIR)

ここで上記のサンプルコードに対して rustc -Zunpretty=ast-tree src/main.rs とコマンドを実行することでで以下のような AST が得られる。

Crate {
    attrs: [],
    items: [
        Item {
            attrs: [],
            id: NodeId(4294967040),
            span: src/main.rs:1:1: 3:2 (#0),
            vis: Visibility {
                kind: Inherited,
                span: no-location (#0),
                tokens: None,
            },
            ident: main#0,
            kind: Fn(
                Fn {
                    defaultness: Final,
                    generics: Generics {
                        params: [],
                        where_clause: WhereClause {
                            has_where_token: false,
                            predicates: [],
                            span: src/main.rs:1:10: 1:10 (#0),
                        },
                        span: src/main.rs:1:8: 1:8 (#0),
                    },
                    sig: FnSig {
                        header: FnHeader {
                            unsafety: No,
                            asyncness: No,
                            constness: No,
                            ext: None,
                        },
                        decl: FnDecl {
                            inputs: [],
                            output: Default(
                                src/main.rs:1:11: 1:11 (#0),
                            ),
                        },
                        span: src/main.rs:1:1: 1:10 (#0),
                    },
                    body: Some(
                        Block {
                            stmts: [
                                Stmt {
                                    id: NodeId(4294967040),
                                    kind: Local(
                                        Local {
                                            id: NodeId(4294967040),
                                            pat: Pat {
                                                id: NodeId(4294967040),
                                                kind: Ident(
                                                    ByValue(
                                                        Not,
                                                    ),
                                                    x#0,
                                                    None,
                                                ),
                                                span: src/main.rs:2:9: 2:10 (#0),
                                                tokens: None,
                                            },
                                            ty: None,
                                            kind: Init(
                                                Expr {
                                                    id: NodeId(4294967040),
                                                    kind: Lit(
                                                        Lit {
                                                            token: Lit {
                                                                kind: Integer,
                                                                symbol: "5",
                                                                suffix: None,
                                                            },
                                                            kind: Int(
                                                                5,
                                                                Unsuffixed,
                                                            ),
                                                            span: src/main.rs:2:13: 2:14 (#0),
                                                        },
                                                    ),
                                                    span: src/main.rs:2:13: 2:14 (#0),
                                                    attrs: ThinVec(
                                                        None,
                                                    ),
                                                    tokens: None,
                                                },
                                            ),
                                            span: src/main.rs:2:5: 2:15 (#0),
                                            attrs: ThinVec(
                                                None,
                                            ),
                                            tokens: None,
                                        },
                                    ),
                                    span: src/main.rs:2:5: 2:15 (#0),
                                },
                            ],
                            id: NodeId(4294967040),
                            rules: Default,
                            span: src/main.rs:1:11: 3:2 (#0),
                            tokens: None,
                            could_be_bare_literal: false,
                        },
                    ),
                },
            ),
            tokens: None,
        },
    ],
    span: src/main.rs:1:1: 3:2 (#0),
    is_placeholder: None,
}

一通り見ていこう

上記で AST を確認できるようになった。ここでは生成された AST とドキュメントを照らし合わせながら読んでいく方法の一例について説明する。ただし、細かいところまでは説明しない。

流れとしては、トップレベルの Crate から読んでいき、実際のコードにある fn main {}let x = 5; が AST 上のどこに現れるかについて通して見ていくことにする。

まずはトップレベルの Crate について見てみよう。定義は以下の通りである。

pub struct Crate {
    pub attrs: Vec<Attribute>,
    pub items: Vec<P<Item>>,
    pub span: Span,
    pub is_placeholder: Option<NodeId>,
}

これは現在作業している crate を示している。いろいろあるが全て見るのは面倒なので重要なところだけ確認したく items に注目する。ということで、次に Item を見てみよう。

pub struct Item<K = ItemKind> {
    pub attrs: Vec<Attribute>,
    pub id: NodeId,
    pub span: Span,
    pub vis: Visibility,
    pub ident: Ident,
    pub kind: K,
    pub tokens: Option<LazyTokenStream>,
}

ItemCrate を構成する要素であり、 mod, use, fn といった宣言などが Item となる。詳細な説明については https://doc.rust-lang.org/reference/items.html を見てもらいたい。

ここでこのサンプルコードにおける AST で Crate にただ一つ含まれる Item の種類が何か知りたいので kind を確認する。kindItemKind として定義されており、今回は fn main が唯一コードに含まれるので関数 Fn(Box<Fn>) である。

次に Fn を見てみよう。

pub struct Fn {
    pub defaultness: Defaultness,
    pub generics: Generics,
    pub sig: FnSig,
    pub body: Option<P<Block>>,
}

ここでも色々あるが、関数の中に入りたいので body を見ていく。関数の処理が宣言されない場合もあるようでここでは Option<P<Block>> と定義されており、次に Block を見ていくことにする。

pub struct Block {
    pub stmts: Vec<Stmt>,
    pub id: NodeId,
    pub rules: BlockCheckMode,
    pub span: Span,
    pub tokens: Option<LazyTokenStream>,
    pub could_be_bare_literal: bool,
}

上記と同様にメインとなる stmtsVec<Stmt> と定義されているので次は Stmt を見ていく。

pub struct Stmt {
    pub id: NodeId,
    pub kind: StmtKind,
    pub span: Span,
}

ここでの statement は実際のコードでは let x = 5; のことである。let で変数に値を代入しているところである。このため ItemKind のときと同様に StmtKind を確認すると、ここでは Local が該当するとわかる。

pub struct Local {
    pub id: NodeId,
    pub pat: P<Pat>,
    pub ty: Option<P<Ty>>,
    pub kind: LocalKind,
    pub span: Span,
    pub attrs: AttrVec,
    pub tokens: Option<LazyTokenStream>,
}

ドキュメントに書いてある Local represents a let statement, e.g., let <pat>:<ty> = <expr>;. を見てもらえると直感的でわかりやすいと思う。つまり実際のコードにおける let x = 5; との対応を考えれば良いので、<pat>x<ty> は今回はなし、<expr>5 と対応することがわかる。では次は Pat を見てみよう。

pub struct Pat {
    pub id: NodeId,
    pub kind: PatKind,
    pub span: Span,
    pub tokens: Option<LazyTokenStream>,
}

同様にして PatKind を確認するとここでは単なる変数宣言なので Ident に該当する。また、AST でも該当の部分を確認すると変数宣言した x が確認できる。

次に <expr> に該当する部分だが Local の定義に expr はなくここでは LocalKindを確認する。これもドキュメントを見ると直感的でわかりやすいが Init(P<Expr>) が今回のものに該当し Expr を見ればいい。

pub struct Expr {
    pub id: NodeId,
    pub kind: ExprKind,
    pub span: Span,
    pub attrs: AttrVec,
    pub tokens: Option<LazyTokenStream>,
}

ExprStmt の詳細ついては https://doc.rust-lang.org/reference/statements-and-expressions.html を見てほしい。

5 に該当する ExprKind を探すといい。ここでは literal である Lit

pub struct Lit {
    pub token: Lit,
    pub kind: LitKind,
    pub span: Span,
}

繰り返しばかりで少し面倒だがだいたい XxxKind のところを見れば大まかな構造がわかると思う。LitKind を確認する。

pub enum LitKind {
    Str(Symbol, StrStyle),
    ByteStr(Lrc<[u8]>),
    Byte(u8),
    Char(char),
    Int(u128, LitIntType),
    Float(Symbol, LitFloatType),
    Bool(bool),
    Err(Symbol),
}

ここでは単なる 5 なので Int となる。

終わりに

これは、元々は Clippy についていろいろ書きたかったのだが、その準備として Rust の AST について説明しなきゃと思い書いていた文章だった。