rust语言学习笔记Trait(十三)Borrow、BorrowMut(借用)

BorrowBorrowMut 是 Rust 标准库中用于抽象‌借用行为 ‌的两个核心 trait,位于 std::borrow 模块。它们为类型系统提供了一种"可以通过引用访问底层数据"的通用契约,尤其适合泛型编程和集合类型的键查找场景。‌

1、Borrow 不可变借用

(1)定义

rust 复制代码
pub trait Borrow<Borrowed: ?Sized> {
    fn borrow(&self) -> &Borrowed;
}

Borrow<Borrowed> 表示"当前类型可以被不可变地借用为 &Borrowed"。

  • String 实现了 Borrow<str>,即 String 可以借用为 &str
  • Vec<T> 实现了 Borrow<[T]>,即 Vec<T> 可以借用为 &[T]
  • Box<T> 实现了 Borrow<T>,即 Box<T> 可以借用为 `&T

(2)核心特点:语义等价保证

Borrow 有一个‌文档规定的契约 ‌:如果类型 T 实现了 Borrow<U>,那么 T 的借用结果 &U 必须在 EqOrdHash 这些 trait 上与 T 自身保持行为一致。也就是说,‌借用前后的值是语义等价的‌。

这一约束是 BorrowAsRef 最本质的区别。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;
}

BorrowMutBorrow 的可变版本,并且‌**自动继承了 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),并且两者的 HashEq 实现是兼容的,从而确保通过 &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)
}

同样的机制也适用于 BTreeMapHashSetBTreeSet 等标准库集合类型。

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>
}

函数只需要一个实现,就能同时接受 &strStringCow<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::getBTreeMap 键查询 File::open、接受 Path 参数的函数
实现约束 需确保 x.borrow() == y.borrow() 当且仅当 x == y 无此约束

简单记忆 ‌:涉及集合查找、比较、哈希等价时用 Borrow;仅是类型转换、路径处理等用 AsRef

相关推荐
凌云若寒13 小时前
SENTINEL软件
学习·sentinel·产品经理·制造·软件需求
星轨初途13 小时前
【C++ 进阶】list 核心机制解析及 vector 巅峰对决
开发语言·数据结构·c++·经验分享·笔记·list
朝阳58113 小时前
树莓派跑了个 M3U8 下载服务,内存从 600MB 降到 2MB
性能优化·rust
鹏北海-RemHusband13 小时前
Go 包管理笔记 — 面向 JS/TS 前端开发者
笔记·golang
Chloeis Syntax14 小时前
JavaEE初阶学习日记(3)---网络初认识
java·网络·笔记·学习
智者知已应修善业14 小时前
【proteus仿真CD4511抢答器4路】2024-5-13
驱动开发·经验分享·笔记·硬件架构·proteus·硬件工程
kinl201814 小时前
cs236_note1 (lec5-lec6) VAEs
笔记
还在忙碌的吴小二14 小时前
Spring Boot Examples 学习示例集新手入门指南
java·spring boot·后端·学习·spring
吃好睡好便好14 小时前
说说如何爱护头发
学习·生活