rust 标准库 (2)

 

Option类型 Box类型

  • Option类型
  • Box类型

Option 类型

说明

  • Option是枚举体类型
    • 它或者是Some值,并包含一个value值
    • 或者是None值,没有包含
  • Option通常与match模式匹配配合
    • 以查询一个值的存在并采取行动
    • match匹配总是要考虑到None的情况

引用转换器

提取包含的值

Opt到Result的转换

转换Some变体

Opt的整合

Opt作为bool值

迭代相关

收集到vec中

  • Option实现了FromIterator特性,它允许将一个Option值的迭代器收集成一个Option的集合,这个集合包含了原始Option值的每一个值,如果有任何元素是None,则是None。

    let v = vec![Some(2), Some(4), None, Some(8)];
    let res: Option<Vec<_>> = v.into_iter().collect();
    
    >>> res == None
    
    let v = vec![Some(2), Some(4), Some(8)];
    let res: Option<Vec<_>> = v.into_iter().collect();
    
    >>> res == Some(vec![2, 4, 8])
    
  • Option也实现了Product和Sum的特性,允许一个Option值的迭代器提供product和sum方法。

    let v = vec![None, Some(1), Some(2), Some(3)];
    let res: Option<i32> = v.into_iter().sum();
    assert_eq!(res, None);
    
    let v = vec![Some(1), Some(2), Some(21)];
    let res: Option<i32> = v.into_iter().product();
    assert_eq!(res, Some(42));
    

就地修改一个Option

转移Option中的T

复制

Option<&T>

Option

Box

说明

pub struct Box<T, A = Global>(Unique<T>, A)
  where T: ?Sized, A: Allocator;

从这里看到,Box是通过Global::Allocator分配的内存

Global::Allocator::alloc(layout: Layout) -> *mut u8
  • 这里layout: Layout是内存对齐的方式,一般是2的指数对齐
Box::new(x: T) -> Box<T, Global>
  • 通过alloc在堆上分配内存,然后把x放进去
  • 而且得到的是一个元组类型Box(Unique<T>, Global)唯一指针
  • 用于堆分配的指针类型
    • Box,通常被称为 "box", 提供了Rust中最简单的堆分配形式
    • box为这种分配提供了所有权,并在box指针超出作用域时析构它们封装的数据
    • box还确保它们分配的内容永远不会超过isize::MAX字节
  • 通过创建Box将一个值从移到

    let val: u8 = 5;
    let boxed: Box<u8> = Box::new(val);
    
  • 运行通过解引用将一个值从Box移回

    let boxed: Box<u8> = Box::new(5);
    let val: u8 = *boxed;
    

内存分布

对于非零大小的值,Box 将使用全局分配器进行分配。在Box和用Global分配器分配的原始指针之间进行双向转换是合法的,只要分配器使用的Layout对该类型是正确的。更确切地说,一个用Layout::for_value(&*value)全局分配器分配的*mut T的值,可以用Box::<T>::from_raw(value)转换为一个Box。反之,在一个(*mut T)值后面的内存(从Box::<T>::into_raw获得)可以用Layout::for_value(&*value)的全局分配器来取消分配

对于零大小的值,Box指针仍然必须对读和写有效,并且充分地对齐。特别是,将任何对齐的非零整数字面量转换为一个原始指针会产生一个有效的指针,但是指向先前分配的内存的指针是无效的,因为该内存已经被释放了。如果Box::new不能被使用,推荐的方法是使用ptr::NonNull::dangling来构建一个ZST的Box

只要 T:SizedBox<T>就能保证被表示为一个指针,并且也与C指针(即C类型T*)的ABI兼容。这意味着,如果你有外部的 “C “Rust函数将从C中调用,你可以使用Box<T>类型来定义这些Rust函数,并在C端使用T*作为对应的类型。作为一个例子,考虑一下这个C头文件,它声明了创建和销毁某种Foo值的函数。

/* C header */

/* 将所有权归还给调用者 */
struct Foo* foo_new(void);

/* 从调用者那里获得所有权;当以null调用时,没有作用 */
void foo_delete(struct Foo*);

这两个函数可以在Rust中实现,如下所示。在这里,C语言中的struct Foo*类型被翻译成Box<Foo>,这就抓住了所有权约束。还需要注意的是,foo_delete的可空参数在Rust中被表示为Option<Box<Foo>>,因为Box<Foo>不能为空。

