个人网站备案取名/seo优化方案报价

个人网站备案取名,seo优化方案报价,网站建设实战教程,网络推广文章的方法深入再谈智能指针、AsRef引用与Borrow借用 这是一个具有深度的技术主题。每次重温其理论知识&#xff0c;都会有新的领悟。大约 2 年前&#xff0c;我曾就这一技术方向撰写过另一篇短文《从类型转换视角&#xff0c;浅谈Deref<Target T>, AsRef<T>, Borrow<T&g…

深入再谈智能指针、AsRef引用与Borrow借用

这是一个具有深度的技术主题。每次重温其理论知识,都会有新的领悟。大约 2 年前,我曾就这一技术方向撰写过另一篇短文《从类型转换视角,浅谈Deref<Target = T>AsRef<T>Borrow<T>From<T> trait差异》。在那篇文章中,我依据当时的经验知识,归纳了自定义智能指针、引用和借用的代码实现方式,并罗列了一些原则。但那种“快餐式”的知识分享存在不少遗憾。两年后的今天,我凭借更多的知识积累,利用春节长假的时间,重拾此话题,力求通过一篇长文将(广义的)Rust“引用”阐述清楚。

名词解释

为了统一对非具名抽象概念的理解,我们先对几个Rust常见项的中文表述进行约定。

@Rustacean

一般发音为/ˌrʌstəˈsiːən/。它是由单词Rust和词根-acean(表示“……的人”的后缀)构成。它被用作Rust技术栈程序员的自称。

自定义引用

代表std::convert::AsRef<F>std::convert::AsMut<F>的特征实现类。例如,泛型类型参数<T: AsRef<F>>表示类型TF的自定义引用。普通引用、自定义引用与智能指针之间的对比关系可以被归纳为

输入图片说明
输入图片说明

自定义借用

代表std::borrow::Borrow<F>std::borrow::BorrowMut<F>特征的实现类。比如,泛型类型参数<T: Borrow<F>>表示类型TF的自定义借用,也可读作“T被借入作为F”。

胖引用

代表动态分派特征对象trait Object的普通引用。例如,&dyn AsRef<std::path::Path>

胖智能指针

代表动态分派特征对象trait Object的智能指针。例如,Box<dyn AsRef<std::path::Path>>

胖指针

是对胖引用与胖智能指针的统称。

泛型覆盖实现

英文全称是Blanket Implementation,它是作用于泛型类型参数的特征实现块。简单来说,泛型覆盖实现允许 @Rustacean 为一批满足特定泛型条件的类型统一实现某个特征,而无需为每个具体类型单独实现,这样可以减少代码重复。例如,impl<T: ?Sized> Borrow<T> for T { /* 特征成员方法的实现 */ }。该定义的关键在于特征实现块的“受体”不是一个具体类型,而是满足泛型参数限定条件的一批具体类型。

特征成员方法

对应英文词条是trait method,指在特征定义内声明的成员方法以及在特征实现块内实现的成员方法。

智能指针内部值的数据类型

<T: Deref<Target = F>>为例,在文章中,

  • 要么,将内部值数据类型记作F,出于文字简洁的目的。

  • 要么,将其记作<T as Deref>::Target关联类型,以强调当前是在智能指针讨论上下文内。

<T: ?Copy>特征限定条件

首先,这个“泛型(类型)参数限定条件”记法从语法规则上肯定是的,因为即便最新版rustc也仅只认识?Sized一个“不确定”限定条件。

其次,<T: ?Sized>代表“直至编译时,还不能确定被限定的泛型(类型)参数T是否有可度量的字节大小”。即,类型TDST的。

模仿?Sized,我想用<T: ?Copy>表示“直至编译时,还不能确定被限定的泛型(类型)参数T是否可复制”。注:【可克隆】不等同于【可复制】 — 这是两码事。

概述

接下来,文章正文将从下图《自定义引用、自定义借用与智能指针的解引用方式分类》开始逐层深入展开论述。

输入图片说明
输入图片说明

解引用的触发方式不同

首先,对“自定义引用”与“自定义借用”的解引用操作都要求 @Rustacean 必须手动调用对应的特征成员方法(

  • 解引用自定义引用

    • <T as AsMut>::as_mut(&mut T) ➜ &mut F — 来自【标准库】对trait AsMut的直接实现

    • <&mut T as AsMut>::as_mut(&mut &mut T) ➜ &mut F — 来自【标准库】对trait AsMut泛型覆盖实现

    • <T as AsRef>::as_ref(&T) ➜ &F — 来自【标准库】对trait AsRef的直接实现

    • <&T as AsRef>::as_ref(&&T) ➜ &F — 来自【标准库】对trait AsRef泛型覆盖实现

    • <&mut T as AsRef>::as_ref(&&mut T) ➜ &F — 来自【标准库】对trait AsRef泛型覆盖实现

    • 只读

    • 可变

  • 解引用自定义借用

    • [只读]<T as Borrow>::borrow(&T) ➜ &F

    • [可变]<T as BorrowMut>::borrow_mut(&mut T) ➜ &mut F

)才能完成从T/&T/&mut T&F/&mut F的(纯手动)解引用类型转换。举个[例程 1]

use ::std::path::{ Path, PathBuf };
fn print<K: AsRef<Path>>(file_path: K) {// 注意:由于编译器不会自动生成对 file_path.as_ref() 成员方法的调用语句,//      因此开发者必须显式地手动调用 <K as AsRef>::as_ref(&K)。let file_path: &Path = file_path.as_ref();println!("文件路径= {}", file_path.display());
}
fn main() {let string_2_path = String::from("/etc/<String>");let path_buf_2_path = PathBuf::from("/etc/<PathBuf>");// &T ➜ &F 在 print() 函数调用之后,保留变量所有权不被消费掉。print(&string_2_path);   // &String ➜ &Pathprint(&path_buf_2_path); // &PathBuf ➜ &Path// &&T ➜ &Fprint(&&string_2_path); // &&String ➜ &Pathprint(&&path_buf_2_path); // &&PathBuf ➜ &Path// T ➜ &F 在 print() 函数调用之后,消耗掉了变量所有权。print(string_2_path); // String ➜ &Pathprint(path_buf_2_path); // PathBuf ➜ &Path
}

