str &str and more
Publish on 2024-11-21

char

在老牌语言中,char通常是 1 Byte 的长度,储存一个ASCII字符。但是在rust中,字符是utf-8编码的,而utf-8采用的是变长编码 (长度为 1-4 Byte)。为了内存对齐,rust中最小的char长度应当是可以是2的幂次的,大于等于4的最小长度,也就是4。

字符串的实现

字符串储存了文本的字面量,首先要明确这一点

然后需要理解的是,所有的类型,都是固定长度的,比如i32就是4个字节。而字符串的长度显然是不定的,是可变的,作为 byte sequence [u8] 被直接储存在可执行文件的 data section 中。

┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
H  │  e  │  l  │  l  │  o  │     │  W  │  o  │  r  │  l  │  d  │
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
721011081081113287111114108100
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘

C 的方法

在 C 中,\0 作为字符串的结束符 append 在字符串的末尾。为了确定字符串的长度,就需要遍历每个 byte。

Rust 的方法

let arr = [1, 2, 3, 4]; // array

// 以下是slice
let slice1 = &arr[..];
let slice2 = &arr[1..3];
let slice3 = &arr[..3];
let slice4 = &arr[1..];

rust中的切片类型就是 &[T],表示连续内存序列(数组) [T]视图,这个指针在最底层是通过称为胖指针(FatPtr)的结构体来模拟的:

// src/libcore/ptr/mod.rs

// Repr<T> 联合体定义了不同种类的指针,用于引用数组(切片)的数据。这个联合体使用了 repr(C) 属性,这表示 Rust 应该按照 C 语言的内存布局规则来布局这个联合体,以便与其他语言进行交互。

#[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,
}

在内存布局(memory layout)上, 切片变量和FatPtr类型的变量共享同一片内存空间,而FatPtr中则保存了”切片”的必要特征:

  • data: 指向若干同质连续数据内存首地址的指针;
  • len: data指针所指向的连续内存段中存放的元素数目;

而借助于Rust类型系统的优势,标准库在[T]类型上定义的方法和trait则完全封装了底层负责解释指针含义的工作(这部分解释工作需要依赖unsafe rust来实现)。

str 在本质上是 ‘static str,对应的,s: &’static str = “foo”

str在rust中是很特殊的一个存在,由于utf-8动态编码的性质,如果你想要获得str的第n个字符

&str.chars().nth(n)

浅浅对比 String

pub struct String {
    vec: Vec<u8>,
}

rust的String实际上就很直观了,底层就是一个vec,只不过因为是private的,必须要通过暴露的借口进行初始化。

pub const fn new() -> String {
  String { vec: Vec::new() }
}

同时,在rust中,str被硬编码在了程序的二进制文件中(static data),而&str正是硬编码后的str在文件中的地址。而string则是被创建在堆上的。

© 2024 humbornjo :: based on 
nobloger  ::  rss