#[repr(C)]
pub struct Foo;

#[no_mangle]
pub extern "C" fn foo_new() -> Box<Foo> {
    Box::new(Foo)
}

#[no_mangle]
pub extern "C" fn foo_delete(_: Option<Box<Foo>>) {}

尽管 Box 具有与 C 指针相同的表示方法和 C ABI,但这并不意味着你可以将一个任意的 `T*` 转换为 Box 并期望事情能够顺利进行。Box的值将总是完全对齐的、非空的指针。此外,Box的析构器将试图用全局分配器释放该值。一般来说,最好的做法是只对源自全局分配器的指针使用Box

重要的是: 至少在目前,你应该避免对那些在C语言中定义但在Rust中调用的函数使用Box<T>类型。在这些情况下,你应该尽可能地直接反映C语言的类型。使用Box<T>这样的类型,而C语言的定义只是使用T*,会导致未定义的行为,正如rust-lang/unsafe-code-guidelines#198所述。

引用转换

转换到原始指针

其中

let (raw, alloc) = Box::into_raw_with_allocator(self);
  • 调用这个函数后,调用者要对之前由Box管理的内存负责
  • 特别是,调用者应该正确地销毁T并释放内存,同时考虑到Box使用的内存布局
    • 最简单的方法是用Box::from_raw函数将原始指针转换回Box,让Box的析构器来执行清理工作。

取出内部的T

复制 clone

Box.clone()-> Box
  • 得到一个内容一样的Box结构体

    impl<T: Clone, A: Allocator + Clone> Clone for Box<T, A> {
      fn clone(&self) -> Self {
          // 以self.1的对齐方式创建一个未初始化的内存空间
          let mut boxed = Self::new_uninit_in(self.1.clone());
          unsafe {
              (**self).write_clone_into_raw(boxed.as_mut_ptr());
              boxed.assume_init()
          }
      }
    }
    
  • 根据self.1的内存对齐方式分配一个内存空间boxed
  • 把box的内容拷贝到这个新分配的空间boxed中
  • 把这个boxed: Box<MayBeUninit<T>, A>未初始化的类型通过assume_init()声明为Box(Unique<T>, A)类型

    pub unsafe fn assume_init(self) -> Box<T, A> {
        // 把未初始化内存boxed通过allocator分解成(raw指针, alloc对齐方式)
        let (raw, alloc) = Box::into_raw_with_allocator(self);
        // 用from_raw_in()函数把raw指针,alloc组装成Box
        unsafe { Box::from_raw_in(raw as *mut T, alloc) }
    }
    

Vec 向量

说明

  • Vec是一个可连续增长的数组类型,内容由堆分配,写成Vec<T>
    • 新建
      • Vec::new()
      • vec![]
    • 压入
      • v.push(32)
    • 弹出
      • v.pop()
    • v[1] = v[2]+v[3]
    • v[0]
pub struct Vec<T>, A: Allocator = Global> {
    buf: RawVec<T, A>,
    len: usize,
}

得到Vec的元数据

增加元素

复制元素

转换到Box

删除元素

迭代器

Vec迭代器

Vec在for循环

  • for-in-loop,或者更准确地说,迭代器循环,是Rust中常见做法的一个简单语法糖
    • 即循环任何实现IntoIterator的东西
    • 直到由.into_iter()返回的迭代器返回None(或者循环体使用break)

有关链表的一些技巧

指针向前移动

// 创建一个链表头
let mut head = Some(Box::new(ListNode::new(-999)));
let mut tail = head.as_mut();

// tail指向结点的next
tail = tail.unwrap().next.as_mut()

用下一个结点替换当前结点

// list_vec = [list1, list2, list3, ...]

list_vec[min_idx] = list_vec[min_idx].as_mut().unwrap().next.clone();

当Option不能Move Out时

面对如下问题:

tail.as_mut().unwrap().next = list_vec[min_idx];

cannot move out of index of Vec<Option<Box<ListNode>>> move occurs because value has type Option<Box<ListNode>>, which does not implement the Copy trait

使用take()

resptr.as_mut().unwrap().next = list_vec[min_idx].take();
list_vec[min_idx] = resptr.as_mut().unwrap().next.as_mut().unwrap().next.clone();
  • 先把Option的内容用None替换出来
  • 再用新的内容填充进去