版权声明:原创不易,转载请注明出处。
&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版本编译:
关于 str和&str标准库文档是如此说明的:
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
,最直观的例子就是拥有静态生命周期'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]、&[T] 和 FatPtr
Rust中切片类型表示为 &[T]
,它表示无法在编译期确定大小的同一种类型数据的连续内存序列[T]
的视图
,它在内存中的管理是基于Repr
union 来实现的,&[T]
即指向[T]
类型的指针,这个指针在最底层是通过称为胖指针(FatPtr
)的结构体来模拟的:
// src/libcore/ptr/mod.rs
#[repr(C)]
pub(crate) union Repr {
pub(crate) rust: *const [T],
rust_mut: *mut [T],
pub(crate) raw: FatPtr,
}
#[repr(C)]
pub(crate) struct FatPtr {
data: *const T,
pub(crate) len: usize,
}
在内存布局(memory layout)上, 切片变量和FatPtr
类型的变量共享同一片内存空间,而FatPtr中则保存了"切片"的必要特征:
- data: 指向若干同质连续数据内存首地址的指针;
- len:
data
指针所指向的连续内存段中存放的元素数目;
而借助于Rust类型系统的优势,标准库在[T]
类型上定义的方法和trait则完全封装了底层负责解释指针含义的工作(这部分解释工作需要依赖unsafe rust来实现)。
如标准库实现的len方法:
// src/libcore/slice/mod.rs
#[lang = "slice"]
#[cfg(not(test))]
impl [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类型
查看标准库对于 str
类型的实现:
// 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
类型变量可以通过调用len
方法获取字符串中的字节个数,查看len
函数的定义可以发现,其内部是调用了as_bytes
方法实现的;as_bytes
方法中定义了一个union类型 Slices
,并且声明为和C语言的内存布局一致(#[repr(C)]
):
#[repr(C)]
union Slices<'a> {
str: &'a str,
slice: &'a [u8],
}
熟悉union的同学不难发现,&str
和&[u8]
的内存布局是一样的,从而&str
是&[T]
当T=u8
时的特例!而len
方法不过是调用了&[u8]
的len
方法而已。
&str v.s. &[u8]
String slices are always valid UTF-8.
字符串切片类型总是合法的utf-8
字节序列。
&str -> &[u8]
let s = "hello rust";
let bytes = s.as_bytes();
&[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字节序列的合法性检测,若不符合utf-8规范,则抛出Error。
One more thing
思考下面的例子:
let s = "hello rust";
let len = s.len();
其中 s的类型是 &str
,那么s是怎么调用定义在 str
类型上的方法len
的呢?
是因为标准库已经为我们对任意类型&T
实现了 Deref
trait:
// src/libcore/ops/deref.rs
#[stable(feature = "rust1", since = "1.0.0")]
impl Deref for &T {
type Target = T;
fn deref(&self) -> &T {
*self
}
}
// ...
#[stable(feature = "rust1", since = "1.0.0")]
impl Deref for &mut T {
type Target = T;
fn deref(&self) -> &T {
*self
}
}
而实现了Deref trait的类型,编译器会在适当的地方对变量进行足够多的解引用以使变量的类型转变为 T
。
由于deref
函数获取的变量&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;
}
因此保证了由编译器来进行解引用总是安全的。
参考资料
- ptr: https://doc.rust-lang.org/std/ptr/index.html
- str: https://doc.rust-lang.org/std/str/index.html
- slice: https://doc.rust-lang.org/std/slice/index.html
- 《Rust编程之道》
- 《Why Rust?》