【社区投稿】自动特征auto trait的扩散规则

自动特征auto trait的扩散规则

公式化地概括,auto trait = marker trait + derived trait。其中,等号右侧的markerderived是在Rustonomicon书中的引入的概念,鲜见于Rust References。所以,若略感生僻,不奇怪。

marker traitderived trait精准概括了auto trait功能的两面性

  1. 前者指明auto trait实现类具备了由rustc编译器和std标准库对其约定的“天赋异能 intrinsic properties”。

  2. 后者描述了这些“天赋异能”沿auto trait实现类数据结构【自内及外】的继承性与扩散性。

接下来逐一解释。

marker trait标识“天赋”特征是什么

既然是“天赋”,那么auto trait没有任何抽象成员方法待被“后天实现”或关联项待被“后天赋值” — 这也是marker trait别名的由来。rustc甚至未配备专项检查器以静态分析与推断 @Rustacean 对auto trait的实现是否合理。类似于unsafe块,@Rustacean 需向rustc承诺:知道自已正在干什么,和提交可供其它程序模块信任的“天赋异能“代码实现。否则,运行时程序就会执行出未定义行为 U.B.。相比于传统的std程序接口和rustc内存安全承诺,这是一项“反向契约” — 即,由rustc充当“甲方”和规划功能要求,而由 @Rustacean 充当“乙方”完成功能代码和提供正确性保证。在硕大的Rust标准库中,这类“天赋异能“的”反向契约”并不多见,但包括

536b7280071b9e5f8fa4dab61194f88e.png

输入图片说明

derived trait明确“天赋”特征如何扩散

概括起来,auto trait的扩散规则就四个字“由内及外”。其遇到不同的场景,伴有不同解释的扩散链条

场景一:变量 ➜ 指针

以变量的数据类型为内,和以指向该变量值的指针/引用为外 — 变量值(数据类型T)实现的auto trait会自动扩散至它的各类指针与引用:

  • &T

  • &mut T

  • *const T

  • *mut T

所以,该扩散链条也被记作【类型 ➜ 指针】。

场景二:字段 ➜ 结构体

以字段的数据类型为内,和以父数据结构为外 — 所有字段(数据类型)都实现的auto trait会自动扩散至它们的紧上一层数据结构:

  • structs

  • enums

  • unions

  • tuples

所以,该扩散链条也被记作【字段 ➜ 结构】。

场景三:元素 ➜ 集合

以集合元素的数据类型为内,和以集合容器为外 — 由元素(数据类型T)实现的auto trait会自动扩散至该元素的紧上一层集合容器:

  • [T; n]

  • [T]

  • Vec<T>

场景四:捕获变量 ➜ 闭包

以捕获变量的数据类型为内,和以闭包为外 — 所有捕获变量(数据类型)都实现的auto trait会自动扩散至引用(或所有权占用)这些捕获变量的闭包。

场景五:函数 ➜ 函数指针

函数项fn与函数指针fn ptr总是会被rustc编译时自动实现全部auto trait

扩散链条的“串连”

前四个场景的扩散链是可以多重嵌套衔接的。举个例子,Vec<Wrapping<u8>>一定满足trait Send限定条件,因为这条扩散链条的存在:

575ab6313829c47c199ebdb53458f90e.png
输入图片说明

再举个更复杂的例子,假设有如下枚举类

enum Test<'a> {
    Str(&'a String),
    Num(u8)
}

那么Vec<Test>也一定满足trait Send限定条件,因为此扩散链条的存在:

2f123f967f6e9da64b11bc4a6dabb241.png
输入图片说明

auto trait扩散链条的“阻断”

  1. 安装nightlyrustc编译器。然后,在代码中,

  2. 开启#![feature(negative_impls)]feature-gate编译开关

  3. 否定实现auto trait。比如,impl !Unpin for Test {}

于是,rustc就不会再对遇到的【类型定义】自动添加曾被否定实现过的auto trait了。在众多auto trait中,仅trait Unpin绕过nightly编译工具链的依赖和仅凭stable标准库内符号类型std::marker::PhantomPinned定义的幻影字段阻止rustc悄悄地实现自动特征。

【幻影字段】是仅作用于编译时的零成本抽象项。它被用来帮助编译器理解 @Rustacean 提交的代码和推断 @Rustacean 的程序设计意图。其语义功能很像typescript中的【@ 装饰器】。即,

  1. 辅助代码静态分析

  2. 辅助编译器生成垫片程序

  3. 编译后立即抹除

  4. 对【运行时】不可见 — 这也是【零成本】的由来。但,世间任何事物都有两面性和是双刃剑。“零成本”是省CPU,但更费脑细胞呀!Rust编程的心智成本高已是行业共识了。

