この投稿は、プログラムをコンパイルするために
to_string()
を使用する必要性に混乱しているすべての人々に捧げられています。 そして、Rustが
String
と
&str
2つの文字列型を持っている理由の問題に光を当てたいと思い
&str
。
文字列を受け入れる関数
文字列を受け入れるインターフェイスを作成する方法について説明します。 私はハイパーメディアの大ファンであり、ユーザーフレンドリーなインターフェースの作成に情熱を注いでいます。
String
を取るメソッドから始めましょう。 この検索により、
std::string::String
型が得られますが、これは最初は悪くありません。
fn print_me(msg: String) { println!(": {}", msg); } fn main() { let message = ", "; print_me(message); }
コンパイルエラーが発生します。
expected `collections::string::String`, found `&'static str`
&str
ような文字列リテラル
&str
String
互換性
&str
ないことが
&str
。 コンパイルが成功するように、
message
変数の型を
String
に変更する必要があり
message
let message = ", ".to_string();
。 動作しますが、
clone()
を使用して所有権継承エラーを修正するようなものです。
print_me
引数
print_me
を
&str
に変更する3つの理由を次に
print_me
&str
。
-
&
記号は参照タイプを示します。 つまり、変数を貸し出します。print_me
が変数のprint_me
すると、所有権は元の所有者に戻ります。message
変数の所有権を関数に転送する正当な理由がない限り、借入を使用する必要があります。 - リンクを使用する方が効率的です。
message
String
を使用すると、プログラムは値をコピーする必要があります。&str
などのリンクを使用する場合、コピーは発生しません。 -
String
型は、Deref
と型キャストを使用して魔法のように&str
に変換でき&str
。 例は、この点をはるかに改善します。
参照解除キャストの例
この例では、行は4つの異なる方法で作成され、それらはすべて
print_me
関数で機能します。 このすべてが機能する主な点は、参照による値の転送です。
owned_string
自身の文字列を
String
として渡す代わりに、
&String
ポインタとして渡します。 コンパイラは、
&String
&str
が
&str
を取る関数に
&String
渡されることを認識すると、
&String
を
&str
にキャストし
&str
。 通常のアトミックリンクカウンターで文字列を使用する場合、まったく同じ変換が使用されます。
string
変数はすでに参照されているため、
print_me(string)
呼び出すときに
&
を使用する必要はありません。 この知識があれば、コードで
.to_string()
を常に呼び出す必要はなくなりました。
fn print_me(msg: &str) { println!("msg = {}", msg); } fn main() { let string = ", "; print_me(string); let owned_string = ", ".to_string(); // String::from_str(", ") print_me(&owned_string); let counted_string = std::rc::Rc::new(", ".to_string()); print_me(&counted_string); let atomically_counted_string = std::sync::Arc::new(", ".to_string()); print_me(&atomically_counted_string); }
また、
Vec
ベクトルなどの他のタイプで逆参照キャストを使用することもできます。 それでも、
String
は8バイト文字の単なるベクトルです。 強制参照の詳細については、「 Rust Programming Language 」( 英語 ) を参照してください 。
構造の使用
この時点で、不必要な
to_string()
呼び出しはすでにないはずです。 ただし、構造体を使用すると問題が発生する場合があります。 利用可能な知識を使用して、次の構造を作成できます。
struct Person { name: &str, } fn main() { let _person = Person { name: "Herman" }; }
次のエラーが表示されます。
<anon>:2:11: 2:15 error: missing lifetime specifier [E0106] <anon>:2 name: &str,
Rustは、
Person
が
name
参照を生き残ることができないことを確認しようとします。
Person
name
生き延びた場合、プログラムがクラッシュするリスクがあります。 Rustの主な目標は、これを防ぐことです。 このコードをコンパイルしてみましょう。 Rustがセキュリティを提供できるように、 ライフタイム ( 英語 )またはスコープを指定する必要があります。 通常、ライフタイムは
'a
と呼ばれます。 そのような伝統がどこから来たのかはわかりませんが、私たちはそれに従います。
struct Person { name: &'a str,' } fn main() { let _person = Person { name: "Herman" }; }
コンパイルしようとすると、次のエラーが表示されます。
<anon>:2:12: 2:14 error: use of undeclared lifetime name `'a` [E0261] <anon>:2 name: &'a str,
考え直してみましょう。
Person
構造は
name
フィールドを超えてはいけないという考えをRustコンパイラに何らかの形で伝えたいことがわかっています。 したがって、
Person
構造体の有効期間を宣言する必要があります。 短い検索により、ライフタイムを宣言するための構文
<'a>
導かれます。
struct Person<'a> { name: &'a str, } fn main() { let _person = Person { name: "Herman" }; }
コンパイルします! 通常、構造体にいくつかのメソッドを実装します。
greet
メソッドを
Person
クラスに追加しましょう。
struct Person<'a> { name: &'a str, } impl Person { fn greet(&self) { println!(", {}", self.name); } } fn main() { let person = Person { name: "Herman" }; person.greet(); }
次のエラーが表示されます。
<anon>:5:6: 5:12 error: wrong number of lifetime parameters: expected 1, found 0 [E0107] <anon>:5 impl Person {
Person
構造には有効期間パラメーターがあるため、実装にもそれが必要です。 次のように
Person
実装でライフタイムを宣言しましょう:
impl Person<'a> {
。 残念ながら、今ではこのような奇妙なコンパイルエラーが発生します。
<anon>:5:13: 5:15 error: use of undeclared lifetime name `'a` [E0261] <anon>:5 impl Person<'a> {
ライフタイムを宣言するには 、次のように
impl
直後にライフタイムを指定する必要があります
impl<'a> Person {
。 もう一度コンパイルすると、エラーが発生します。
<anon>:5:10: 5:16 error: wrong number of lifetime parameters: expected 1, found 0 [E0107] <anon>:5 impl<'a> Person {
すでにより明確です。 このような実装の
Person
構造の説明に有効期間パラメーターを追加しましょう
impl<'a> Person<'a> {
。 これでプログラムがコンパイルされます。 完全な作業コードは次のとおりです。
struct Person<'a> { name: &'a str, } impl<'a> Person<'a> { fn greet(&self) { println!(", {}", self.name); } } fn main() { let person = Person { name: "Herman" }; person.greet(); }
構造体の文字列または&str
ここでの質問は、いつ
String
を使用する価値があるのか、および構造体の
&str
するのかということです。 つまり、構造内の別の型への参照をいつ使用する必要がありますか? 構造が変数の所有権を必要としない場合は、リンクを使用する必要があります。 意味が少しぼやけている場合があるため、この質問に答えるためにいくつかのルールを使用します。
- 構造外で変数を使用する必要がありますか? 少し工夫された例を次に示します。
struct Person { name: String, } impl Person { fn greet(&self) { println!(", {}", self.name); } } fn main() { let name = String::from_str("Herman"); let person = Person { name: name }; person.greet(); println!(" {}", name); // move error }
ここでは、変数を構造体に配置する前に変数を使用する必要があるため、リンクを使用する必要があります。 実際の例はrustc_serializeです。
Encoder
構造は、
std::fmt::Write
を実装する
writer
変数を所有する必要がないため、借用のみが使用されます。 実際、
String
は
Write
実装しています。 この例では、
encode
関数を使用
encode
と、
String
型の変数が
Encoder
渡され、
encode
返されます。
- 私のタイプは大きいですか? タイプが大きい場合、参照による転送はメモリを節約します。 参照渡しでは変数がコピーされないことに注意してください。 大量のデータを持つ
String
型のバッファを想像してください。 別の関数に転送されるたびにコピーすると、プログラムの速度が大幅に低下する可能性があります。
これで、
&str
、
String
形式の文字列を受け入れる関数を作成できます。さらに、参照カウンターを使用することもできます。 リンクを含む構造を作成することもできます。 構造体の寿命は、その構造体に含まれるリンクに関連付けられているため、構造体は、それが参照する変数に耐えることができないため、プログラムでエラーが発生します。 これで、構造内でリンクを使用する場合と使用しない場合の基本的な理解が得られました。
「静的」について
もう1点注意する価値があると思います。 静的ライフタイム
'static
(最初の例のように)を使用して、サンプルを強制的にコンパイルできますが、これを行うことはお勧めしません。
struct Person { name: &'static str, } impl Person { fn greet(&self) { println!(", {}", self.name); } } fn main() { let person = Person { name: "Herman" }; person.greet(); }
静的寿命
'static
プログラムの寿命を通して有効です。 あなたは
Person
や
name
そんなに長く生きること
name
望みません。 (たとえば、プログラム自体にコンパイルされた静的文字列リテラルは、タイプ
&'static str
。つまり、プログラムの存続期間中-およそTransl。)