aboutsummaryrefslogtreecommitdiffhomepage
path: root/content/posts/2021-10-02/rust-where-are-primitive-types-from.adoc
blob: 8fc70b4dbe1d84d2a59a549bc7dab543297b02b9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
= Rust のプリミティブ型はどこからやって来るか
:tags: rust
:description: Rust のプリミティブ型は予約語ではなく普通の識別子である。 \
              どのようにこれが名前解決されるのかを調べた。
:revision-1: 2021-10-02 Qiita から移植

この記事は Qiita から移植してきたものです。 元 URL:
https://qiita.com/nsfisis/items/9a429432258bbcd6c565

'''''

== 前置き

Rust
において、プリミティブ型の名前は予約語でない。したがって、次のコードは合法である。

[source,rust]
----
#![allow(non_camel_case_types)]
#![allow(dead_code)]

struct bool;
struct char;
struct i8;
struct i16;
struct i32;
struct i64;
struct i128;
struct isize;
struct u8;
struct u16;
struct u32;
struct u64;
struct u128;
struct usize;
struct f32;
struct f64;
struct str;
----

では、普段単に `bool` と書いたとき、この `bool`
は一体どこから来ているのか。rustc のソースを追ってみた。

____
前提知識: 一般的なコンパイラの構造、用語。`rustc` そのものの知識は不要
(というよりも筆者自身がよく知らない)
____

== 調査

調査に使用したソース (調査時点での最新 master)

https://github.com/rust-lang/rust/tree/511ed9f2356af365ad8affe046b3dd33f7ac3c98

どのようにして調べるか。rustc
の構造には詳しくないため、すぐに当たりをつけるのは難しい。

大雑把な構造としては、`compiler` フォルダ以下に `rustc_*`
という名前のクレートが数十個入っている。これがどうやら `rustc`
コマンドの実装部のようだ。

`rustc` はセルフホストされている (= `rustc` 自身が Rust で書かれている)
ので、`bool` や `char`
などで適当に検索をかけてもノイズが多すぎて話にならない。
しかし、お誂え向きなことに `i128`/`u128`
というコンパイラ自身が使うことがなさそうな型が存在するのでこれを使って
`git grep` してみる。

....
$ git grep "\bi128\b" | wc      # i128
     165    1069   15790

$ git grep "\bu128\b" | wc      # u128
     293    2127   26667

$ git grep "\bbool\b" | wc      # cf. bool の結果
    3563   23577  294659
....

165
程度であれば探すことができそうだ。今回は、クレート名を見ておおよその当たりをつけた。

....
$ git grep "\bi128\b"
...
rustc_resolve/src/lib.rs:        table.insert(sym::i128, Int(IntTy::I128));
...
....

`rustc_resolve`
というのはいかにも名前解決を担いそうなクレート名である。該当箇所を見てみる。

[source,rust]
----
/// Interns the names of the primitive types.
///
/// All other types are defined somewhere and possibly imported, but the primitive ones need
/// special handling, since they have no place of origin.
struct PrimitiveTypeTable {
    primitive_types: FxHashMap<Symbol, PrimTy>,
}

impl PrimitiveTypeTable {
    fn new() -> PrimitiveTypeTable {
        let mut table = FxHashMap::default();

        table.insert(sym::bool, Bool);
        table.insert(sym::char, Char);
        table.insert(sym::f32, Float(FloatTy::F32));
        table.insert(sym::f64, Float(FloatTy::F64));
        table.insert(sym::isize, Int(IntTy::Isize));
        table.insert(sym::i8, Int(IntTy::I8));
        table.insert(sym::i16, Int(IntTy::I16));
        table.insert(sym::i32, Int(IntTy::I32));
        table.insert(sym::i64, Int(IntTy::I64));
        table.insert(sym::i128, Int(IntTy::I128));
        table.insert(sym::str, Str);
        table.insert(sym::usize, Uint(UintTy::Usize));
        table.insert(sym::u8, Uint(UintTy::U8));
        table.insert(sym::u16, Uint(UintTy::U16));
        table.insert(sym::u32, Uint(UintTy::U32));
        table.insert(sym::u64, Uint(UintTy::U64));
        table.insert(sym::u128, Uint(UintTy::U128));
        Self { primitive_types: table }
    }
}
----

これは初めに列挙したプリミティブ型の一覧と一致している。doc comment
にも、

____
All other types are defined somewhere and possibly imported, but the
primitive ones need special handling, since they have no place of
origin.
____

とある。次はこの struct
の使用箇所を追う。追うと言っても使われている箇所は次の一箇所しかない。なお説明に不要な箇所は大きく削っている。

[source,rust]
----
    /// This resolves the identifier `ident` in the namespace `ns` in the current lexical scope.
    /// (略)
    fn resolve_ident_in_lexical_scope(
        &mut self,
        mut ident: Ident,
        ns: Namespace,
        // (略)
    ) -> Option<LexicalScopeBinding<'a>> {
        // (略)

        if ns == TypeNS {
            if let Some(prim_ty) = self.primitive_type_table.primitive_types.get(&ident.name) {
                let binding =
                    (Res::PrimTy(*prim_ty), ty::Visibility::Public, DUMMY_SP, ExpnId::root())
                        .to_name_binding(self.arenas);
                return Some(LexicalScopeBinding::Item(binding));
            }
        }

        None
    }
----

関数名や doc comment が示している通り、この関数は識別子 (identifier,
ident) を現在のレキシカルスコープ内で解決 (resolve) する。
`if ns == TypeNS` のブロック内では、`primitive_type_table` (上記の
`PrimitiveTypeTable::new()` で作られた変数) に含まれている識別子
(`bool`、`i32` など)
かどうか判定し、そうであればそれに紐づけられたプリミティブ型を返している。

なお、`ns` は「名前空間」を示す変数である。Rust
における名前空間はC言語におけるそれとほとんど同じで、今探している名前が関数名/変数名なのか型なのかマクロなのかを区別している。この
`if`
は、プリミティブ型に解決されるのは型を探しているときだけだ、と言っている。

重要なのは、これが `resolve_ident_in_lexical_scope()`
の最後に書かれている点である。つまり、最初に挙げたプリミティブ型の識別子は、「名前解決の最終段階で」、「他に同名の型が見つかっていなければ」プリミティブ型として解決される。

動作がわかったところで、例として次のコードを考える。

[source,rust]
----
#![allow(non_camel_case_types)]

struct bool;

fn main() {
    let _: bool = bool;
}
----

ここで `main()` の `bool` は `struct bool`
として解決される。なぜなら、プリミティブ型の判定をする前に `bool`
という名前の別の型が見つかるからだ。

== まとめ

Rust
のプリミティブ型は予約語ではない。名前解決の最終段階で特別扱いされ、他に同名の型が見つかっていなければ対応するプリミティブ型に解決される。