Rust 里有些基础性的问题,自己一时搞懂了,过段时间又忘记了,下一次碰见又要花费不少时间重新去想。所以就想找个地方专门放这些已经想清楚的问题,或是容易忘掉的知识。

box 关键字和 Box::new()

作用一样,都是在堆中分配内存。Box::new() 的内部实现就是单纯使用了一下 box 关键字。但 Box::new() 是函数调用,所以在调用前会先构建。

1
2
3
4
fn big_array() -> Box<[u8; 268435456]> {
// 268435456 = 256 * 1024 * 1024
Box::new([0; 268435456])
}

比如上面这个函数就会先在栈上构建一个 256 MB 的数组,然后才会在堆上分配,这很容易造成爆栈。

而使用 box 关键字并不会,这个数组会直接在堆上分配:

1
2
3
4
5
6
#![feature(box_syntax)]

fn big_array() -> Box<[u8; 268435456]> {
// 268435456 = 256 * 1024 * 1024
box [0; 268435456]
}

但是 box 关键字在 stable 版上不能使用。如果要在堆上分配大数组,使用 Vec。(通过 Vec::with_capacity()vec! 宏)

Vec 的索引访问解释

我们不能 moveVec<T> 里的元素,比如这样子是不行的:

1
2
3
4
fn vec_index() {
let v = vec!["a".to_string(), "b".to_string()];
let a = v[0]; // cannot move out of indexed content
}

原理是什么呢?

由于有 impl<T> Index<usize> for Vec<T>v[0] 其实是一个语法糖,它等于 *v.index(0)

(index 函数的签名: fn index(&self, index: usize) -> &T

如果我们把语法糖恢复:

1
2
3
4
5
fn vec_index() {
use std::ops::Index;
let v = vec!["a".to_string(), "b".to_string()];
let a = *v.index(0); // cannot move out of borrowed content
}

这样就很清楚了,我们只是取得了 0 号元素的不可变引用,并不能将其 move

使用方括号时候报的错误和下面一种不一样,但只是同一个错误的不同表述罢了。

[T] 的使用

由于 [T] 不是 Sized,所以我们在实际使用时都是使用的 &[T]&[T] 是一个 fat pointer,包含了指针指向的地址和 slice 长度。

针对 &[T] 的索引访问的返回类型并不是引用类型,所以如果 T 不是 Copy 的话,我们一般还需要取引用:

1
2
3
4
5
6
7
let sl: &[String] = &["foo".to_string(), "bar".to_string()];

// let s = sl[0]; // Compile error! s: String
let s = &sl[0]; // s: &String

// let p = sl[1..]; // Compile error! p: [String]
let p = &sl[1..]; // s: &[String]

原因是解开方括号语法糖后,sl[0] 会变成 *sl.index(0),一元 * 的优先级比方法调用低,所以调用 index 方法时先自动解引用了。然后一元 * 操作符再解引用了 index 方法返回的 &T,所以类型就变成了 T

CopyClone

实现了 Copy 的类型在赋值、传参等场景下不会使用默认的 move 语义,而会进行 bit-to-bit 的复制,不能实现深拷贝。

如果一个 struct 要实现 Copy,那么它的每一个字段也必须是 Copy 的。

注:&TCopy 的,而 &mut T 不是 Copy 的。

一般在自动实现 Copy 的同时也会选择同时自动实现 Clone

在有深拷贝或是其他特殊需求时,我们则需要手动实现 Clone