Rust標(biāo)準(zhǔn)庫之——&str類型

版權(quán)聲明:原創(chuàng)不易墩衙,轉(zhuǎn)載請注明出處务嫡。

&str類型是rust中最基本的字符串類型甲抖,聲明一個&str類型的變量很簡單:

let s = "hello rust";

&str類型

我們可以打印出上述定義中變量s的類型:

#![feature(type_name_of_val)]

fn main() {
    let s = "hello rust!";
    println!("{}: {}", std::any::type_name_of_val(&s), s);
}

rust-playground 中使用nightly版本編譯:

image.png

關(guān)于 str和&str標(biāo)準(zhǔn)庫文檔是如此說明的:

The str type, also called a 'string slice', is the most primitive string type. It is usually seen in its borrowed form, &str. It is also the type of string literals, &'static str.
String slices are always valid UTF-8.

通俗理解,str類型是字符串切片類型心铃,是rust中最基本的字符串類型惧眠,但是我們見的更多的是它的借用類型(引用值),也就是&str于个,最直觀的例子就是擁有靜態(tài)生命周期'static的字符串字面量。

另有 《Why Rust?》中給出的示例:

let seasons = vec!["Spring", "Summer", "Bleakness"];

即:

This declares seasons to be a value of type Vec<&str>, a vector of references to statically allocated strings.

因此在rust中&str類型為: 靜態(tài)內(nèi)存分配字符串的引用

[T]暮顺、&[T] 和 FatPtr

Rust中切片類型表示為 &[T]厅篓,它表示無法在編譯期確定大小的同一種類型數(shù)據(jù)的連續(xù)內(nèi)存序列[T]視圖,它在內(nèi)存中的管理是基于Repr union 來實(shí)現(xiàn)的捶码,&[T]即指向[T]類型的指針羽氮,這個指針在最底層是通過稱為胖指針(FatPtr)的結(jié)構(gòu)體來模擬的:

// src/libcore/ptr/mod.rs

#[repr(C)]
pub(crate) union Repr<T> {
    pub(crate) rust: *const [T],
    rust_mut: *mut [T],
    pub(crate) raw: FatPtr<T>,
}

#[repr(C)]
pub(crate) struct FatPtr<T> {
    data: *const T,
    pub(crate) len: usize,
}

在內(nèi)存布局(memory layout)上, 切片變量和FatPtr類型的變量共享同一片內(nèi)存空間,而FatPtr中則保存了"切片"的必要特征:

  • data: 指向若干同質(zhì)連續(xù)數(shù)據(jù)內(nèi)存首地址的指針惫恼;
  • len: data指針?biāo)赶虻倪B續(xù)內(nèi)存段中存放的元素?cái)?shù)目档押;

而借助于Rust類型系統(tǒng)的優(yōu)勢,標(biāo)準(zhǔn)庫在[T]類型上定義的方法和trait則完全封裝了底層負(fù)責(zé)解釋指針含義的工作(這部分解釋工作需要依賴unsafe rust來實(shí)現(xiàn))祈纯。
如標(biāo)準(zhǔn)庫實(shí)現(xiàn)的len方法:

// src/libcore/slice/mod.rs
#[lang = "slice"]
#[cfg(not(test))]
impl<T> [T] {
    /// Returns the number of elements in the slice.
    ///
    /// # Examples
    ///
    /// ```
    /// let a = [1, 2, 3];
    /// assert_eq!(a.len(), 3);
    /// ```
    #[stable(feature = "rust1", since = "1.0.0")]
    #[rustc_const_stable(feature = "const_slice_len", since = "1.32.0")]
    #[inline]
    // SAFETY: const sound because we transmute out the length field as a usize (which it must be)
    #[allow(unused_attributes)]
    #[allow_internal_unstable(const_fn_union)]
    pub const fn len(&self) -> usize {
        unsafe { crate::ptr::Repr { rust: self }.raw.len }
    }

str類型

查看標(biāo)準(zhǔn)庫對于 str類型的實(shí)現(xiàn):

// src/libcore/str/mod.rs

#[lang = "str"]
#[cfg(not(test))]
impl str {
    // ...
    #[stable(feature = "rust1", since = "1.0.0")]
    #[rustc_const_stable(feature = "const_str_len", since = "1.32.0")]
    #[inline]
    pub const fn len(&self) -> usize {
        self.as_bytes().len()
    }
    // ...
    #[stable(feature = "rust1", since = "1.0.0")]
    #[rustc_const_stable(feature = "str_as_bytes", since = "1.32.0")]
    #[inline(always)]
    #[allow(unused_attributes)]
    #[allow_internal_unstable(const_fn_union)]
    pub const fn as_bytes(&self) -> &[u8] {
        #[repr(C)]
        union Slices<'a> {
            str: &'a str,
            slice: &'a [u8],
        }
        // SAFETY: const sound because we transmute two types with the same layout
        unsafe { Slices { str: self }.slice }
    }
   // ...

我們知道令宿,&str類型變量可以通過調(diào)用len方法獲取字符串中的字節(jié)個數(shù),查看len函數(shù)的定義可以發(fā)現(xiàn)腕窥,其內(nèi)部是調(diào)用了as_bytes方法實(shí)現(xiàn)的粒没;as_bytes方法中定義了一個union類型 Slices,并且聲明為和C語言的內(nèi)存布局一致(#[repr(C)]):

#[repr(C)]
union Slices<'a> {
    str: &'a str,
    slice: &'a [u8],
 }

熟悉union的同學(xué)不難發(fā)現(xiàn)簇爆,&str&[u8]的內(nèi)存布局是一樣的癞松,從而&str&[T]當(dāng)T=u8時(shí)的特例!而len方法不過是調(diào)用了&[u8]len方法而已入蛆。

&str v.s. &[u8]

String slices are always valid UTF-8.

字符串切片類型總是合法的utf-8字節(jié)序列响蓉。