另值一提的是,【Rust幻影字段】与【typescript装饰器】皆都不同于【Java的 @ 注释】,因为

  • 前者是给编译器看和解读的 — 充其量是代码正文的旁白注脚。

  • 后者是给运行时VM用和执行的 — 这已算是正文指令的一部分了。

它们就是两个不同“位面”的东西。

我日常仅用过std::marker::PhantomPinnedstd::marker::PhantomData两类幻影字段

  • 前者解决stable编译工具链对trait Unpin的否定实现 — 就是这里正在讲的事

  • 后者被用于“类型状态Type State Pattern”设计模式中,将【运行时】对象状态的(动态)信息编码入【编译时】对象类型的(静态)定义里,以扩展Rust类型系统的应用场景至对状态集的“状态管理”。若您对“类型状态”设计模式有兴趣,推荐移步至我的另一篇主题文章对照 OOP 浅谈【类型状态】设计模式保证有收获。

这闲篇扯远了,让咱们重新回到文章的主题上来。

请细读下面自引用结构体的类型定义(特别含注释内容)和体会std::marker::PhantomPinned如何被用来声明结构体内的幻影字段:

use ::std::marker::PhantomPinned;
struct SelfReferential {
    // 整个结构体内唯一包含了有效信息的字段。
    a: String,
    // 自引用前一个字段`a`的值。
    ref_a: *const String,
    // 1. 这是对【幻影字段】的定义
    // 2. 因为该字段并不会真的被后续功能代码用到,
    //    所以为了压制来自编译器的`useless field`警告,
    //    字段名以`_`为前缀
    _marker: PhantomPinned 
} // 于是,自引用结构体`Test`就是 !Unpin 的了

即便不太理解,也请不要质疑【自引用结构体】的存在必要性。至少在异步程序块中,跨.await轮询点的变量引用都依赖这套机制。仅因为async {}语法糖把每次构造trait Future实现类的细节都隐藏了起来,所以 @Rustacean 对自引用结构体的直观感受会比较弱。

auto trait扩散不至的自定义数据结构

若数据结构定义内含有实现auto trait的字段(比如,

use ::std::env::Vars;
struct Dumb(Vars);

其中Dumb.0字段Vars明确是!Send的),那么 @Rustacean 就有必要考虑

  • 要么,重新规划程序设计,以规避要求struct Dumb满足trait Send限定条件

  • 要么,给struct Dumb添加unsafeauto trait实现块。

    unsafe impl Send for Dumb {};

后者的unsafe impl语法前缀就是rustc对程序作者最后的警告:“你真的明白,你正在做什么事吗?”。

【快排序】 综合例程

先贴源码,再做详解。敲黑板强调:代码内的注释同样重要呀!推荐同正文一样重视和仔细阅读。

fn quick_sort<T: Ord + Send>(v: &mut [T]) {
    if v.len() <= 1 {
        return;
    }
    let mid = {
        let pivot = v.len() - 1;
        let mut i = 0;
        for j in 0..pivot {
            if v[j] <= v[pivot] {
                v.swap(i, j);
                i += 1;
            }
        }
        v.swap(i, pivot);
        i
    };
    // 1. 此处,虽然 lo 与 hi 是两个崭新的【切片】胖指针实例,
    //    但由【切片】胖指针引用的底层 Vec<i32> 数据值却只有
    //    一份呀!
    let (lo, hi) = v.split_at_mut(mid);
    // 2. 所以,后续的【多线程+递归】是修改的同一个 Vec<i32> 
    //    实例。
    rayon::join(|| quick_sort(lo),
                || quick_sort(hi));
    // 3. 至此,虽然此函数没有返回值,但仍可沿函数的【输入输出】
    //    实参 v: &mut [T] 向调用端传递排序结果。
}
fn main() {
    use ::rand::prelude::*;
    // 1. 生成一个大数字集合
    let mut numbers: Vec<i32> = (1000..10000).collect();
    // 2. 乱序数组内容
    numbers.shuffle(&mut rand::thread_rng()); 
    // 3. 多线程快排序
    quick_sort(&mut numbers); 
    // 4. 打印排序结果
    println!("Sorted: {:?}", &numbers[..10]);
    // 5. 一个单例 Vec<i32> 对象贯穿整个多线程快排序 demo 始终。
}

上述例程的难点并不是rayon::join()如何在后台悄悄唤起多个线程加速大数据集【快排序】 — 这不需要 @Rustacean 操心,咱们只要多读读 API 文档和了解Work Stealing工作原理就足够了。相反,曾经困惑过我一段时间的痛点是:

为什么虽然泛型类型参数<T: Ord + Send>的书面语义是“跨线程·数据复制”但程序的实际执行结果却是对Vec<i32>单例的“跨线程·内存共享”?即,多个线程透过【切片】胖指针,修改同一个Vec<T>变长数组实例。

