一、智能指针

指针还是引用

引用是特殊的指针

  1. 指针是一个持有内存地址的值,可以通过解引用来访问它指向的内存地址,理论上可以解引用到任意数据类型;
  2. 引用是一个特殊 的指针,它的解引用访问是受限的,只能解引用到它引用数据的类型,不能用作它用。

智能指针不仅是指针

智能指针=指针+额外处理能力

  1. 在指针和引用的基础上,Rust 偷师 C++,提供了智能指针。
  2. 智能指针是一个表现行为很 像指针的数据结构,但除了指向数据的指针外,它还有元数据以提供额外的处理能力。

智能指针=胖指针+所有权

  1. 智能指针一定是一个胖指针,但胖指针不一定是一个 智能指针。
  2. 比如 &str 就只是一个胖指针,它有指向堆内存字符串的指针,同时还有关于字 符串长度的元数据。

  1. String 除了多一个 capacity 字段,似乎也没有什么特殊。
  2. 但 String 对 堆上的值有所有权,而 &str 是没有所有权的
  3. 这是 Rust 中智能指针和普通胖指针的区 别。

智能指针和结构体有什么区别

  1. String用结构体定义,其实就是Vec
#![allow(unused)]

fn main() {
pub struct String {
    vec: Vec<u8>,
}
}
  1. 和普通的结构体不同的是,String 实现了 Deref 和 DerefMut,这使得它在解引用的时 候,会得到 &str
#![allow(unused)]

fn main() {
impl ops::Deref for String {
    type Target = str;

    fn deref(&self) -> &str {
        unsafe { str::from_utf8_unchecked(&self.vec) }
    }
}

impl ops::DerefMut for String {
    fn deref_mut(&mut self) -> &mut str {
        unsafe { str::from_utf8_unchecked_mut(&mut *self.vec) }
    }
}
}
  1. 另外,由于在堆上分配了数据,String 还需要为其分配的资源做相应的回收。而 String 内部使用了 Vec,所以它可以依赖 Vec 的能力来释放堆内存
#![allow(unused)]

fn main() {
unsafe impl<#[may_dangle] T, A: Allocator> Drop for Vec<T, A> {
    fn drop(&mut self) {
        unsafe {
            // use drop for [T]
            // use a raw slice to refer to the elements of the vector as weakest necessary type;
            // could avoid questions of validity in certain cases
            ptr::drop_in_place(ptr::slice_from_raw_parts_mut(self.as_mut_ptr(), self.len))
        }
        // RawVec handles deallocation
    }
}
}

在 Rust 中,凡是需要做资源回收的数据结构,且实现了 Deref/DerefMut/Drop,都是智能指针。

按照这个定义,除了 String,还有很多智能指针,比如:

  1. 用于在堆上 分配内存的 Box 和 Vec

  2. 用于引用计数的 Rc 和 Arc

  3. 很多其他数据结 构,如 PathBuf、Cow<’a, B>、MutexGuard、RwLockReadGuard 和 RwLockWriteGuard 等也是智能指针。

自定义智能指针

MyString结构示意图

MyString

MyString实现代码

use std::{fmt, ops::Deref, str};

const MINI_STRING_MAX_LEN: usize = 30;

// MyString 里,String 有 3 个 word,供 24 字节,所以它以 8 字节对齐
// 所以 enum 的 tag + padding 最少 8 字节,整个结构占 32 字节。
// MiniString 可以最多有 30 字节(再加上 1 字节长度和 1字节 tag),就是 32 字节.
struct MiniString {
    len: u8,
    data: [u8; MINI_STRING_MAX_LEN],
}

impl MiniString {
    // 这里 new 接口不暴露出去,保证传入的 v 的字节长度小于等于 30
    fn new(v: impl AsRef<str>) -> Self {
        let bytes = v.as_ref().as_bytes();
        // 我们在拷贝内容时一定要要使用字符串的字节长度
        let len = bytes.len();
        let mut data = [0u8; MINI_STRING_MAX_LEN];
        data[..len].copy_from_slice(bytes);
        Self {
            len: len as u8,
            data,
        }
    }
}

impl Deref for MiniString {
    type Target = str;

