Rust では"文字列"は文字列でない
Rust を触り始めて最初に驚いたこと。
というより、最近よく利用している TypeScript の経験をもとに勝手な先入観をもって始めたがゆえに感じた言語のギャップのお話です。
文字列とは
ソースコード内にハードコードした文字列は・・・
ソースコードの中に直接記述した文字列は &str
、つまり str
型の参照になります。
文字列だから String
型と思いきや、そうでは無いです。
let s1: &str = "Hello, World!';
早速、題名回収です。
文字列のデータ型は 2 種類ある
同じ(ような/ように)文字列を扱うにしても、文字列を冠したデータ型は 2 つある。
-
&str
型 -
String
型
言語によっては単にショートハンド・略称であり扱いは一緒みたいな事があったりして、それらを知ってると逆にハマるやつ(1敗)。
Rust では明確に区別されています。
変換するには
&str → String
String
型に変換するには to_string() メソッドをつかいます。
let s2: String = "Hello, World!".to_string();
String → &str
&str
型に変換するには as_str() メソッドをつかいます。受け取り側が明示されていれば、参照渡しするだけでもいけます。
let s2: String = "Hello, World!".to_string();
// s3 は &str になる
let s3 = s2.as_str();
// 型推論が効いて適切な参照の実装が呼ばれる
let s4: &str = &s2;
受け手の型が自明でない場面で &s2
とすると、単に String 型の参照である &String
としてふるまうので注意したい。
メソッドの引数
文字列を受け取るデータ型の違いでみるポイントです。
String 型の場合
最もシンプルに書けそうなのに型エラーが出てハマるやつ(1敗)。
fn say1(name: String) {
println!("Hello, {}!", name);
}
fn main() {
say1("Taro"); // これは NG。型エラーになる
say1("Taro".to_string()); // こうする。
}
&str 型の場合
逆パターンについても。
fn say2(name: &str) {
println!("Hello, {}!", name);
}
fn main() {
say2("Taro");
let taro: String = "Taro".to_string();
say2(taro.as_str()); // こうする。
say2(&taro); // または参照演算子をつかう。
}
「String → &str」の時にみたように、参照演算子を通して推論変換できるのでシンプルに書くこともできます。
参照の変換
便利じゃん!て思いますが、ストレートに読むと String の参照を渡してるようにも見えて「どうして &String が &str で受け取れるの?互換性あるの?」みたいな勘違いにもなりやすい部分です(1敗)。
これは単純な参照ではなくて、コア機能として trait AsMut による参照演算子の定義を String 向けに実装 (演算子のオーバーライド) されてることで型変換も同時に行われています。
参考・謝辞
文字列について、ほげさんがより体系的に詳しくまとめておられます。
これで掘り下げて学ぶことができました。感謝と共に参考リンクにしますので是非。
おわりに
初学の時に、メソッドへの橋渡しで String 型ないしは、その参照を要求されることが多いので、テスト的に直接文字列を引数に与えたら、型違いでダメだとコンパイラに怒られて "test".to_string()
のように変換を求められて、、。「ええ。。。尾ひれの方が長いやん、毎回やるの?これ」みたいに驚いたものです。
たかが文字列と侮るなかれ。僕はいろんなパターンで3敗しました。
チュートリアルを読み進めて発展させようとしたら、躓いたところをメインに取り上げました。
今は C++ 言語での string と char* の関係に似ているなと思い、腑に落ちています。
よき Rust ライフにつながれば。
それではまた!
Discussion