[WIP] &str vs String in Rust

 

TL;DR

  • &str はプリミティブ型で、String は標準ライブラリが提供する型

&str

  • &str はプリミティブ型
  • &str はスライスの一種
    • つまりコレクションの一部への参照(&[u8]
    • preallocated read only memoryにある実データの開始位置へのポインタとデータ長を持つ
  • 別称・文字列リテラル
  • 不変( read only & cannot resize )
    • したがって、コンパイル時にどのようなデータであるか判明するので、バイナリファイルにハードコードされることになり、読み出しが高速になる
  • スタックに積まれる
 

String

  • ヒープにメモリを確保する
    • Vec<u8> として保持される
  • コンパイル時にサイズが不明なテキストを保持することができる
  • ヒープにある実データへのポインタとデータ長、そして確保したヒープの許容量(ヒープの再割り当て無しで格納できるUTF-8バイト長)をスタックに持つ
 
ref:
多コピーの原罪
Rust 分かんないッピ ・ε・ Rust の文字列周りのプラクティスを基礎から勉強してみようと思って勉強したのでそのときのメモをまとめます。 なぜ Rust の文字列周りの型があんなに大変なことになっているかは、Rust のメモリモデルと Copy の仕組みを学ぶことで理解できた気がしたので、メモリの話から始めます。 FYI: https://www.reddit.com/r/rustjerk/comments/ovx0uq/the_two_major_ways_rust_changed_my_life/ まずは GC からです。 GC とは Wikipedia をそのまま引用すると ガベージコレクション(英: garbage collection; GC)とは、コンピュータプログラムが動的に確保したメモリ領域のうち、不要になった領域を自動的に解放する機能である。 とのことです。 FYI: https://ja.wikipedia.org/wiki/%E3%82%AC%E3%83%99%E3%83%BC%E3%82%B8%E3%82%B3%E3%83%AC%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3 GC が必要とされる背景としては、プログラマがメモリ管理を気にせずにコードを書けるようにしたいからです。 もしプログラマがメモリの管理でミスをすると、 メモリリーク メモリを解放せずにポインタだけを消すなどした場合、プログラムは実行中にどんどん不要なメモリを貯めていくこととなり、利用可能なメモリがなくなる。 メモリの二重解放 手動でメモリを解放するも、ダングリングポインタを放置していると間違えて再度メモリを解放するかもしれない。その場合は未定義動作を引き起こす といった問題を起こしてしまうかもしれません。 そこでメモリの管理を信頼できる機能で機械的に行います。 それが GC であり、GC があればメモリ管理でバグを埋め込んでしまうことを防げます。 そして今世の中で使われている言語は GC を持つことが多いです。 しかし、Rust には GC がありません。 Rust は GC
Rustの&strとStringについて整理したかった - パケット畑でつかまえて
Rustを触っていて今までなぁなぁで&strとStringを使っており、少し調べて違いを整理したかったのでこの記事を書いてみました。 間違った事を書いている可能性もあるので参考にする際は十分にご注意ください。 では、&strとStringを知る上でRustのスライスという概念とメモリのスタック領域とヒープ領域の知識が必要なのでまずはそこから書いていきます。 Rustにはスライスという概念があります。 スライスとはコレクション内の一連の要素を参照したものです コレクションとはRustのデータ構造の一つで、複数の値を含むことのできる型です。ヒープに確保され、実行時に伸縮可能なのが特徴です。 ベクタや 文字列(String)や HashMap などがコレクションに該当します。 スライスは2つのデータからできており、一つは 開始地点のポインタ もう一つは そのスライスの長さ(length) です。これらはスタックに積まれます。 例えば以下のようなコード があったとして、メモリモデルをイメージすると以下のようになります。 今回のスライス worldは先頭が'w'なのでwがあるポインタを指しています。 次はヒープとスタックについてです。 メモリの領域にはヒープ領域とスタック領域という領域があります。他にもいくつか領域はありますが今回はヒープとスタックに絞ります。 ヒープはサイズが可変なデータを保持するためのメモリ領域です。ヒープにデータを置く際、プログラムはOSを訪ねてOSは十分なサイズの空のメモリ領域を見つけ出し、その領域を使用中としてその領域のポインタをプログラムに返します。ポインタはメモリ上のアドレスです。これらの一連の流れをアロケートすると言います。 ポインタ自体は固定サイズなので後述するスタックに積まれます。 ですからヒープのデータが必要になる際は、スタックにあるポインタを追う必要があります。 ヒープへのデータアクセスはスタックに比べ低速です。 スタックはデータ構造がシンプルなのに対し、ヒープはポインタを追う必要があるためです。 スタックはLIFO (Last In, First Out)のデータ構造をした実行時に利用できるメモリ領域です。 LIFO とはLast In, First Outの略で、日本語で言うと後入れ先出しと言います。箱の中に本を縦に積み上げていったとしたら取り出す際は上から取り出す(つまり後から積み上げられたやつから取り出す)イメージです。 このデータ構造はとてもシンプルでデータを取りやすいため、スタックのデータアクセスは高速と言われています。 スタックにデータを積み上げる際、データは全て既知で固定なサイズでなければなりません。このこともスタックのデータアクセスが高速だと言われている理由の一つです。 本当に触りの部分しか触れられていませんが、これらを頭に入れて&strとStringについて書いていきます。 Rustには&str型と String型が用意されており、&strはプリミティブ型で String は標準ライブラリが提供する型という大まかな違いがあります。 まずはこの&str について書いていきます。 &str は先程書いた通りプリミティブ型に分類されます。 &strはスライスで一種で、文字列 リテラル と呼ばれ、不変でありスタックに積まれます。 不変というのは read only でありリサイズが不可という事です。 文字列 リテラルが不変なお陰で コンパイル 時にどんなデータか判明するので、文字列は最終的なバイナリファイルに直接ハードコードされ非常に高速で効率的です。 そして&strがRustではどういう風にメモリ管理されてるかイメージする上で大事なメモリモデルは以下の画像の通りです。 非常に簡潔かつ分かりやすかったので画像を Yuki Toyodaさんの Rustハンズオン のスライドから拝借しました。 画像の通り文字列 リテラル &strはスタックにpreallocated read only memoryにある実データへのポインタとその長さlengthなどの文字列 リテラル に関するデータを積みます。 &strは不変な文字列なので CLI アプリの--helpで出力されるような実行時に動的に変わらないような文字列を保持したい時とかに使えそうです。 お次は String 型です。 String型は端的に言うとヒープにメモリを確保し、 コンパイル 時にサイズが不明なテキストを保持できる型です。 Stringのメモリモデルは以下のとおりです。また拝借しました。 Stringは3つのパーツで構成されており、 文字列の中身を保持するメモリ領域(ヒープ)へのポインタと ...