Borrow 和 BorrowMut 是 Rust 标准库中用于抽象借用行为 的两个核心 trait,位于 std::borrow 模块。它们为类型系统提供了一种"可以通过引用访问底层数据"的通用契约,尤其适合泛型编程和集合类型的键查找场景。
1、Borrow 不可变借用
(1)定义
rust
pub trait Borrow<Borrowed: ?Sized> {
fn borrow(&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 的签名
rust
pub fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V>
where
K: Borrow<Q>,
Q: Hash + Eq,
{
self.base.get(k)
}
这里的 K: Borrow<Q> 保证了键类型 K(如 String)可以被借用为查询类型 Q(如 str),并且两者的 Hash 和 Eq 实现是兼容的,从而确保通过 &str 计算出的哈希桶与插入时通过 String 计算出的哈希桶完全一致。
rust
use std::collections::HashMap;
fn main() {
let mut m = HashMap::new();
m.insert("aaa".to_string(), 111);
println!("{:?}", m.get("aaa")); // Some(111)
}
同样的机制也适用于 BTreeMap、HashSet、BTreeSet 等标准库集合类型。
4、泛型函数参数的类型扩展
当函数需要接受多种"可借用为某类型"的参数时,Borrow 可以完美胜任:
rust
use std::borrow::Borrow;
fn print_info<T: Borrow<str>>(s: T) {
let st = s.borrow(); // &str
println!("{}", st);
}
fn main() {
print_info("aaaaa"); // &str
print_info(String::from("bbbbb")); // String
print_info(std::borrow::Cow::Borrowed("ccc")); // Cow<str>
}
函数只需要一个实现,就能同时接受 &str、String、Cow<str> 等多种类型,且所有类型在 Hash/Eq/Ord 上的行为保持一致。
5、自定义类型的语义借用
当自定义类型包装了某个值,且希望它在集合中表现得如同底层类型一样时,可以实现 Borrow:
rust
use std::borrow::Borrow;
use std::collections::HashSet;
use std::hash::Hash;
#[derive(PartialEq, Eq, Hash)]
struct MyStruct(String);
impl Borrow<str> for MyStruct {
fn borrow(&self) -> &str {
&self.0
}
}
fn main() {
let mut s = HashSet::new();
s.insert(MyStruct("aaeeaa".to_string()));
println!("{}", s.contains("aaeeaa"));
}
6、BorrowMut 在可变上下文中的应用
当需要泛型地修改包装类型的内部值时:
rust
use std::borrow::BorrowMut;
fn my_fn<T: BorrowMut<i32>>(s: &mut T) {
*s.borrow_mut() += 2;
}
fn main() {
let mut a = 6;
my_fn(&mut a);
println!("{}", a); // 8
let mut b = Box::new(15);
my_fn(&mut b);
println!("{}", *b); // 17
}
函数不关心传入的是 &mut i32 还是 Box<i32>,只要它能通过 BorrowMut<i32> 提供对 i32 的可变引用即可。
7、 Borrow vs AsRef 的选择指南
| 维度 | Borrow |
AsRef |
|---|---|---|
| 语义保证 | 要求借用值与原值在 Eq/Hash/Ord 上等价 |
仅做类型转换,无语义保证 |
| 主要用途 | 集合键查找、需要语义等价的场合 | 简单的引用转换、文件路径处理 |
| 典型场景 | HashMap::get、BTreeMap 键查询 |
File::open、接受 Path 参数的函数 |
| 实现约束 | 需确保 x.borrow() == y.borrow() 当且仅当 x == y |
无此约束 |
简单记忆 :涉及集合查找、比较、哈希等价时用 Borrow;仅是类型转换、路径处理等用 AsRef。