web-dev-qa-db-ja.com

Rustの `String`と` str`の違いは何ですか?

RustにStringstrがあるのはなぜですか? Stringstrの違いは何ですか? Stringの代わりにstrを使用するのはいつですか?それらの1つは非推奨になっていますか?

279
Daniel Fath

Stringは、Vecのような動的ヒープ文字列型です。文字列データを所有または変更する必要がある場合に使用します。

strは不変です1 メモリ内のどこかにある動的な長さのUTF-8バイトのシーケンス。サイズは不明であるため、ポインタの背後でしか処理できません。これは、strが最も一般的であることを意味します2 &strとして表示されます。通常は「文字列スライス」または単に「スライス」と呼ばれるUTF-8データへの参照です。 スライス は一部のデータの単なるビューであり、そのデータはどこにでもあります。

  • 静的ストレージ:文字列リテラル"foo"&'static strです。データは実行可能ファイルにハードコーディングされ、プログラムの実行時にメモリにロードされます。
  • 割り当てられたヒープ内StringString&strビューへの逆参照Stringのデータの。
  • スタック上:例以下は、スタックに割り当てられたバイト配列を作成し、 &strとしてのデータのビュー を取得します。

    use std::str;
    
    let x: &[u8] = &[b'a', b'b', b'c'];
    let stack_str: &str = str::from_utf8(x).unwrap();
    

要約すると、所有する文字列データが必要な場合はString(他のスレッドに文字列を渡す、実行時に文字列を作成するなど)を使用し、文字列のビューのみが必要な場合は&strを使用します。

これは、ベクトルVec<T>とスライス&[T]の関係と同一であり、一般的な型の値によるTと参照による&Tの関係に似ています。


1 strは固定長です。末尾を超えてバイトを書き込んだり、無効なバイトを末尾に残したりすることはできません。 UTF-8は可変幅エンコーディングであるため、多くの場合、これによりすべてのstrsが事実上不変になります。一般に、変更には以前よりも多いまたは少ないバイトを書き込む必要があります(たとえば、a(1バイト)をä(2+バイト)に置き換えるには、strにより多くのスペースが必要です)。ある場所で&strを変更できる特定のメソッドがあり、ほとんどは make_ascii_uppercase のようなASCII文字のみを処理するものです。

2 動的サイズの型 Rust 1.2以降、UTF-8バイトをカウントした一連の参照にRc<str>などを許可します。 Rust 1.21では、これらのタイプを簡単に作成できます。

341
huon

私にはC++のバックグラウンドがあり、C++の用語でString&strについて考えることは非常に便利です。

  • Rust Stringstd::stringのようなものです。メモリを所有し、メモリを管理するという汚い仕事をします。
  • Rust &strchar*に似ています(ただし、もう少し洗練されています)。 std::stringの内容へのポインターを取得できるのと同じ方法で、チャンクの先頭を示します。

どちらかが消えますか?そうは思わない。それらは2つの目的を果たします。

Stringはバッファを保持し、非常に実用的です。 &strは軽量で、文字列を「見る」ために使用する必要があります。新しいメモリを割り当てることなく、チャンクを検索、分割、解析、さらには置き換えることができます。

&strは、何らかの文字列リテラルを指すことができるため、Stringの中を見ることができます。次のコードでは、リテラル文字列をString管理メモリにコピーする必要があります。

let a: String = "hello Rust".into();

次のコードでは、コピーせずにリテラル自体を使用できます(ただし、読み取り専用)

let a: &str = "hello Rust";
55
Luis Ayuso

str は、&strとしてのみ使用され、文字列スライス、UTF-8バイト配列への参照です。

String は、以前は~strであったもので、成長可能な所有UTF-8バイト配列です。

36
Chris Morgan

実際には完全に異なります。まず、strは型レベルのものにすぎません。いわゆる動的サイズ型(DST)であるため、型レベルでのみ推論できます。 strが占めるサイズは、コンパイル時に知ることができず、ランタイム情報に依存します。コンパイラーはコンパイル時に各変数のサイズを知る必要があるため、変数に格納できません。 strは概念的にはu8バイトの単なる行であり、有効なUTF-8を形成することを保証します。行の大きさは?実行時まで誰も知らないため、変数に保存できません。