前面我们介绍了解引用自定义引用和自定义借用的方式,接下来看看对“智能指针”引用的解引用处理有什么不同。

对“智能指针”引用的解引用处理(&T ➜ &F&mut T ➜ &mut F)则是由(前端)编译器rustc自动完成的。具体地讲,编译器会在语义分析过程中

  1. 先识别出

  • 在成员方法调用语句中,对应&self&mut self形参的智能指针引用实参&T&mut T,以及

  • 在函数、闭包、成员方法调用语句中,对应其它普通引用形参的智能指针引用实参&T&mut T

再将&T&mut T原地替换为对它们的解引用特征成员方法调用表达式

  • <T as Deref>::deref(&T) ➜ &F

  • <T as DerefMut>::deref_mut(&mut T) ➜ &mut F

接着,判断上一步返回值中的F是否又是智能指针?

  • 要么,解引用后的&F已匹配于函数形参的数据类型

  • 要么,F已不再是智能指针,且不能进一步解引用了

  1. 若是,则绕回第二步将F当作T接着递归解引用F。递归处理会持续进行,直至

    若递归解引用被持续执行多次,那么在原来&T实参位置上会出现一条对T.deref()成员方法调用链条,或在原来&mut T实参位置上会出现一条对T.deref_mut()成员方法调用链条。

  2. 否则,则直接进行下一步。

最后,判断&F是否匹配于函数形参的数据类型

  1. 若匹配,则此段编译成功,并执行后续编译处理。

  2. 否则,整体编译失败

上述文字描述较为抽象,一图胜千言,请参考下图。注意:执行图中操作的行为主体是rustc,而不是 @Rustacean。

输入图片说明
输入图片说明

再举个[例程2],佐证上述理论总结。

use ::std::path::{ Path, PathBuf };
fn print(file_path: &Path) {println!("文件路径= {}", file_path.display());
}
fn main() {let mut path_buf = PathBuf::from("/etc/<&PathBuf>");// 场景一:在成员方法调用中,对 &self 与 &mut self 的自动解引用// (1) &mut T ➜ &mut F 修改智能指针内部值的内部状态信息path_buf.push("usr");// (2) &T ➜ &F 读取智能指针内部值的内部状态信息println!("文件路径= {}", path_buf.display());// 场景二:在普通函数调用中,对智能指针引用的自动解引用// 模拟了 OOP 编程中的函数重载。print(&path_buf); // &T ➜ &Flet path: &Path = path_buf.as_path();print(path);// 不存在 T ➜ &F,所以下面会编译失败// print(PathBuf::from("/etc/<PathBuf>"));
}

最后,对“智能指针”所有权变量的解引用(T ➜ F)就得具体问题具体分析了。@Rustacean 需分三步渐进推导:

第一步:确认如何处理智能指针的解引用结果F

  • 是要【替换】智能指针的内部值。例如,赋值语句*t = new_value — 千万别忘了以let mut声明智能指针变量t

  • 还是【拾出】内部数据的所有权值。例如,let value = *t

第二步:若是后者(拾出所有权值),再接着判断智能指针内部值的类型F是否满足trait Copy限定条件?

  • where F: Copy成立,那么被拾取出的所有权值其实是F值的复本

  • F?Copy,那么rustc就会判定本次编译操作整体失败。知识点回顾,Rust变量的所有权规则禁止从外层数据结构搬移出它内部字段的所有权值,因为禁止“掏空”数据结构留空坑位null

第三步,确认拾出智能指针内部值F的手段方式

  • 手动解引用】使用一元解引用操作符*拾出所有权值。例如,let f_copy = *t;

  • 自动解引用】调用F数据结构上“消费”self所有权的成员方法。例如,PathBuf::into_boxed_path(self) ➜ Box<Path>

    此处"自动解引用"的本质就是rustcMIR中间代码生成前,将智能指针成员方法调用语句中对应self形参的智能指针实参T替换为对T的解引用表达式*<T as Deref>::deref(&T)

下面是对【手动解引用】过程的详细图解

输入图片说明
输入图片说明

此外,使用一元解引用操作符*替换(可变)智能指针内部值的代码套路可概括为:

  1. 声明和初始化一个可变智能指针变量。

  2. 在解引用该智能指针变量的同时,对其做赋值处理。这对Cpp语法的一比一复刻真让人看着就亲切!

用伪码表示大约是如下的样子:

// 1. 假设结构体 SmartPointer实现了 Deref<Target = PathBuf> 特征。
let mut path_buf_sp = SmartPointer::new(PathBuf::from("/etc/abc"));
// 2. 该[解引用+赋值]语句总是能编译成功,无论 <SmartPointer as Deref>::Target 是否满足 trait Copy 限定条件
*path_buf_sp = PathBuf::from("/etc/abc1");

将上述“解引用智能指针所有权变量(T ➜ F)”的知识点都捏合至[例程3],可向读者呈现:

