rust语言学习笔记Trait(十三)Borrow、BorrowMut(借用)
Borrow和BorrowMut是 Rust 标准库中用于抽象借用行为的两个核心 trait,位于std::borrow模块。它们为类型系统提供了一种“可以通过引用访问底层数据”的通用契约,尤其适合泛型编程和集合类型的键查找场景。
1、Borrow不可变借用
(1)定义
pubtraitBorrow<Borrowed:?Sized>{fnborrow(&self)->&Borrowed;}Borrow<Borrowed>表示“当前类型可以被不可变地借用为&Borrowed”。
String实现了Borrow<str>,即String可以借用为&strVec<T>实现了Borrow<[T]>,即Vec<T>可以借用为&[T]Box<T>实现了Borrow<T>,即Box<T>可以借用为 `&T
(2)核心特点:语义等价保证
Borrow有一个文档规定的契约:如果类型T实现了Borrow<U>,那么T的借用结果&U必须在Eq、Ord、Hash这些 trait 上与T自身保持行为一致。也就是说,借用前后的值是语义等价的。
这一约束是Borrow与AsRef最本质的区别。AsRef只关注类型转换本身,不关心相等性、哈希值等语义是否一致;而Borrow明确要求这种一致性。
(3)为什么是泛型 Trait?
一个类型可能需要被借用为多种形式。例如String:
- 实现了
Borrow<str>(借用为字符串切片) - 通过 blanket impl 自动实现了
Borrow<String>(借用为自身)
这种泛型设计让同一种类型能以不同的“视角”被安全借用,极大提升了 API 的灵活性。
2、BorrowMut可变借用
(1)定义
rustpub trait BorrowMut<Borrowed: ?Sized>: Borrow<Borrowed> { fn borrow_mut(&mut self) -> &mut Borrowed; }BorrowMut是Borrow的可变版本,并且**自动继承了Borrow**。它允许通过可变引用获取底层数据的可变借用。
(2)常见实现
&mut T实现BorrowMut<T>Vec<T>实现BorrowMut<[T]>RefCell<T>实现BorrowMut<T>(在运行时检查借用规则)
3、HashMap等集合的异构键查找(Borrow的核心用途)
HashMap::get的签名
pubfnget<Q:?Sized>(&self,k:&Q)->Option<&V>whereK:Borrow<Q>,Q:Hash+Eq,{self.base.get(k)}这里的K: Borrow<Q>保证了键类型K(如String)可以被借用为查询类型Q(如str),并且两者的Hash和Eq实现是兼容的,从而确保通过&str计算出的哈希桶与插入时通过String计算出的哈希桶完全一致。
usestd::collections::HashMap;fnmain(){letmutm=HashMap::new();m.insert("aaa".to_string(),111);println!("{:?}",m.get("aaa"));// Some(111)}同样的机制也适用于BTreeMap、HashSet、BTreeSet等标准库集合类型。
4、泛型函数参数的类型扩展
当函数需要接受多种“可借用为某类型”的参数时,Borrow可以完美胜任:
usestd::borrow::Borrow;fnprint_info<T:Borrow<str>>(s:T){letst=s.borrow();// &strprintln!("{}",st);}fnmain(){print_info("aaaaa");// &strprint_info(String::from("bbbbb"));// Stringprint_info(std::borrow::Cow::Borrowed("ccc"));// Cow<str>}函数只需要一个实现,就能同时接受&str、String、Cow<str>等多种类型,且所有类型在Hash/Eq/Ord上的行为保持一致。
5、自定义类型的语义借用
当自定义类型包装了某个值,且希望它在集合中表现得如同底层类型一样时,可以实现Borrow:
usestd::borrow::Borrow;usestd::collections::HashSet;usestd::hash::Hash;#[derive(PartialEq, Eq, Hash)]structMyStruct(String);implBorrow<str>forMyStruct{fnborrow(&self)->&str{&self.0}}fnmain(){letmuts=HashSet::new();s.insert(MyStruct("aaeeaa".to_string()));println!("{}",s.contains("aaeeaa"));}6、BorrowMut在可变上下文中的应用
当需要泛型地修改包装类型的内部值时:
usestd::borrow::BorrowMut;fnmy_fn<T:BorrowMut<i32>>(s:&mutT){*s.borrow_mut()+=2;}fnmain(){letmuta=6;my_fn(&muta);println!("{}",a);// 8letmutb=Box::new(15);my_fn(&mutb);println!("{}",*b);// 17}函数不关心传入的是&mut i32还是Box<i32>,只要它能通过BorrowMut<i32>提供对i32的可变引用即可。
7、BorrowvsAsRef的选择指南
| 维度 | Borrow | AsRef |
|---|---|---|
| 语义保证 | 要求借用值与原值在Eq/Hash/Ord上等价 | 仅做类型转换,无语义保证 |
| 主要用途 | 集合键查找、需要语义等价的场合 | 简单的引用转换、文件路径处理 |
| 典型场景 | HashMap::get、BTreeMap键查询 | File::open、接受Path参数的函数 |
| 实现约束 | 需确保x.borrow() == y.borrow()当且仅当x == y | 无此约束 |
简单记忆:涉及集合查找、比较、哈希等价时用Borrow;仅是类型转换、路径处理等用AsRef。