興味深いのは、実行時に&strまたはBox<str>のようなstrへの他のポインターdoesが存在することです。これは、いわゆる「ファットポインター」です。余分な情報(この場合はポイントしているもののサイズ)を持つポインターなので、2倍の大きさです。実際、&strStringに非常に近い(ただし、&Stringには近くない)。 &strは2ワードです。 strの最初のバイトへのポインターと、strの長さを示す別の数値。

言われたことに反して、strは不変である必要はありません。 strへの排他ポインタとして&mut strを取得できる場合、それを変更できます。また、それを変更するすべての安全な関数は、UTF-8制約が維持されることを保証します。制約は真であり、それをチェックしません。

では、Stringとは何ですか? three words; 2つは&strの場合と同じですが、ヒープ上のstrバッファーの容量である3番目のWordを追加します。常にヒープ上に(strは必ずしもヒープ上にあるとは限りません) 。 String基本的にはowns a strのように;それを制御し、サイズを調整し、適切と判断したときに再割り当てすることができます。したがって、Stringは、strよりも&strに近いと言われています。

もう1つはBox<str>です。これはstrも所有し、そのランタイム表現は&strと同じですが、&strとは異なりstrも所有しますが、容量がわからないためサイズを変更できません。したがって、基本的にBox<str>は固定長のStringサイズを変更することはできません(サイズを変更する場合は、いつでもStringに変換できます)。

[T]Vec<T>の間には、UTF-8制約がなく、サイズが動的でない任意の型を保持できることを除いて、非常に類似した関係が存在します。

型レベルでのstrの使用は、主に&strで一般的な抽象化を作成することです。特性を簡単に記述できるように、型レベルで存在します。理論的には、strは型として存在する必要はなく、&strのみでしたが、それは汎用的なコードを大量に記述する必要があることを意味します。

&strは、コピーすることなくStringの複数の異なるサブストリングを持つことができるために非常に便利です。前述のように、Stringownsヒープ上のstrであり、Stringのサブストリングを新しいStringでのみ作成できる場合、Rustのすべてがコピーできるためメモリの安全性に対処する所有者は1人だけです。したがって、たとえば、文字列をスライスできます。

let string: String   = "a string".to_string();
let substring1: &str = &string[1..3];
let substring2: &str = &string[2..4];

同じ文字列の2つの異なるサブストリングstrsがあります。 stringは、ヒープ上の実際の完全なstrバッファーを所有するものであり、&strサブストリングはヒープ上のそのバッファーへの単なるポインターです。

5
Zorf

C#およびJavaの場合:

  • Rust 'String === StringBuilder
  • Rustの&str ===(不変)文字列

&strは、変更できないJava/C#のインターンされた文字列のような、文字列のビューと考えるのが好きです。新しい文字列を作成するだけです。

0
Squirrel

std::Stringは、単にu8のベクトルです。その定義は source code にあります。ヒープに割り当てられ、拡張可能です。

#[derive(PartialOrd, Eq, Ord)]
#[stable(feature = "Rust1", since = "1.0.0")]
pub struct String {
    vec: Vec<u8>,
}

strはプリミティブ型で、string sliceとも呼ばれます。文字列スライスのサイズは固定されています。 let test = "hello world"のようなリテラル文字列には、&'static str型があります。 testは、この静的に割り当てられた文字列への参照です。 &strは変更できません。たとえば、

let mut Word = "hello world";
Word[0] = 's';
Word.Push('\n');

strには可変スライス&mut strがあります。例:pub fn split_at_mut(&mut self, mid: usize) -> (&mut str, &mut str)

let mut s = "Per Martin-Löf".to_string();
{
    let (first, last) = s.split_at_mut(3);
    first.make_ascii_uppercase();
    assert_eq!("PER", first);
    assert_eq!(" Martin-Löf", last);
}
assert_eq!("PER Martin-Löf", s);

ただし、UTF-8を少し変更するとバイト長が変更される可能性があり、スライスはリファレントを再割り当てできません。

0
Aperion

簡単に言えば、Stringはヒープに格納されるデータ型で(Vecと同様)、その場所にアクセスできます。

&strはスライスタイプです。つまり、ヒープ内の既に存在するStringへの参照にすぎません。

&strは実行時に割り当てを行いません。そのため、メモリ上の理由から、Stringではなく&strを使用できます。ただし、&strを使用する場合は、明示的なライフタイムを処理する必要がある場合があることに注意してください。

0
00imvj00