// 注释内容同样很精彩和更重要。
mod generic_deref {use ::std::ops::{ Deref, DerefMut };pub struct GenericDeref<T> {value: T}impl<T> GenericDeref<T> {pub fn new(value: T) -> Self {GenericDeref { value }}}impl<T> Deref for GenericDeref<T> {type Target = T;fn deref(&self) -> &Self::Target {&self.value}}impl<T> DerefMut for GenericDeref<T> {fn deref_mut(&mut self) -> &mut Self::Target {&mut self.value}}
}
use ::std::path::PathBuf;
use generic_deref::GenericDeref;type Deref1 = GenericDeref<PathBuf>;
type Deref2 = GenericDeref<i32>;fn main() {let mut i32_wrapper = Deref2::new(1);// 用法1:替换智能指针的内部字段值。这总是能够被成功地完成*i32_wrapper = -1;// 用法2:拾取出智能指针内部字段的所有权值。// 因为 <T as Deref2>::Target 是满足 trait Copy 限定条件的 i32 类型,// 所以如下解引用操作才能成功通过编译:// (1) 调用"消耗所有权的"成员方法,和自动解引用。assert_eq!(0, i32_wrapper.leading_zeros());// (2) 使用解引用操作符 * 手动解引用。解引用结果是智能指针内部值的复本。assert_eq!(-1, *i32_wrapper);let mut path_buf_wrapper = Deref1::new(PathBuf::from("/etc/abc"));// 用法1:替换智能指针内部值。这总是能够被成功地完成*path_buf_wrapper = PathBuf::from("/etc/abc1");// 用法2:拾取出智能指针内部字段的所有权值。// 因为 <T as Deref2>::Target 未满足 trait Copy 限定条件,// 所以如下解引用操作未能通过编译:// (1) 调用消耗所有权的成员方法,自动解引用。// let path_buf = path_buf_wrapper.into_boxed_path();// (2) 使用解引用操作符,手动解引用。// let path_buf = *path_buf_wrapper;
}

编译时,触发解引用的时间窗口不同

输入图片说明
输入图片说明

由上图可总结出

  1. 解引用类型转换“自定义引用”和“自定义借用”的代码是由人工手动添加于程序文件编写阶段;而

  2. 解引用“智能指针”的处理逻辑则是由(前端)编译器rustc条件地注入于编译过程中的“语义分析”之后和“MIR中间代码生成”之前。

我甚至猜测:“rustc对智能指针追加的解引用表达式不是人类可读的Rust代码,而是面向语义分析器的AST子节点树”。

解引用的技术原理不同

自定义引用

对“自定义引用”的解引用处理是建立在Rust泛型类型系统的“通用底盘”基础之上。凭借FST静态分派机制,Rust能模拟出OOP的同一函数形参兼容于不同类型实参的“重载”效果。举个例子,正是因为【标准库】预置了类型&strStringstd::path::PathBuftrait AsRef<std::path::Path>的特征实现和定义这些类型可作为std::path::Path的自定义引用,所以[例程4]中模仿多态的函数调用才有能通过编译检查

use ::std::{ convert::AsRef, path::{ Path, PathBuf } };
//
// 因为该函数的唯一【形参】兼容于任何“可解引用为 &Path 的”自定义引用【实参】。
//
fn print_fst<T: AsRef<Path>>(file_path: T) {let file_path: &Path = file_path.as_ref(); // 手动解引用,而不是自动解引用println!("文件路径fst= {}", file_path.display());
}
fn main() {let str_file_path = "/etc/<str>";let string_file_path = "/etc/<string>".to_string();let path_buf = PathBuf::from("/etc/<PathBuf>");//// 所以,形似 OOP 函数重载的多态调用语句才有机会出现在 Rust 程序内。//print_fst(str_file_path);    // &str ➜ &Path// 美中不足,这都是按所有仅传值。print_fst(string_file_path); // String ➜ &Pathprint_fst(path_buf);         // PathBuf ➜ &Path// 因消费掉了变量所有权,所以 string_file_path 与 path_buf 都将不可再被访问
}

又因为“一味地按所有权传值”是件非常“内耗的”程序设计选择,所以故事并没有止步于此。【标准库】还为“自定义引用”的引用(甚至,引用的引用递归)提供了泛型覆盖实现,以使自定义引用的引用们(比如,&T&&T&&mut T&mut T&mut &mut T)继续是初始被引用值F的“自定义引用”。以下是摘录于【标准库】源码的泛型覆盖实现块签名:

  1. impl<T: ?Sized, F: ?Sized> AsRef<F> for &T where T: AsRef<F> { .. }

    读作:若类型T是类型F只读自定义引用,那么T只读引用&T也同样是类型F只读自定义引用。

  2. impl<T: ?Sized, F: ?Sized> AsMut<F> for &mut T where T: AsMut<F> { .. }

    读作:若类型T是类型F可变自定义引用,那么T可变引用&mut T也同样是类型F的可变自定义引用。

依旧感觉文字描述苍白无力,我还是接着画张图吧!

输入图片说明
输入图片说明

由此,我们就能将按所有权传值的[例程4]升级改造为仅按引用传值的[例程5]。

use ::std::{ convert::AsRef, path::{ Path, PathBuf } };
//
// 因为该函数的唯一【形参】兼容于任何“可解引用为 &Path 的”自定义引用【实参】。
//
fn print_fst<T: AsRef<Path>>(file_path: T) {let file_path: &Path = file_path.as_ref(); // 手动解引用,而不是自动解引用println!("文件路径fst= {}", file_path.display());
}
fn main() {let string_file_path = "/etc/<string>".to_string();let path_buf = PathBuf::from("/etc/<PathBuf>");//// 因为【标准库】预置了对"自定义引用"的引用的泛型覆盖实现,所以//// 1. AsRef<F> 实现类也就具备了部分“自动解引用”能力,和能够按引用传值。print_fst(&string_file_path); // &String ➜ &Pathprint_fst(&path_buf);         // &PathBuf ➜ &Path// 2. 甚至,引用的引用也能传值。print_fst(&&string_file_path); // &&String ➜ &Pathprint_fst(&&path_buf);         // &&PathBuf ➜ &Path// 最后,再消费掉变量的所有权print_fst(string_file_path); // String ➜ &Pathprint_fst(path_buf);          // PathBuf ➜ &Path
}

阅读至此,擅长发散思维的读者一定已经开始掂量如何将泛型覆盖实现的效用推广至自定义引用的

  • DST动态分派胖指针,以及

  • 智能指针

,而不仅限于普通引用。棒!这个思路是十分正确的,而且它对胖引用(比如,&dyn AsRef<F>)也确实有效。举个[例程6]