推导明白这条逻辑链(元素Send ➜ 集合Sync)先后耗费了我不少心神。但总结起来也无非如下几步:

  1. 依据前文介绍的auto trait扩散规则,对特征trait Send和泛型类型参数T,构造初始扩散链条:

    75d06a4e7b6f1f88522d6db13caa0219.png
    输入图片说明
  2. 依据trait Sendtrait Sync的(单向)转换关系,有<&S: Send> → <S: Sync>

  3. 将 #2 代入 #1,进一步完善trait Send扩散链条,有

    d70179ea7fa2cc2e7277867d58e8ba66.png
    输入图片说明
  4. 依据rustc赋予trait Sync的语义,集合[T]被允许跨线程引用与多线程共享

  5. 又因为fn quick_sort()的形参是对Vec<i32>实例的可修改引用&mut,所以多个线程被允许并行修改同一个变长数组实例。

你不会以为故事就此结束了吧?难道你没有发觉例程中多线程代码有缺了点儿什么的异样吗?没错!细心的读者可能早就想问:

对单实例变长数组的并行修改,为什么未采用【读写锁RwLock】或【互斥锁Mutex】加以同步保护呢?甚至rustc在编译时连警告提示都没有输出?What's wrong?

好问题!您有心了。快速回答是:虽然Vec<i32>实例同时被多个线程并行修改不假,但每个线程并行修改的切片却只是同一变长数组内彼此衔接却并不相交的“子段”。所以,在快排序过程中,事实上没有任何数据竞争发生 — 这是彻头彻尾的算法胜利。果真,编程的尽头是数学与算法啊!此外,rustc能顺利地接受与成功地编译这样的代码也足已破除人们以往对它保守且不变通的刻板印象。

结束语

这次就先分享这一个小知识点。文章里埋的有关Unpin的坑以后再填。2024搞了一年的“鸿蒙Next ArkTs”真不容易。哎!天大地大,饭辙最大。我是一颗螺丝钉,甲方爸爸需要什么,我就研究什么。但,年底写篇Rust知识分享文章压压惊。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Elasticsearch检索之三:官方推荐方案search_after检索实现(golang)

Elasticsearch8.17.0在mac上的安装 Kibana8.17.0在mac上的安装 Elasticsearch检索方案之一&#xff1a;使用fromsize实现分页 快速掌握Elasticsearch检索之二&#xff1a;滚动查询(scrool)获取全量数据(golang) 1、search_after检索 在前面的文章介绍了fromsize的普通分页…

精读DeepSeek v3技术文档的心得感悟

最近宋大宝同学读完了DeepSeekv3的文档&#xff0c;心中颇多感慨&#xff0c;忍不住想在这里记录一下对这款“业界有望启示未来低精度训练走向”的开源大模型的观察与思考。DeepSeek v3的亮点绝不仅仅是“Float8”或“超长上下文”这么简单&#xff0c;而是贯穿了从数值精度、注…

WAV文件双轨PCM格式详细说明及C语言解析示例

WAV文件双轨PCM格式详细说明及C语言解析示例 一、WAV文件双轨PCM格式详细说明1. WAV文件基本结构2. PCM编码方式3. 双轨PCM格式详细说明二、C语言解析WAV文件的代码示例代码说明一、WAV文件双轨PCM格式详细说明 WAV文件是一种用于存储未压缩音频数据的文件格式,广泛应用于音频…

Day1 微服务 单体架构、微服务架构、微服务拆分、服务远程调用、服务注册和发现Nacos、OpenFeign

目录 1.导入单体架构项目 1.1 安装mysql 1.2 后端 1.3 前端 2.微服务 2.1 单体架构 2.2 微服务 2.3 SpringCloud 3.微服务拆分 3.1 服务拆分原则 3.1.1 什么时候拆 3.1.2 怎么拆 3.2 拆分购物车、商品服务 3.2.1 商品服务 3.2.2 购物车服务 3.3 服务调用 3.3.1 RestTemplate 3.…

DeepSpeed 使用 LoRA 训练后文件结构详解

DeepSpeed 使用 LoRA 训练后文件结构详解 在大语言模型&#xff08;LLM&#xff09;的训练过程中&#xff0c;DeepSpeed 提供了强大的分布式训练能力&#xff0c;而 LoRA&#xff08;Low-Rank Adaptation&#xff09;通过参数高效微调技术显著减少了资源占用。完成训练后&…

Llama 3 预训练(二)

目录 3. 预训练 3.1 预训练数据 3.1.1 网络数据筛选 PII 和安全过滤 文本提取与清理 去重&#xff08;De-duplication&#xff09; 启发式过滤&#xff08;Heuristic Filtering&#xff09; 基于模型的质量过滤 代码和数学推理数据处理 多语言数据处理 3.1.2 确定数…

Autoware Universe 安装记录