    fn deref(&self) -> &Self::Target {
        // 由于生成 MiniString 的接口是隐藏的,它只能来自字符串,所以下面这行是安全的
        str::from_utf8(&self.data[..self.len as usize]).unwrap()
        // 也可以直接用 unsafe 版本
        // unsafe { str::from_utf8_unchecked(&self.data[..self.len as usize]) }
    }
}

impl fmt::Debug for MiniString {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // 这里由于实现了 Deref trait,可以直接得到一个 &str 输出
        write!(f, "{}", self.deref())
    }
}

#[derive(Debug)]
enum MyString {
    Inline(MiniString),
    Standard(String),
}

// 实现 Deref 接口对两种不同的场景统一得到 &str
impl Deref for MyString {
    type Target = str;

    fn deref(&self) -> &Self::Target {
        match *self {
            MyString::Inline(ref v) => v.deref(),
            MyString::Standard(ref v) => v.deref(),
        }
    }
}

// impl From<&str> for MyString {
//     fn from(s: &str) -> Self {
//         match s.len() > MINI_STRING_MAX_LEN {
//             true => Self::Standard(s.to_owned()),
//             _ => Self::Inline(MiniString::new(s)),
//         }
//     }
// }

// impl From<String> for MyString {
//     fn from(s: String) -> Self {
//         match s.len() > MINI_STRING_MAX_LEN {
//             true => Self::Standard(s),
//             _ => Self::Inline(MiniString::new(s)),
//         }
//     }
// }

impl<T> From<T> for MyString
where
    T: AsRef<str>,
{
    fn from(s: T) -> Self {
        match s.as_ref().len() > MINI_STRING_MAX_LEN {
            true => Self::Standard(s.as_ref().to_owned()),
            _ => Self::Inline(MiniString::new(s)),
        }
    }
}

impl fmt::Display for MyString {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.deref())
    }
}

impl MyString {
    pub fn push_str(&mut self, s: &str) {
        match *self {
            MyString::Inline(ref mut v) => {
                let len = v.len as usize;
                let len1 = s.len();
                if len + len1 > MINI_STRING_MAX_LEN {
                    let mut owned = v.deref().to_string();
                    owned.push_str(s);
                    *self = MyString::Standard(owned);
                } else {
                    let total = len + len1;
                    v.data[len..len + len1].copy_from_slice(s.as_bytes());
                    v.len = total as u8;
                }
            }
            MyString::Standard(ref mut v) => v.push_str(s),
        }
    }
}

fn main() {
    let len1 = std::mem::size_of::<MyString>();
    let len2 = std::mem::size_of::<MiniString>();
    println!("Len: MyString {}, MiniString {}", len1, len2);

    let s1: MyString = "hello world".into();
    let s2: MyString = "这是一个超过了三十个字节的很长很长的字符串".into();

    // debug 输出
    println!("s1: {:?}, s2: {:?}", s1, s2);
    // display 输出
    println!(
        "s1: {}({} bytes, {} chars), s2: {}({} bytes, {} chars)",
        s1,
        s1.len(),
        s1.chars().count(),
        s2,
        s2.len(),
        s2.chars().count()
    );

    // MyString 可以使用一切 &str 接口,感谢 Rust 的自动 Deref
    assert!(s1.ends_with("world"));
    assert!(s2.starts_with('这'));

    let s = String::from("这是一个超过了三十个字节的很长很长的字符串");
    println!("s: {:p}", &*s);
    // From<T: AsRef<str>> 的实现会导致额外的复制
    let s3: MyString = s.into();
    println!("s3: {:p}", &*s3);

    let mut s4: MyString = "Hello Tyr! ".into();
    println!("s4: {:?}", s4);
    s4.push_str("这是一个超过了三十个字节的很长很长的字符串");
    println!("s4: {:?}", s4);
}

为了让 MyString 表现行为和 &str 一致:

  1. 我们可以通过实现 Deref trait 让 MyString 可以被解引用成 &str。
  2. 除此之外,还可以实现 Debug/Display 和 From trait,让 MyString 使用起来更方便。
  3. 这个简单实现的 MyString,不管它内部的数据是纯栈上的 MiniString 版本,还是包含堆 上内存的 String 版本,使用的体验和 &str 都一致,仅仅牺牲了一点点效率和内存,就可 以让小容量的字符串,可以高效地存储在栈上并且自如地使用。
  4. smartstring 的第三方库实现类似功能,还做了优化。