use ::std::{ convert::AsRef, ops::Deref, path::{ Path, PathBuf } };
//
// 该函数的唯一【形参】兼容于任何“可解引用为 &Path 的”自定义引用【实参】。
//
// 静态分派形参
fn print_fst<T: AsRef<Path>>(file_path: T) {let file_path: &Path = file_path.as_ref(); // 手动解引用,而不是自动解引用println!("[静态分派]文件路径fst= {}", file_path.display());
}
// 动态分派形参
fn print_dst(file_path: &dyn AsRef<Path>) {let file_path: &Path = file_path.as_ref(); // 手动解引用,而不是自动解引用println!("[动态分派][普通引用]文件路径fst= {}", file_path.display());
}
fn main() {let string_file_path = "/etc/<string>".to_string();let path_buf = PathBuf::from("/etc/<PathBuf>");//// 因为【标准库】预置了对"自定义引用"的引用的泛型覆盖实现,所以//// 1. 对动态分派的函数形参,其实参也能兼容。print_dst(&string_file_path);print_dst(&path_buf);// 2. 对静态分派的函数形参,其实参依旧能“自动解引用”。print_fst(&string_file_path);print_fst(&path_buf);
}

但这对智能指针就不一定成立了,无论它是动态分派的特征对象(例如,Box<dyn AsRef<F>>),还是静态分派的泛型(例如,Box<T> where T: AsRef<F>)。至少由【标准库】直供的智能指针数据结构都定义了自己专属的trait AsRef<F>trait AsMut<F>trait Borrow<F>trait BorrowMut<F>实现块和为它们定义了独立解释的语义功能。于是,智能指针实例自身的特征成员方法(比如,<Box<T> as AsRef>::as_ref(&Box<T>))就会遮蔽掉其内部值上的同名成员方法<Deref::Target as AsRef>::as_ref(&Deref::Target),进而阻断模拟“自动解引用”的泛型匹配。再举个[例程7]

use ::std::{ convert::AsRef, ops::Deref, path::{ Path, PathBuf } };
//
// 该函数的唯一【形参】兼容于任何“装箱于Box<T>智能指针且可解引用为 &Path 自定义引用的”实参。
//
// 静态分派形参
#[allow(dead_code)]
fn print_fst<T: AsRef<Path>>(file_path: T) {let file_path: &Path = file_path.as_ref(); // 手动解引用,而不是自动解引用println!("[静态分派]文件路径fst= {}", file_path.display());
}
// 动态分派形参
fn print_dst(file_path: Box<dyn AsRef<Path>>) {let file_path: &Path = file_path.deref().as_ref(); // 手动解引用,而不是自动解引用println!("[动态分派][智能指针]文件路径fst= {}", file_path.display());
}
fn main() {//// 定义“装箱于Box<T>”智能指针的 &Path 自定义引用。//let string_file_path = Box::new("/etc/<string>".to_string());let path_buf = Box::new(PathBuf::from("/etc/<PathBuf>"));// 1. 没有自动解引用,因为`<Box as AsRef>::as_ref(&Box)`的返回值是//    &String 与 &PathBuf,而不是 &Path。所以,下面六条语句都会编译失败// print_fst(&string_file_path);// print_fst(&path_buf);// print_dst(&string_file_path);// print_dst(&path_buf);// print_fst(string_file_path);// print_fst(path_buf);// 2. 即便是直接传智能指针的所有权值,对胖智能指针的拆箱也得一步变两步完成//    (1) 从 Box<T> 中拆箱出 自定义引用//    (1) 从 自定义引用  拆箱出 &Path//    最后,才能调用 Path 类型上的成员方法print_dst(string_file_path);print_dst(path_buf);
}

别慌张,办法总比问题多!就 @Rustacean 本地定义的智能指针而言,我在文章正文最后一节分享了一段解决此痛点的《智能指针【条件化特征实现块】补丁》,并对其从工作原理至可复用宏定义都做了讲解。

智能指针

*.rs程序文件编译过程中,(前端)编译器rustc会在AST语义分析后、MIR生成前,对满足特定条件的智能指针实例“定点”注入解引用特征成员方法的调用表达式。

判断某个实例是否是智能指针?

根据数据结构是否实现过trait Deref特征,辨认智能指针实例。因为trait DerefMuttrait Deref的子特征,所以“粗线条地”识别智能指针,就不用专门对trait DerefMut做限定条件检查。

“定点”注入解引用表达式的代码位置筛选条件
  • 要么,智能指针实例正作为一元解引用操作符*的操作数,且该指针关联类型Deref::Target满足trait Copy限定条件。即,智能指针内部值是可复制的

    • 场景复现,请参考[例程3]的第31与42行。

    • <Deref::Target: ?Copy>,则编译失败,因为取不出智能指针内部值的复本来。

  • 要么,智能指针实例正作为函数、成员方法、甚至闭包调用语句中非对应self/&self/&mut self形参引用类型实参

    • 场景复现,请参考[例程2]的第15行。

  • 要么,智能指针实例正作为该指针关联类型Deref::Target成员方法调用语句中对应&self/&mut self形参引用类型实参

    • 场景复现,请参考[例程2]的第10与12行。

  • 要么,智能指针实例正作为该指针关联类型Deref::Target成员方法调用语句中对应self形参所有权实参,且Deref::Target还得满足trait Copy限定条件。

    • 场景复现,请参考[例程3]的第36行。

    • <Deref::Target: ?Copy>,则编译失败。

智能指针的语义功能

虽然智能指针可作为模仿OOP编程风格(比如,继承)的反模式语法糖,但它的首要任务却是从如下两个维度(同时或之一地)增强其Deref::Target类型内部值的语义功能:

  • 所有权关系 ownership。例如,Rc<T>被当作其内部值T的“引用”,和按所有权传值,并摆脱普通引用规则的诸多限制。

  • 内存存储位置 storage。还是以Rc<T>为例,它腾挪内部值T从【栈】内存至【堆】内存。然后,构造多个指向相同【堆】数据的【栈】(所有权)“引用”变量。

自定义借用

