String in Rust

daniel1in

Daniel Lin

Posted on September 22, 2021

String in Rust

之前看到一個有趣的圖,在 c 中 string 其實就是 char*,而在 rust 中 string 卻有很多種型別,對寫 Golang 的我來說光是為何要 to_string() 才能 append string 就滿頭問號。

slice, array, vector

講 string 前可以先理解 rust 中 slice, array, vector 的定義
array 為固定長度的序列
[T; N] T 是類型 N 是 長度

let s = [1; 5];
println!("{:?}", s);
// [1, 1, 1, 1, 1]
Enter fullscreen mode Exit fullscreen mode

vector 是可以動態增長的序列

let mut s = vec![1];
s.push(2);
assert_eq!(s, &[1, 2]);
assert_eq!(s.len(), 2);
assert_ne!(s.capacity(), 2);
Enter fullscreen mode Exit fullscreen mode

slice 則是對連續空間的 reference,可以是 vector / array 的一部分參考,可以不需要經過複製就可以對 slice 參照的 vector / array 進行操作

常見的 &str 和 String 的差別

&str

let s = "abc";
Enter fullscreen mode Exit fullscreen mode

在編譯時因為 "abc" 長度是固定的所以被認為是靜態的放 stack 上,所以引用時去參照這段位置,所以 s 為 str 這個類別的 reference &str,而 &str 其實是一個 slice 參照 u8 的 array,

String

let s = "abc".to_string();
Enter fullscreen mode Exit fullscreen mode

s 是 String 其實是一個 u8 的 vector,因為是可以動態增長,compiler 不會知道 s 會長多大,所以必須放在 heap 上,可以做下面的操作來改變 String 本身

let mut s = "abc".to_string();
s.push_str("def");
Enter fullscreen mode Exit fullscreen mode

那為何 rust 不把我定義的 "abc" 值當作 [u8] 就好了呢? 因為 rust 還同時要確保 &str 中的 [u8] 是合法的 UTF-8 字元,就像大部份語言 char 與 string 的區別,這種 pointer 包含了 type 還有其他規則的 pointer 常被稱為 fat pointer。

可以看出來其實 &str 與 String 類型的差別跟 compiler 將 data 放在 heap 還是 stack 有很大的關係,當你對 &str 使用 to_string 就產生的 memory allocation,所以不需要就不要使用 to_string 增加開銷。

其他意外發現的坑

計算字數

let s = "🗻∈🌏";
let byte = s.as_bytes();

assert_eq!(s.len(), 11);
assert_eq!(s.chars().count(), 3);
assert_eq!(byte.len(), 11);
Enter fullscreen mode Exit fullscreen mode

s.len() 竟然是 11 讓我有點傻眼XD...

Precomposed Characters

除此之外,如果是 precomposed characters 會有很神奇的現象 [1]

println!("{}", "é".chars().count()); // 2
println!("{}", "é".chars().count()); // 1
Enter fullscreen mode Exit fullscreen mode

何會得到不一樣的結果?因為實際上這兩個是不一樣的字 程式碼

fn main() {
    assert_eq!("é","é")
} thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `"e\u{301}"`,
 right: `"é"`', src/main.rs:2:5
"""
Enter fullscreen mode Exit fullscreen mode

這讓我理解到數字其實背後也有很多學問,chars count 是數 Unicode Scalar Value,而 "é" 是 e 還有 \u301 這個 Unicode 組成 ,所以直接算就會是兩個字,那解法就是用 graphemes 這個 method 對應 composited char 的問題

use unicode_segmentation::UnicodeSegmentation; // 1.6.0
fn main() {
    let s = "é";
    println!("{}", s.chars().count()); // 2
    println!("{}", "é".graphemes(true).count()); // 1
}
Enter fullscreen mode Exit fullscreen mode

註 [1]: 感謝 Rust-TW Telegram 上 Lo Weihang 勘誤
Updated at 2021/11/28

💖 💪 🙅 🚩
daniel1in
Daniel Lin

Posted on September 22, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related