  1. &str -> &[u8]
let s = "hello rust";
let bytes = s.as_bytes();
  1. &[u8] -> &str
// src/libcore/str/mod.rs

#[stable(feature = "rust1", since = "1.0.0")]
pub fn from_utf8(v: &[u8]) -> Result<&str, Utf8Error> {
    run_utf8_validation(v)?;
    // SAFETY: Just ran validation.
    Ok(unsafe { from_utf8_unchecked(v) })
}

#[stable(feature = "str_mut_extras", since = "1.20.0")]
pub fn from_utf8_mut(v: &mut [u8]) -> Result<&mut str, Utf8Error> {
    run_utf8_validation(v)?;
    // SAFETY: Just ran validation.
    Ok(unsafe { from_utf8_unchecked_mut(v) })
}

#[inline]
#[stable(feature = "rust1", since = "1.0.0")]
pub unsafe fn from_utf8_unchecked(v: &[u8]) -> &str {
    &*(v as *const [u8] as *const str)
}

#[inline]
#[stable(feature = "str_mut_extras", since = "1.20.0")]
pub unsafe fn from_utf8_unchecked_mut(v: &mut [u8]) -> &mut str {
    &mut *(v as *mut [u8] as *mut str)
}

其中 run_utf8_validation(v)做了必要的utf-8字節(jié)序列的合法性檢測,若不符合utf-8規(guī)范哨毁,則拋出Error枫甲。

One more thing

思考下面的例子:

let s = "hello rust";
let len = s.len();

其中 s的類型是 &str,那么s是怎么調(diào)用定義在 str類型上的方法len的呢扼褪?
是因?yàn)闃?biāo)準(zhǔn)庫已經(jīng)為我們對任意類型&T實(shí)現(xiàn)了 Dereftrait:

// src/libcore/ops/deref.rs

#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> Deref for &T {
    type Target = T;

    fn deref(&self) -> &T {
        *self
    }
}
// ...
#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> Deref for &mut T {
    type Target = T;

    fn deref(&self) -> &T {
        *self
    }
}

而實(shí)現(xiàn)了Deref trait的類型言秸,編譯器會在適當(dāng)?shù)牡胤綄ψ兞窟M(jìn)行足夠多的解引用以使變量的類型轉(zhuǎn)變?yōu)?T

由于deref函數(shù)獲取的變量&self是不可變引用:

#[lang = "deref"]
#[doc(alias = "*")]
#[doc(alias = "&*")]
#[stable(feature = "rust1", since = "1.0.0")]
pub trait Deref {
    /// The resulting type after dereferencing.
    #[stable(feature = "rust1", since = "1.0.0")]
    type Target: ?Sized;

    /// Dereferences the value.
    #[must_use]
    #[stable(feature = "rust1", since = "1.0.0")]
    fn deref(&self) -> &Self::Target;
}

因此保證了由編譯器來進(jìn)行解引用總是安全的迎捺。

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末举畸,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子凳枝,更是在濱河造成了極大的恐慌抄沮,老刑警劉巖跋核,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異叛买,居然都是意外死亡砂代,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進(jìn)店門率挣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來刻伊,“玉大人,你說我怎么就攤上這事椒功〈废洌” “怎么了?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵动漾,是天一觀的道長丁屎。 經(jīng)常有香客問我,道長旱眯,這世上最難降的妖魔是什么晨川? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮删豺,結(jié)果婚禮上共虑,老公的妹妹穿的比我還像新娘。我一直安慰自己呀页,他們只是感情好看蚜,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著赔桌,像睡著了一般肮疗。 火紅的嫁衣襯著肌膚如雪证薇。 梳的紋絲不亂的頭發(fā)上袍患,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天俊戳,我揣著相機(jī)與錄音,去河邊找鬼雪位。 笑死竭钝,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的雹洗。 我是一名探鬼主播香罐,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼时肿!你這毒婦竟也來了庇茫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤螃成,失蹤者是張志新(化名)和其女友劉穎旦签,沒想到半個月后查坪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡宁炫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年偿曙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片羔巢。...
    茶點(diǎn)故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡望忆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出竿秆,到底是詐尸還是另有隱情启摄,我是刑警寧澤,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布袍辞,位于F島的核電站,受9級特大地震影響常摧,放射性物質(zhì)發(fā)生泄漏搅吁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一落午、第九天 我趴在偏房一處隱蔽的房頂上張望谎懦。 院中可真熱鬧,春花似錦溃斋、人聲如沸界拦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽享甸。三九已至,卻和暖如春梳侨,著一層夾襖步出監(jiān)牢的瞬間蛉威,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工走哺, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蚯嫌,地道東北人。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓丙躏,卻偏偏與公主長得像择示,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子晒旅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評論 2 348