对“自定义借用”的解引用处理也是建立在Rust泛型类型系统的“通用底盘”基础之上的。但它的首要用途已不再是模仿函数重载多态性的语法糖,而是(以<T: Borrow<F>>为例)

  1. 强制【借用T】与【被借用的值F】都对外呈现相同的:

  • 哈希值 --- 意味着处理逻辑一致的trait std::hash::Hash实现

  • 等价关系 --- 意味着处理逻辑一致的trait std::cmp::Eq实现

  • 排序关系 --- 意味着处理逻辑一致的trait std::cmp::Ord实现

督促 @Rustacean 对【借用T】与【被借用的值F】编写处理逻辑一致的特征实现块,当需要对它们实现除std::borrow::Borrowstd::borrow::BorrowMut之外的特征时。比如,我们一般预期【借用T】与【被借用的值F】都能被print!宏打印输出相同的内容,通过给它们编写处理逻辑一致的trait std::fmt::Display特征实现块。

换句话说,只要某个类型T实现了trait Borrow<F>trait BorrowMut<F>,那么类型TF

  • 【必有】相同的“哈希值”和“判等+排序”偏好。

  • 【可选但有理由期望】对其它trait的实现,也有处理逻辑一致的特征实现块。

这是一套非常重要的约束规则。

同时从概念冠名上,

  • TF的自定义借用,和

  • T被借用作为F

现实意义

令我恍然大悟的是,普通引用&T/&mut T与被引用值T之间处理行为的高度一致性也是源于这套【自定义借用】约束规则,因为【标准库】为任何普通引用都预置了如下对trait Borrow<F>trait BorrowMut<F>泛型覆盖实现

  • impl<T: ?Sized> Borrow<T> for &T { .. }

    读作:任何类型T只读引用 &T同时也T自身的只读自定义借用

  • impl<T: ?Sized> BorrowMut<T> for &mut T { .. }

    读作:任何类型T可变引用&mut T同时也T自身的可变自定义借用

于是才有我凭经验知识与死记硬背才掌握的经验法则:

“比较两个值的引用是否相等”就等效于“比较该引用背后的所有权值是否相等”,而不是匹配这两个引用是否指向同一处内存地址。即,assert!(&1 == &1)等效于assert!(1 == 1),而不是assert!(std::ptr::eq(&1, &1))

举个[例程8]更形象。

use ::std::{ path::PathBuf, ptr };
fn main() {let path1 = PathBuf::from("/a/b/c");let path2 = PathBuf::from("/a/b/c");// 根据自定义借用的限定规则,比较引用就相当于比较被引用的值,assert!(&path1 == &path2); // 断言成功assert!(path1 == path2); // 断言成功// 而不是匹配引用的内存地址是否是指向的同一处。assert!(ptr::eq(&path1, &path2)); // 断言失败
}

到这,发散思维的读者必定又要发问:“这套约束规则对【智能指针】有啥影响呀?”。我快速回答:“没影响,因为【标准库】未提供面向Deref(Mut)限定条件的Borrow(Mut)泛型覆盖实现”。另外,只要Deref(Mut)实现类不定义与其关联类型Deref::Target重名的成员方法,那么rustc自动解引用机制就能保证:

  1. 智能指针不仅能点出其内部值的全部pub成员方法,更会保持与内部值的trait method完全一致的处理行为。但,很可惜,

  2. 当面对泛型类型匹配时,智能指针却不能“透明”呈现出其内部值的trait“形状”。还是讲,办法总比问题多。采用ambassador crate,借助 crate 作者预定义的过程宏,便可:

    1. 给智能指针数据结构增补实现其内部值才实现的trait

    2. 将智能指针的trait method实现委托给内部值实现的trait method

      于是,从宏观效果来看,智能指针与它的内部值既对外呈现相同的泛型trait“形状”,还保持了一致的trait method处理逻辑,这简直是完美致极!可是,这一切就已经与rustc自动解引用没关系了。

故事依旧未结束。甚至,一个惊艳接着另一个惊艳。【自定义借用】的约束规则还大幅提升了MapSet类“可检索”数据结构的查询效率。简单地讲,【自定义借用】允许 @Rustacean

  • 既能,将所有权值作为数据保存于MapSet数据结构中,以满足容器占有子元素的要求。

  • 又可,使用更轻量级的自定义借用(算是广义引用的一种)作为对键数据匹配查询的搜索条件。

进而避免,为每次检索操作,都重新构造一个所有权值作为【键】的查询条件 — 内存效率极低。举个[例程10],让读者更形象地体会一下

use ::std::collections::HashMap;
fn main() {let mut map: HashMap<String, i32> = HashMap::new();// 向 Map 内保存字符串的所有权作为【键】let key123 = String::from("123");let key124 = String::from("124");map.insert(key123, 123);map.insert(key124, 124);// 在这一步涉及了 trait Borrow<F> 的两个知识点:// 1. 因为 String 是 &str 的自定义借用,所以 String 与 &str//    有相同的等价偏好与 hash 值。于是,由 String 为内容保存//    的键,就能由 &str 为检索条件给匹配出来。// 2. 因为 &i32 就是 i32 的自定义借用,&i32 与 i32 就具备相//    同的等价偏好,所以就允许由 &i32 引用之间的判等来断言其//    背后 i32 值是否相等。assert_eq!(map.get("123"), Some(&123));
}

写到这里,我有感而发:“哪有什么天生的易用体质,只是有【标准库】替我们负重前行”。

反身性Reflexivity

相比于自定义引用,自定义借用还具备“反身性Reflexivity”,因为【标准库】为任何类型都预置了如下对trait Borrow<F>trait BorrowMut<F>泛型覆盖实现

  • impl<T: ?Sized> Borrow<T> for T { .. }

    读作:任何类型T就是它自身的只读自定义借用

  • impl<T: ?Sized> BorrowMut<T> for T { .. }

    读作:任何类型T就是它自身的可变自定义借用

为了证明反身性的存在,我再举个[例程9]佐证一下

