笔面试笔记 Rust

Rust笔面试记录

Posted by MenghanStudio on November 2, 2024

0. 所有权机制和借用

所有权机制 一个值只能由一个变量作为所有者 离开作用域即生命周期就会释放 同一时间只能有一个所有者(单一所有权) 借用 只能同时拥有一个可变引用和多个不可变引用

1. 关于Drop

析构 实现:

fn drop<T>(_x: T) {}

_x会在返回前自动丢弃, 编译器不允许显性调用drop,但可以通过Trait mem::drop的方式调用 结构体中优先丢弃_x再依次丢弃_x.one 、 _x.two … 但局部变量会以相反的顺序丢弃

Drop和Copy是排他的

2. Copy 和 Move

let a:T = b;

只有T: Clone + Copy 才会 Copy

T: Clone + !Copy 以及 T: !Clone 为Move

3. HashMap 和 BTreeMap

B-Tree: 自平衡的数,数据有序

HashMap是基于哈希表 BTreeMap是基于BTree BTree是动态分配的,所以不需要指定

4. HashMap和HashSet

hash set,实现为 HashMap,其中值为 ()。

HashSet只有键没有值,是键的集合(无重复)

5. Sized/Send/Sync/Unpin

Sized: 编译时大小已知,编译器会自动为T实现Sized Trait

Send 、 Sync:并发安全

  • unsafe auto trait
  • Send: T跨线程移动,独占访问是线程安全的
  • Sync:&T线程共享,只读共享是安全的
  • 不支持的数据结构: const T/mut T 、UnsafeCell 、Rc tips Arc、Rc 区别在于Arc使用原子操作访问,而Rc是内存操作访问 Arc支持多线程,而Rc是单线程

6. Deref/DerefMut

pub trait Deref {
    // 解引用出来的结果类型
    type Target: ?Sized;
    fn deref(&self) -> &Self::Target;
}

pub trait DerefMut: Deref {
    fn deref_mut(&mut self) -> &mut Self::Target;
}

7. Debug、Display

Debug可以派生宏实现通过”“打印

Display必须手动实现通过”{}”打印

8. 切片 slice

切片是描述一组属于同一类型、长度不确定的、在内存中连续存放的数据结构,动态增长

可以查看某集合的一部分数据

9. 迭代器

使用iter等进行调用,使用map、flier等进行处理

只有最后进行next操作才会真正执行 比如collect()

10. 自定义Hash Key

实现Hash、PartialEq、Eq

11. Error Trait

pub trait Error: Debug + Display {

    /// 此错误的下级来源 (如果有)
    fn source(&self) -> Option<&(dyn Error + 'static)> { ... }
    /// 返回一个描述错误的字符串
    fn description(&self) -> &str { ... }
    /// 指向当前错误造成的原因 这个方法应该返回那个导致当前错误的错误对象。
    fn cause(&self) -> Option<&dyn Error> { ... }
    fn provide<'a>(&'a self, demand: &mut Demand<'a>) { ... }
}

From GPT

  • source(&self) -> Option<&(dyn Error + ‘static)> 方法用来返回此错误的下级来源(如果有的话),返回类型是一个包含 Error trait 的引用的 Option。

  • description(&self) -> &str 方法返回一个描述错误的字符串。

  • cause(&self) -> Option<&dyn Error> 这是旧版本的方法,现在一般不建议使用。

  • provide<’a>(&’a self, demand: &mut Demand<’a>) 方法接受一个 demand 参数,并且返回一个生命周期绑定到 self 的一个引用。

12. 闭包

如果闭包不使用move语义则借用上下文变量

闭包约束为Send + ‘static的原因

Send 为了线程安全,可以跨线程使用 ‘static 确保不会发送悬垂引用以及生命周期问题

FnOnce 只能调用一次 涉及到捕获并move时会被判断为FnOnce 例如

    let c = move |greeting: String| (greeting, name); // FnOnce 
	let c = move |greeting: String| (greeting, name.clone()); // 非FnOnce

因为 Fn 继承 FnMut , 而 FnMut 继承了 FnOnce FnOnce是Fn FnMut 的super trait

所以当Fn 或者 FnMut 当作 impl FnOnce 传入时,会被转换为FnOnce,因而只能调用一次

Fn 不同于 FnMut

Fn传入的是&self FnMut为&mut self 代表捕获的参数可被改变即mut

13. 泛型

对于结构

struct id<T>{
	value: i32,
}

struct User{
	id: id<Self>,
}

struct Pro{
	id: id<Self>,
}

会编译不通过

因为Struct id并没有使用T而编译器不容许这样的当前未使用在未来可能使用的情况

改为以下就能通过编译

struct id<T>{
	value: i32,
	_tag: PhantomData<T>,
}

PhantomData幽灵数据,编译器不会实际产生参数,而是用于编译检查提示编译器T会用到

14. 并发

无畏并发

避免

  1. while 取锁
  2. 修改左值加锁

在次期间产生的问题,引入原子操作CAS(比较与修改操作, 指令读取某个内存地址,判断其值是否等于某个前置值,如果相等,将其修改为新的值)

while self
	.locked
	// compare_exchange Rust 提供的 CAS 操作
	//                             保证读操作对应读false  保证写操作对应赋值为true
	.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)// 比较是否为false即能够取锁,是则加锁(true)并向下执行反之继续循环
	.is_err() {}


//需要的操作执行完后解锁同理
self.locked.store(false, Ordering::Release);

优化

while self
    .locked
    .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
    .is_err()
{
    // 性能优化:compare_exchange 需要独占访问,当拿不到锁时,我们
    // 先不停检测 locked 的状态,直到其 unlocked 后,再尝试拿锁
    while self.locked.load(Ordering::Relaxed) == true {}
}

15. Condvar 与 Mutex

Mutex 用于保证条件在读写时互斥,Condvar 用于控制线程的等待和唤醒

let pair = Arc::new((Mutex::new(false), Condvar::new()));
let (lock, cvar) = &*pair;

主线程

let mut started = lock.lock().unwrap();
cvar.wait(started).unwrap(); // 堵塞等待started改变并返回改变的值

16. 异步

无畏并发

Future 的调度是协作式多任务 协程

tokio::async 会运行Future await会等待Future执行完毕

tips: Framed::new(stream, LinesCodec::new());

tokio_util::codec::Framed的解码器,将编码以某种方式解码以进行I/O操作

17. 通道 channel

let (tx, rx) = channel::<i32>();
let tx1 = tx.clone();
thread::spaw(move || {
	tx.send(1).unwrap();
 });
thread::spawn(move || {
	tx1.send(2).unwrap();
});
rx.recv().unwrap();