🤖

【Rust入門】char、&str、String型の違いについて

2024/08/04に公開
5

Rustに入門した中で、文字(列)の扱いについて少し混乱したのでメモ。
C++の復習にもなりますね。

1. 文字(列)型の種類について

  1. char
    組み込み型(プリミティブ型)。
    単一のUnicode文字を表現でき、スタック上に確保される。

  2. str
    これも組み込み型。
    後述のString型への参照として使用されるので、固定値。別名string slice(文字列スライス)とも呼ばれる。

    通常は参照付きの&strとして使用されがち。
    文字列データ自体は別の場所に存在し、それを参照する。
    string sliceの先頭のバイトへのポインタとその長さから構成される、&[u8]型のスライス[1]

  3. String
    標準ライブラリで提供される型。文字列データ。実態はVec<u8>型で実装されている。
    上記2つと違ってヒープ上に確保され、サイズ変更が可能。

2. char&strの違いについて

シングルクォーテーションかダブルクォーテーションの
どちらで囲んだかによって型推論が変動するので注意。

let a = 'a' // char型
let b = "b" // &str型

また、&strcharへの変換は以下のように実装する。

let s = "Hello, world!";
for c in s.chars() {  // charを取り出すイテレータ.
    println!("{}", c);
}

H
e
l
l
o
,
 
w
o
r
l
d
!

3. &strStringの違いについて

前述のとおり、Stringはヒープで動的に確保された文字列で、strはそれを参照する固定長である。

以下は&strStringに変換する例[2]

let s = "Hello".to_string(); // s: String

また、Stringは所有権を持つ。

4. (余談その1)メモリ上ではどうなってる?

以下のコードを実行した時、内部でどのような事が起こっているか。

let my_str = "hello";
let my_string = String::from(my_str);

L1を実行した時

  • ptr: 文字列データへのポインタ
  • len: 文字列データの長さ

静的メモリに配置されているため、不変。
staticライフタイムを持つ。
変更の必要がない文字列を宣言する場合はこれで十分。

L2を実行した時


先ほどと異なり、ヒープ上に新しく割り当てられている事がわかる。

  • ptr: (ヒープ上に割り当てられた)文字列データへのポインタ
  • len: 文字列データの長さ
  • capacity: 現在割り当てられているメモリ量

5. (余談その2)String型は所有権を持つ

組み込み型(プリミティブな値)の値は、所有権を持たない。
これはCopyトレイトによるもので、ディープコピーの挙動と実質同じと思われる。

1. char型

fn char_example() {
    let c1 = 'A';
    let c2 = c1;  // コピーされる
    println!("c1: {}, c2: {}", c1, c2);  // 両方使用可能
}

2. &str型

fn str_example() {
    let s1 = "Hello";
    let s2 = s1;  // 参照がコピーされる
    println!("s1: {}, s2: {}", s1, s2);  // 両方使用可能
}

3. String型

charstrと異なり、所有権を持つ。

fn string_example() {
    let s1 = String::from("Hello");
    let s2 = s1;  // 所有権が移動する
    // println!("s1: {}", s1);  // コンパイルエラー: s1の所有権はs2に移動している
    println!("s2: {}", s2);  // s2は使用可能

    // String型の借用
    let s3 = String::from("World");
    let s4 = &s3;  // s3を借用
    println!("s3: {}, s4: {}", s3, s4);  // 両方使用可能
}

6. 所感

結局、メモリ上で何が起こっているかを深堀する事が大事だなぁと感じました🧐

参考

脚注
  1. RustのcharとstrとString ↩︎

  2. 文字列(公式Doc) ↩︎

Discussion

白山風露白山風露

また、それを&strが参照するようになっている。

from の引数に渡した &str の変数が書き換えられるようなことはありません。参照先は元のままのはずです

ににゃあににゃあ

白山様、返信遅くなりました。
ご指摘いただき感謝します。
確認し、文言と図を修正しましたので、また機会があれば見ていただけると幸いです。

コメントありがとうございました。

田中田中

白山様で指摘された通り

println!("my_str: {:p}", my_str.as_ptr());       //    my_str: 0x102c3b9d0
println!("my_string: {:p}", my_string.as_ptr()); // my_string: 0x7fb9ca704a50

それぞれ異なるアドレスを指します

ににゃあににゃあ

田中様、返信遅くなりました。
コード添付した上でのご指摘、大変感謝しております。
確認し、文言と図を修正しましたので、もしまた機会があれば見ていただけると幸いです。

まだまだ入門中の身ですので勉強になりました。
コメントありがとうございました。

ににゃあににゃあ

白山さん、田中さんコメントありがとうございます。
(また、返信遅れてしまい申し訳ございませんでした)
後ほど確認し返信させていただきます。