use ::std::{ borrow::Borrow, path::PathBuf };
// 注意下面函数形参的类型不是引用 &PathBuf 哟,而是像所有权值的类型!
fn print_fst<T: Borrow<PathBuf>>(file_path: T) {let file_path: &PathBuf = file_path.borrow(); // 手动解引用,而不是自动解引用println!("文件路径fst= {}", file_path.display());
}
fn main() {let path_buf = PathBuf::from("/etc/<PathBuf>");// 1. 任何类型 T 的普通引用 &T 同时也是该类型 T 自身的自定义借用。//    所以,即便函数的形参不是引用,我们也能将引用作为它的实参。这是兼容的不违和!print_fst(&path_buf);// 2. trait Borrow<F> 支持【反身性】。即,//    任何类型 T 就是它自身的"自定义借用"print_fst(path_buf);
}

在上段代码中,第3行的函数签名以引用&PathBuf为形参类型,而是将所有权的泛型类型T作为形参类型。但

  1. 第11行既能传递引用&path_buf作为实参 — 自定义借用的泛型覆盖实现。同时,

  2. 第14行又能传所有权变量path_buf作为实参 — 自定义借用的反身性

因为它们都是原始变量path_buf的【自定义借用】。

智能指针的条件化AsRef特征实现块

不同于普通引用,智能指针被允许定义它自己的(特征)实现块和实现它自己的(特征)成员方法。于是,智能指针内部值(Deref::Target)的同名成员方法就会被智能指针自身的成员方法给遮蔽掉和失效,因为rustc在对self/&self/&mut self的实参执行解引用处理前,就检索到了“目标”成员方法和提前进入函数调用处理流程 — 只匹配名,而不匹配参数列表。这不仅造成程序设计上的难点,更导致【普通引用】与【智能指针】对被引用的【自定义引用】处理逻辑的不一致。以自定义引用<T: AsRef<F>>例,

  • &Tas_ref()特征成员方法返回&F,而

  • Box<T>as_ref()特征成员方法却返回&T

它们虽同为T的“引用”但同名成员方法却返回不同类型的解引用值。对此,标准库的技术选择是放任此“不和谐的”存在。但,我忍不了。我要把【智能指针】对【自定义引用】内部值的处理逻辑“掰直”对齐于【普通引用】。具体做法也不难,

第一步,给每个自定义本地智能指针(数据结构),都增补如下一段对trait AsRef<F>trait AsMut<F>的【条件化特征实现块】

  • 欲了解更多“条件化实现块”的精彩内容,请请移步至我的另一篇主题文章《在 Rust 中令人印象深刻的三大【编译时】条件处理》

  • 这里突出强调“本地智能指针”是因为Rust编译沙箱的孤儿原则导致“给任何当前crate的数据结构实现标准库的AsRef<F>AsMut<F>特征都会被编译器拒绝”。

type SmartPointer = /* 前文代码定义的"智能指针"结构体类名 */;
impl<F> AsRef<F> for SmartPointer
where <SmartPointer as Deref>::Target: AsRef<F> {fn as_ref(&self) -> &F {self.deref().as_ref()}
}
impl<F> AsMut<F> for SmartPointer
where <SmartPointer as Deref>::Target: AsMut<F> {fn as_mut(&mut self) -> &mut F {self.deref_mut().as_mut()}
}

这段增补程序块所完成的任务可概括为:

  1. 若智能指针关联类型Deref::Target同时不满足AsRef<F>AsMut<F>特征限定条件,那就什么也不做,也不添加新特征实现块。否则,继续。

  2. 若智能指针关联类型Deref::Target满足AsRef<F>特征限定条件,那就给智能指针数据结构增补trait AsRef<F>特征实现块,和实现特征成员方法<SmartPointer as AsRef<F>>::as_ref(&SmartPointer)返回&F

  3. 若智能指针关联类型Deref::Target满足AsMut<F>特征限定条件,那就给智能指针数据结构增补trait AsMut<F>特征实现块,和实现特征成员方法<SmartPointer as AsMut<F>>::as_mut(&mut SmartPointer)返回&mut F

此外,即便上例中的SmartPointer智能指针已实现过面向关联类型<SmartPointer as Deref>::Target的自定义引用特征

  • trait AsRef<<SmartPointer as Deref>::Target>

  • trait AsMut<<SmartPointer as Deref>::Target>

之一(或全部)也不会与此处新增补的trait AsRef<F>trait AsMut<F>特征实现块冲突,因为它们的泛型类型参数不一样

第二步,在调用智能指针实例上的as_ref()as_mut()特征成员方法时,有一点儿麻烦,因为需要分辨两种情况:

  1. 智能指针数据结构上未定义过面向其它类型的trait AsRef<_>trait AsMut<_>特征实现块,那么从智能指针实例直接点出新增补的as_ref()as_mut()特征成员方法即可,不会有任何的歧义。

  2. 否则,

  • 要么,采用TurboFish语法,从成员方法调用表达式,标注泛型参数值和关联目标特征实现块。例如,println!("{}", <SmartPointer as AsMut<i32>>::as_mut(&mut smartPointer));

  • 要么,定义独立的赋值语句,从赋值变量的类型声明,标注泛型参数值和关联目标特征实现块。例如,let mut value: i32 = smartPointer.as_mut();

可复用的宏定义

因为上面那段增补代码几乎伴随着我本地定义的每个智能指针类,所以它还被特意零成本抽象为如下一段宏定义,以方便复用。因为这个功能太小,所以我还未将其发包于crates.io仓库。

// 宏定义
macro_rules! smart_pointer_patch_builder {[@ref ($struct: ty)] => {impl<F> std::convert::AsRef<F> for $structwhere <$struct as std::ops::Deref>::Target: AsRef<F> {fn as_ref(&self) -> &F {use ::std::ops::Deref;self.deref().as_ref()}}impl<F> std::convert::AsMut<F> for $structwhere <$struct as std::ops::Deref>::Target: AsMut<F> {fn as_mut(&mut self) -> &mut F {use ::std::ops::DerefMut;self.deref_mut().as_mut()}}};
}
// 宏调用样例
type SmartPointer = /* 前文代码定义的"智能指针"结构体类名 */;
// 装配条件化的 AsRef 与 AsMut 特征实现块
smart_pointer_patch_builder!{ @ref (SmartPointer) }

