UMBRELLA

未雨绸缪,举重若轻

std::marker::PhantomData 是一个零大小的类型,用于标记一些类型,这些类型看起来拥有类型 T,但实际上并没有:

1
2
3
pub struct PhantomData<T>
where
T: ?Sized;

Rust 并不希望在定义类型时,出现目前还没使用,但未来会被使用的泛型参数,例如未使用的生命周期参数以及未使用的类型。

PhantomData 最常见的用例可能是具有未使用的生命周期参数的结构体,例如,这儿有一个结构体 Slice,它有两个 *const T 类型的指针,可能指向某个地方的数组,我们期望 Slice 类型的值在生命周期 'a 内仅仅有效,但是如果像下面这样,'a 我们又无处安放:

1
2
3
4
struct Slice<'a, T> {
start: *const T,
end: *const T,
}

我们可以使用 PhantomData 告诉编译器就像 Slice 结构包含引用 &'a T 一样来纠正这个问题:

1
2
3
4
5
6
7
use std::marker::PhantomData;

struct Slice<'a, T: 'a> {
start: *const T,
end: *const T,
phantom: PhantomData<&'a T>,
}

这反过来要求 T 类型中的任何引用在生命周期 'a 内都是有效的,初始化 Slice 时,仅需要为 phantom 字段提供值 PhantomData 即可:

1
2
3
4
5
6
7
8
fn borrow_vec<T>(vec: &Vec<T>) -> Slice<'_, T> {
let ptr = vec.as_ptr();
Slice {
start: ptr,
end: unsafe { ptr.add(vec.len()) },
phantom: PhantomData,
}
}
阅读全文 »

很多时候,我们需要实现一些自动优化的数据结构,在某些情况下是一种优化的数据结构和相应的算法,在其他情况下使用通用的结构和通用的算法。比如当一个 HashSet 的内容比较少的时候,可以用数组实现,但内容逐渐增多,再转换成用哈希表实现。如果我们想让使用者不用关心这些实现的细节,使用同样的接口就能享受到更好的性能,那么,就可以考虑用智能指针来统一它的行为。

我们来实现一个智能 StringRustString 在栈上占了 24 个字节,然后在堆上存放字符串实际的内容,对于一些比较短的字符串,这很浪费内存。

参考 Cow,我们可以用一个 enum 来处理:当字符串小于 N 字节时,我们直接用栈上的数组,否则使用 String。但是这个 N 不宜太大,否则当使用 String 时,会比目前的版本浪费内存。

当使用 enum 时,额外的 tag + 为了对齐而使用的 padding 会占用一些内存。因为 String 结构是 8 字节对齐的,我们的 enum 最小 8 + 24 = 32 个字节。

所以,可以设计一个数据结构,内部用1个字节表示字符串的长度,用 30 个字节表示字符串内容,再加上 1 个字节的 tag,正好也是 32 字节,可以和 String 放在一个 enum 里使用,我们暂且称这个 enumSmartString,它的结构如下图所示:

阅读全文 »

使用 std::sync::Mutex 可以多线程共享可变数据,MutexRwLock 和原子类型,即使声明为 non-mut,这些类型也可以修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use std::borrow::Cow;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;

fn main() {
// 用 Arc 来提供并发环境下的共享所有权(使用引用计数)
let metrics: Arc<Mutex<HashMap<Cow<'static, str>, usize>>> =
Arc::new(Mutex::new(HashMap::new()));
for _ in 0..32 {
let m = metrics.clone();
thread::spawn(move || {
let mut g = m.lock().unwrap();

// 此时只有拿到 MutexGuard 的线程可以访问 HashMap
let data = &mut *g;

// Cow 实现了很多数据结构的 From trait,
// 所以我们可以用 "hello".into() 生成 Cow
let value = data.entry("hello".into()).or_insert(0);
*value += 1;

// MutexGuard 被 Drop,锁被释放
});
}

thread::sleep(Duration::from_millis(100));
println!("metrics: {:?}", metrics.lock().unwrap());
}

构造 Double Free

使用 unsafe 特性构造指向同一块内存的两个变量,导致 Double Free

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use std::{mem, ptr};

fn main() {
let mut d = String::from("cccc");
let d_len = d.len();

let mut c = String::with_capacity(d_len);
unsafe {
ptr::copy(&d, &mut c, 1);
};
println!("{:?}", c.as_ptr());

println!("{:?}", d.as_ptr());
d.push_str("c");
println!("{}", d);
}
阅读全文 »

Rust 中使用 std::result::Result 表示可能出错的操作,成功的时候是 Ok(T),而出错的时候则是 Err(E)

1
2
3
4
pub enum Result<T, E> {
Ok(T),
Err(E),
}

通常情况下,E 是实现 std::error::Error 的错误类型:

1
2
3
4
5
6
pub trait Error: Debug + Display {
fn source(&self) -> Option<&(dyn Error + 'static)> { ... }
fn backtrace(&self) -> Option<&Backtrace> { ... }
fn description(&self) -> &str { ... }
fn cause(&self) -> Option<&dyn Error> { ... }
}

我们通常也需要在自己的代码中自定义错误,并且为之手动实现 std::error::Error,这个工作很麻烦,所以就有了 thiserror,自动帮我们生成实现的 std::error::Error 的代码。

而借助于 anyhow::Error,和与之对应的 Result<T, anyhow::Error>,等价于 anyhow::Result<T>,我们可以使用 ? 在可能失败的函数中传播任何实现了 std::error::Error 的错误。

阅读全文 »

一直以来,从 JavaScriptPHPPythonGolang,然后还有linux系统中,无处不见正则表达式的身影,可是一致困扰在POSIXPCRE的概念中,分不清这两个是个啥,今天就来翻翻正则表达式的老底,了解了解正则表达式的前世今生。

Regular ExpressionRegular一般被译为正则、正规、常规。此处的Regular即是规则的意思,Regular Expression即描述某种规则的表达式之意。

正则表达式(英语:Regular Expression,在代码中常简写为regexregexpRE),是计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。在很多文本编辑器里,正则表达式通常被用来检索、替换那些匹配某个模式的文本。

许多程序设计语言都支持利用正则表达式进行字符串操作。例如,在Perl中就内建了一个功能强大的正则表达式引擎。正则表达式这个概念最初是由Unix中的工具软件(例如sedgrep)普及开的。正则表达式通常缩写成regex,单数有regexpregex,复数有regexpsregexesregexen

阅读全文 »
0%