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

相关推荐
想你依然心痛3 小时前
内存安全语言在嵌入式中的对比:Rust vs Ada vs SPARK——形式化验证、运行时
安全·rust·spark
恣逍信点3 小时前
《凌微经》助读:本体论根基——“无之自悖”与“形性一体”
人工智能·科技·学习·程序人生·生活·交友·哲学
喜欢打篮球的普通人5 小时前
Trition程序编写:从“Hello CUDA“到“Hello Triton“:向量加法背后的编译黑魔法
开发语言·后端·rust
chh5635 小时前
C++--string
java·开发语言·网络·c++·学习
chnyi6_ya5 小时前
论文阅读笔记:VideoWeaver — 面向智能体长视频生成的技能评估与进化
论文阅读·笔记·音视频
J_yyy5 小时前
基于Reactor的文件管理服务开发笔记
c++·笔记·reactor·多线程编程·muduo
浩瀚地学6 小时前
【Java基础复习】IO流(四)
java·开发语言·经验分享·笔记·学习
疯狂打码的少年6 小时前
【操作系统】板块总结 + 下期预告(软件工程)
笔记·软件工程
心中有国也有家6 小时前
AtomGit Flutter 鸿蒙客户端:一键记录情绪的极致体验
学习·flutter·华为·harmonyos
lauo6 小时前
从52起融资看教育科技新范式:PopLang引擎与Token节点经济如何重塑AI学习终端
人工智能·科技·学习