为了向 @Rustacean 推销我做的这个宏,我还特地做了一套用法展示[例程11]

macro_rules! smart_pointer_patch_builder {[@ref ($struct: ty)] => {impl<F> std::convert::AsRef<F> for $structwhere <$struct as std::ops::Deref>::Target: AsRef<F>,{fn as_ref(&self) -> &F {use ::std::ops::Deref;self.deref().as_ref()}}impl<F> std::convert::AsMut<F> for $structwhere <$struct as std::ops::Deref>::Target: AsMut<F>,{fn as_mut(&mut self) -> &mut F {use ::std::ops::DerefMut;self.deref_mut().as_mut()}}};
}
// 定义实现了`trait AsRef<F>`与`trait AsMut<F>`特征的智能指针内部值
mod wrapping {use ::std::convert::{ AsRef, AsMut };#[derive(Debug)]pub struct Wrapping(i32);impl AsRef<i32> for Wrapping {fn as_ref(&self) -> &i32 {&self.0}}impl AsMut<i32> for Wrapping {fn as_mut(&mut self) -> &mut i32 {&mut self.0}}impl Wrapping {pub fn new(value: i32) -> Self {Wrapping(value)}}
}
// 定义本地智能指针类型
mod smart_pointer {use ::std::{ convert::{ AsRef, AsMut }, ops::{ Deref, DerefMut } };use super::wrapping::Wrapping;#[derive(Debug)]pub struct SmartPointer {value: Wrapping}impl Deref for SmartPointer {type Target = Wrapping;fn deref(&self) -> &Self::Target {&self.value}}impl DerefMut for SmartPointer {fn deref_mut(&mut self) -> &mut Self::Target {&mut self.value}}impl SmartPointer {pub fn new(value: i32) -> Self {SmartPointer { value: Wrapping::new(value) }}}// 下面是【标准库】对【智能指针】与`AsRef<F>`/`AsMut<F>`特征的惯例处理impl AsRef<<SmartPointer as Deref>::Target> for SmartPointer {fn as_ref(&self) -> &<SmartPointer as Deref>::Target {&self.value}}impl AsMut<<SmartPointer as Deref>::Target> for SmartPointer {fn as_mut(&mut self) -> &mut <SmartPointer as Deref>::Target {&mut self.value}}
}
use ::std::{ convert::AsRef, ops::Deref };
use smart_pointer::SmartPointer;
// 为本地智能指针数据结构增补`trait AsRef<F>`与`trait AsMut<F>`特征实现块。
smart_pointer_patch_builder!{ @ref (SmartPointer) }fn main() {let sp = SmartPointer::new(12);println!("sp = {:?}", sp);println!("sp.deref() = {:?}", sp.deref());let ref_as: &i32 = sp.as_ref();println!("sp.as_ref() = {:?}", ref_as);
}

结束语

作为从春节前半个月我就开始着手筹备的倾心大作,这篇长文算是比较全面地汇总了我在生产实践与理论知识沉淀过程中对&T普通引用,<T: AsRef<F>>自定义引用,<T: Borrow<F>>自定义借用和<T: Deref<Target = F>>智能指针四个Rust泛化“引用”项的最新冥悟。文章不仅长,还着实有点儿生涩。感谢耐心的读者能坚持阅读至文章结束。创作不易,请读者们点个赞呗!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/71993.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

外层元素旋转,其包括在内的子元素一并旋转(不改变旋转中心),单元测试

思路&#xff1a;外层旋转后坐标&#xff0c;元素旋转后坐标&#xff0c;计算偏移坐标 <template><div class"outbox"><label>角度: <input v-model.number"rotate" type"number" /></label><br><div c…

Oracle数据库存储结构--物理存储结构

数据库存储结构&#xff1a;分为物理存储结构和逻辑存储结构。 物理存储结构&#xff1a;操作系统层面如何组织和管理数据 逻辑存储结构&#xff1a;Oracle数据库内部数据组织和管理数据&#xff0c;数据库管理系统层面如何组织和管理数据 存储结构 在Oracle数据库的存储结构…

纺织服装制造行业现状 内检实验室系统在纺织服装制造行业的应用

在纺织服装制造行业&#xff0c;内检实验室LIMS系统&#xff08;实验室信息管理系统&#xff09;已成为提升检测效率、优化质量控制和满足行业合规性要求的关键工具。随着行业竞争的加剧和消费者对产品质量要求的提高&#xff0c;纺织服装制造企业需要更加高效、准确的检测流程…

3分钟复现 Manus 超强开源项目 OpenManus

文章目录 前言什么是 OpenManus构建方式环境准备克隆代码仓库安装依赖配置 LLM API运行 OpenManus 效果演示总结个人简介 前言 近期人工智能领域迎来了一位备受瞩目的新星——Manus。Manus 能够独立执行复杂的现实任务&#xff0c;无需人工干预。由于限制原因大部分人无法体验…

【大模型统一集成项目】如何封装多个大模型 API 调用

&#x1f31f; 在这系列文章中&#xff0c;我们将一起探索如何搭建一个支持大模型集成项目 NexLM 的开发过程&#xff0c;从 架构设计 到 代码实战&#xff0c;逐步搭建一个支持 多种大模型&#xff08;GPT-4、DeepSeek 等&#xff09; 的 一站式大模型集成与管理平台&#xff…

AI4CODE】3 Trae 锤一个贪吃蛇的小游戏

【AI4CODE】目录 【AI4CODE】1 Trae CN 锥安装配置与迁移 【AI4CODE】2 Trae 锤一个 To-Do-List 这次还是采用 HTML/CSS/JAVASCRIPT 技术栈 Trae 锤一个贪吃蛇的小游戏。 1 环境准备 创建一个 Snake 的子文件夹&#xff0c;清除以前的会话记录。 2 开始构建 2.1 输入会…

