Rust 有兩種主要的字串型別:String和&str。有時,人們認為這兩種型別使得 Rust 程式碼難以編寫,因為你必須考慮在特定情況下應該使用哪一種。我編寫 Rust 的經驗是,我並沒有真正考慮過這個問題,這篇文章是關於一些經驗法則,你可以使用這些法則來像我一樣。
第一級:根本不要考慮
您可以做的第一件事就是遵循最簡單的規則:
總是使用String,永遠不要使用&str。
看起來像這樣:
struct Person { name: String, }
fn first_word(words: String) -> String { words .split_whitespace() .next() .expect(<font>"words should not be empty") .to_string() }
|
這種風格意味著你有時可能需要新增.to_string()或.clone() 才能使事情正常工作:fn main() { let sentence = <font>"Hello, world!";
println!("{}", first_word(sentence.to_string()));
let owned = String::from("A string");
// if we don't clone here, we can't use owned the second time<i> println!("{}", first_word(owned.clone())); println!("{}", first_word(owned)); }
|
但沒關係,當您需要時,編譯器會通知您:error[E0382]: use of moved value: `owned` --> src/main.rs:21:31 | 18 | let owned = String::from(<font>"A string"); | ----- move occurs because `owned` has type `String`, which does not implement the `Copy` trait 19 | 20 | println!("{}", first_word(owned)); | ----- value moved here 21 | println!("{}", first_word(owned)); | ^^^^^ value used here after move | note: consider changing this parameter type in function `first_word` to borrow instead if owning the value isn't necessary --> src/main.rs:5:22 | 5 | fn first_word(words: String) -> String { | ---------- ^^^^^^ this parameter takes ownership of the value | | | in this function help: consider cloning the value if the performance cost is acceptable | 20 | println!("{}", first_word(owned.clone())); | ++++++++
|
嘿,編譯器,這是個好主意。讓我們進入第 2 級。級別 2:優先&str使用函式引數
一個更好的規則是這樣的:
- 始終String在結構體中使用,對於函式,用於&str引數和String返回值的型別。
這就是第 1 級編譯器錯誤建議我們做的事情.clone。
其結果程式碼如下:
struct Person { name: String, }
fn first_word(words: &str) -> String { words .split_whitespace() .next() .expect(<font>"words should not be empty") .to_string() }
fn main() { let sentence = "Hello, world!";
println!("{}", first_word(sentence));
let owned = String::from("A string");
println!("{}", first_word(&owned)); println!("{}", first_word(&owned)); }
|
現在我們做的複製少了很多。我們確實需要在我們希望傳遞給的值&上新增一個,但這並不是太糟糕,當我們忘記時,編譯器會幫助我們:Stringfirst_word
error[E0308]: mismatched types --> src/main.rs:20:31 | 20 | println!(<font>"{}", first_word(owned)); | ---------- ^^^^^ expected `&str`, found `String` | | | arguments to this function are incorrect | note: function defined here --> src/main.rs:5:4 | 5 | fn first_word(words: &str) -> String { | ^^^^^^^^^^ ----------- help: consider borrowing here | 20 | println!("{}", first_word(&owned)); |
|
+遵循這條規則將幫助你成功度過 95% 的情況。是的,這個數字是透過非常科學的過程得出的:“這是我編造的,但在編寫 Rust 十二年後,感覺它是正確的。”
對於最後 5% 中的 4%,我們可以進入第 3 級:
第 3 級:有時返回&str
以下是針對特定情況的稍微高階一點的規則:
- 在結構體中始終使用 String,在函式中使用 &str 表示引數。
- 如果函式的返回型別是從引數派生的,並且沒有被主體改變,則返回 &str。
- 如果遇到任何問題,請返回 String。
看起來是這樣的:
struct Person { name: String, }
<font>// we're returning a substring of words, so &str is appropriate<i> fn first_word(words: &str) -> &str { words .split_whitespace() .next() .expect("words should not be empty") }
fn main() { let sentence = "Hello, world!";
println!("{}", first_word(sentence));
let owned = String::from("A string");
println!("{}", first_word(&owned)); println!("{}", first_word(&owned)); }
|
.to_string這樣我們就可以刪除一個副本,在的主體中 我們不再有一個first_word。但有時我們不能這樣做:
<font>// 因為我們要將第一個單詞大寫,所以我們的返回型別不能再是<i> // &str,因為我們實際上不是返回一個子串:我們是<i> // 建立我們自己的新字串。<i> fn first_word_uppercase(words: &str) -> String { words .split_whitespace() .next() .expect("words should not be empty") .to_uppercase() }
fn main() { let sentence = "Hello, world!";
println!("{}", first_word_uppercase(sentence));
let owned = String::from("A string");
println!("{}", first_word_uppercase(&owned)); println!("{}", first_word_uppercase(&owned)); }
|
您如何知道情況確實如此?好吧,在這個特定情況下, to_uppercase已經返回了 a String。所以這是一個很好的提示。如果我們嘗試返回 a &str,我們會收到錯誤:<font>// this can't work<i> fn first_word_uppercase(words: &str) -> &str { &words .split_whitespace() .next() .expect("words should not be empty") .to_uppercase() }
|
會給我們error[E0515]: cannot return reference to temporary value --> src/main.rs:7:5 | 7 | &words | ______^- | | ______| 8 | || .split_whitespace() 9 | || .next() 10 | || .expect(<font>"words should not be empty") 11 | || .to_uppercase() | || ^ | ||_______________________| | |_______________________returns a reference to data owned by the current function | temporary value created here
|
就是這樣。遵循這條規則幾乎可以幫你應對所有需要思考String和&str思考的場景。透過一些練習,你會內化這些規則,當你對某個級別感到滿意時,你可以進入下一個級別。
那麼最後的 1% 怎麼辦呢?嗯,還有下一個級別……
第 4 級:何時在結構體中使用&str
以下是第 4 級的規則:
- 是否應該在結構體中使用 &str?
- 如果你想問這個問題,那就使用 String。
- 當你需要在結構體中使用 &str 時,你就會知道了。
在結構體中儲存引用當然很有用,Rust 支援這一點也很好。 但你只有在相當特殊的情況下才會用到它,如果你覺得自己在擔心 String vs &str 的問題,那麼你現在還不適合擔心在結構體中儲存 &str 的複雜性。事實上,有些人對這一規則深信不疑,以至於他們正在開發一種甚至不可能在結構體中儲存引用的語言,我最近發現這種語言非常有趣:Hylo.。
在Hylot語言 中,你可以把所有東西都看作是值,而不是引用。
他們認為,使用這種模型可以編寫出有意義的程式。
對於很多有用的 Rust 程式來說,不在結構體中儲存 &strs 確實是可以做到的。預設使用“值”。
因此,在你確定必須這麼做之前,這並不值得花費心思。 當你對程式進行了剖析,並確定將字串複製到結構體或從結構體複製出字串是一個大問題,足以讓你耗費畢生精力時,情況就會是這樣。