前提&#xff1a; ubuntu20.04&#xff0c;英伟达显卡。 ROS2-Galactic安装 wget http://fishros.com/install -O fishros && . fishros 选择galactic(ROS2)版本&#xff0c;桌面版 ROS2-dev-tools安装 sudo apt install python3-testresources sudo apt update …

【小程序】自定义组件的data、methods、properties

目录 自定义组件 - 数据、方法和属性 1. data 数据 2. methods 方法 3. properties 属性 4. data 和 properties 的区别 5. 使用 setData 修改 properties 的值 自定义组件 - 数据、方法和属性 1. data 数据 在小程序组件中&#xff0c;用于组件模板渲染的私有数据&…

socket编程(C++/Windows)

相关文章推荐&#xff1a; Socket 编程基础 面试官&#xff0c;不要再问我三次握手和四次挥手 TCP的三次握手与四次挥手 参考视频&#xff1a; https://www.bilibili.com/video/BV1aW4y1w7Ui/?spm_id_from333.337.search-card.all.click TCP通信流程 服务端 #include<…

linux自动化一键批量检查主机端口

1、准备 我们可以使用下面命令关闭一个端口 sudo iptables -A INPUT -p tcp --dport 端口号 -j DROP我关闭的是22端口&#xff0c;各位可以关其它的或者打开其它端口测试&#xff0c;谨慎关闭22端口&#xff01;不然就会像我下面一样握手超时&#x1f62d;&#x1f62d;&…

实验五 时序逻辑电路部件实验

一、实验目的 熟悉常用的时序逻辑电路功能部件&#xff0c;掌握计数器、了解寄存器的功能。 二、实验所用器件和仪表 1、双 D触发器 74LS74 2片 2、74LS162 1片 3、74194 1片 4、LH-D4实验仪 1台 1.双…

开源轻量级文件分享服务Go File本地Docker部署与远程访问

???欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学习,不断总结,共同进步,活到老学到老…

flask基础

from flask import Flask, requestapp Flask(__name__)# app.route(/) # def hello_world(): # put applications code here # return Hello World!app.route(/) # 路由 当用户访问特定 URL 时&#xff0c;Flask 会调用对应的视图函数来处理请求 def index():return …

WPF使用OpenCvSharp4

WPF使用OpenCvSharp4 创建项目安装OpenCvSharp4 创建项目 安装OpenCvSharp4 在解决方案资源管理器中&#xff0c;右键单击项目名称&#xff0c;选择“管理 NuGet 包”。搜索并安装以下包&#xff1a; OpenCvSharp4OpenCvSharp4.ExtensionsOpenCvSharp4.runtime.winSystem.Man…

社媒运营专线 - SD-WAN 跨境网络专线 —— 外贸企业社媒平台的专属 “快车道”

在当今全球化的商业浪潮中&#xff0c;社交媒体平台已成为外贸企业拓展国际市场、提升品牌知名度和促进业务增长的关键阵地。然而&#xff0c;网络访问速度慢、IP 不纯净等问题却如影随形&#xff0c;严重制约了企业社媒运营的效率和效果。幸运的是&#xff0c;社媒运营专线 - …

RustDesk内置ID服务器,Key教程

RustDesk内置ID服务器&#xff0c;Key教程 首先需要准备一个域名&#xff0c;并将其指定到你的 rustdesk 服务器 ip 地址上&#xff0c;这里编译采用的是Github Actions &#xff0c;说白了是就workflows&#xff0c;可以创建一些自动化的工作流程&#xff0c;例如代码的检查&a…

代码随想录算法【Day4】

Day4 1.链表的题目&#xff0c;要在草稿纸上模拟清晰后就简单了 2.双指针更加灵活的应用。 3.环形链表多练习。 24. 两两交换链表中的节点 class Solution { public:ListNode* swapPairs(ListNode* head) {ListNode* _dummyHead new ListNode(0); //虚拟头结点_dummyHead…

计算机毕业设计hadoop+spark+hive民宿推荐系统 酒店推荐系统 民宿价格预测 酒店价格 预测 机器学习 深度学习 Python爬虫 HDFS集群

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

win11中win加方向键失效的原因

1、可能是你把win键锁了&#xff1a; 解决办法&#xff1a;先按Fn键&#xff0c;再按win键 2、可能是可能是 贴靠窗口设置 中将贴靠窗口关闭了&#xff0c;只需要将其打开就好了

Unity自定义Inspector属性名特性以及特性自定义布局问题

前言&#xff1a; 在Unity中编辑属性的适合&#xff0c;一般都是显示属性的英文&#xff0c;如果想要改成中文的话又不能改变属性名&#xff0c;那么自定义特性是很好的选择。 一、自定以特性 这一块没有什么要多说的&#xff0c;就是自定义特性 using UnityEngine; #if UNI…