从零开发Chrome广告拦截插件:开发、打包到发布全攻略

从零开发Chrome广告拦截插件&#xff1a;开发、打包到发布全攻略 想打造一个属于自己的Chrome插件&#xff0c;既能拦截烦人的广告&#xff0c;又能优雅地发布到Chrome Web Store&#xff1f;别担心&#xff0c;这篇教程将带你从零开始&#xff0c;动手开发一个功能强大且美观…

基于腾讯云高性能HAI-CPU的跨境电商客服助手全链路解析

跨境电商的背景以及痛点 根据Statista数据&#xff0c;2025年全球跨境电商市场规模预计达6.57万亿美元&#xff0c;年增长率保持在12.5% 。随着平台规则趋严&#xff08;如亚马逊封店潮&#xff09;&#xff0c;更多卖家选择自建独立站&#xff0c;2024年独立站占比已达35%。A…

git安装,配置SSH公钥(查看版本、安装路径,更新版本)git常用指令

目录 一、git下载安装 1、下载git 2、安装Git‌&#xff1a; 二、配置SSH公钥 三、查看安装路径、查看版本、更新版本 四、git常用指令 1、仓库初始化与管理 2、配置 3、工作区与暂存区管理 4、提交 5、分支管理 6、远程仓库管理 7、版本控制 8、其他高级操作 一…

【鸿蒙开发】OpenHarmony调测工具hdc使用教程(设备开发者)

00. 目录 文章目录 00. 目录01. OpenHarmony概述02. hdc简介03. hdc获取04. option相关的命令05. 查询设备列表的命令06. 服务进程相关命令07. 网络相关的命令08. 文件相关的命令09. 应用相关的命令10. 调试相关的命令11. 常见问题12. 附录 01. OpenHarmony概述 OpenHarmony是…

手写简易Tomcat核心实现:深入理解Servlet容器原理

目录 一、Tomcat概况 1. tomcat全局图 2.项目结构概览 二、实现步骤详解 2.1 基础工具包&#xff08;com.qcby.util&#xff09; 2.1.1 ResponseUtil&#xff1a;HTTP响应生成工具 2.1.2 SearchClassUtil&#xff1a;类扫描工具 2.1.3 WebServlet&#xff1a;自定义注解…

【Java开发指南 | 第三十四篇】IDEA没有Java Enterprise——解决方法

读者可订阅专栏&#xff1a;Java开发指南 |【CSDN秋说】 文章目录 1、新建Java项目2、单击项目名&#xff0c;并连续按两次shift键3、在搜索栏搜索"添加框架支持"4、勾选Web应用程序5、最终界面6、添加Tomcat 1、新建Java项目 2、单击项目名&#xff0c;并连续按两次…

在MATLAB中实现PID控制仿真

在MATLAB中实现PID控制仿真可以通过代码编程或Simulink图形化建模两种方式完成。以下是两种方法的详细操作步骤和示例&#xff1a; 方法1&#xff1a;使用MATLAB脚本编程&#xff08;基于控制系统工具箱&#xff09; 步骤1&#xff1a;定义被控对象的数学模型 假设被控对象是…

蓝桥杯历年真题题解

1.轨道炮&#xff08;数学模拟&#xff09; #include <iostream> #include <map> using namespace std; const int N1010; int x[N],y[N],v[N]; char d[N]; int main() {int n;int ans-100;cin>>n;for(int i1;i<n;i)cin>>x[i]>>y[i]>>v…

Pytorch的一小步,昇腾芯片的一大步

Pytorch的一小步&#xff0c;昇腾芯片的一大步 相信在AI圈的人多多少少都看到了最近的信息&#xff1a;PyTorch最新2.1版本宣布支持华为昇腾芯片&#xff01; 1、 发生了什么事儿&#xff1f; 在2023年10月4日PyTorch 2.1版本的发布博客上&#xff0c;PyTorch介绍的beta版本…

阿里千问大模型(Qwen2.5-VL-7B-Instruct)部署

参考链接 知乎帖子 B站视频 huggingface 镜像网站&#xff08;不太全&#xff0c;比如 Qwen/Qwen2.5-VL-7B-Instruct就没有&#xff09; huggingface 5种下载方式汇总 通过huggingface-cli下载模型 不一样的部分是预训练权重的下载和demo 首先安装huggingface_hub pip insta…

Jenkins在Windows上的使用(二):自动拉取、打包、部署

&#xff08;一&#xff09;Jenkins全局配置 访问部署好的Jenkins服务器网址localhost:8080&#xff0c;完成默认插件的安装后&#xff0c;接下来将使用SSH登录远程主机以实现自动化部署。 1. 配置插件 选择dashboard->Manage Jenkins->plugins 安装下面两个插件  …

群晖DS 223 Docker:开启私有云

群晖DS 223 Docker&#xff1a;开启私有云的无限可能 引言 在数据存储与管理的不断演进中&#xff0c;群晖 DS 223 凭借其出色的性能和丰富的功能&#xff0c;成为众多用户搭建私有云的热门选择。而当它与 Docker 技术相遇&#xff0c;犹如为数据管理的舞台添上了绚丽多彩的灯…

Three.js 进阶(灯光阴影关系和设置、平行光、阴影相机)

本篇主要学习内容 : 灯光与阴影聚光灯点光源平行光阴影相机和阴影计算投射阴影接受阴影 点赞 关注 收藏 学会了 1.灯光与阴影 1、材质要满足能够对光有反应 2、设置渲染器开启阴影计算 renderer.shadowMap.enabledtrue 3、设置光照投射阴影 directionalLight.castShadow …

【 <一> 炼丹初探:JavaWeb 的起源与基础】之 Tomcat 的工作原理:从启动到请求处理的流程

<前文回顾> 点击此处查看 合集 https://blog.csdn.net/foyodesigner/category_12907601.html?fromshareblogcolumn&sharetypeblogcolumn&sharerId12907601&sharereferPC&sharesourceFoyoDesigner&sharefromfrom_link <今日更新> 一、Tomcat…