【社区投稿】自动特征auto trait的扩散规则
自动特征auto trait
的扩散规则
公式化地概括,auto trait = marker trait + derived trait
。其中,等号右侧的marker
与derived
是在Rustonomicon书中的引入的概念,鲜见于Rust References。所以,若略感生僻,不奇怪。
marker trait
与derived trait
精准概括了auto trait
功能的两面性
前者指明
auto trait
实现类具备了由rustc
编译器和std
标准库对其约定的“天赋异能 intrinsic properties”。后者描述了这些“天赋异能”沿
auto trait
实现类数据结构【自内及外】的继承性与扩散性。
接下来逐一解释。
marker trait
标识“天赋”特征是什么
既然是“天赋”,那么auto trait
就没有任何抽象成员方法待被“后天实现”或关联项待被“后天赋值” — 这也是marker trait
别名的由来。rustc
甚至未配备专项检查器以静态分析与推断 @Rustacean 对auto trait
的实现是否合理。类似于unsafe
块,@Rustacean 需向rustc
承诺:知道自已正在干什么,和提交可供其它程序模块信任的“天赋异能“代码实现。否则,运行时程序就会执行出未定义行为 U.B.。相比于传统的std
程序接口和rustc
内存安全承诺,这是一项“反向契约” — 即,由rustc
充当“甲方”和规划功能要求,而由 @Rustacean 充当“乙方”完成功能代码和提供正确性保证。在硕大的Rust
标准库中,这类“天赋异能“的”反向契约”并不多见,但包括
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
限定条件,因为这条扩散链条的存在:

再举个更复杂的例子,假设有如下枚举类
enum Test<'a> {Str(&'a String),Num(u8)
}
那么Vec<Test>
也一定满足trait Send
限定条件,因为此扩散链条的存在:

auto trait
扩散链条的“阻断”
安装
nightly
版rustc
编译器。然后,在代码中,开启
#![feature(negative_impls)]
的feature-gate
编译开关,否定实现
auto trait
。比如,impl !Unpin for Test {}
于是,rustc
就不会再对遇到的【类型定义】自动添加曾被否定实现过的auto trait
了。在众多auto trait
中,仅trait Unpin
可绕过对nightly
编译工具链的依赖和仅凭stable
标准库内符号类型std::marker::PhantomPinned
定义的幻影字段阻止rustc
悄悄地实现自动特征。
【幻影字段】是仅作用于编译时的零成本抽象项。它被用来帮助编译器理解 @Rustacean 提交的代码和推断 @Rustacean 的程序设计意图。其语义功能很像
typescript
中的【@ 装饰器】。即,
辅助代码静态分析
辅助编译器生成垫片程序
编译后立即抹除
对【运行时】不可见 — 这也是【零成本】的由来。但,世间任何事物都有两面性和是双刃剑。“零成本”是省
CPU
,但更费脑细胞呀!Rust
编程的心智成本高已是行业共识了。另值一提的是,【
Rust
幻影字段】与【typescript
装饰器】皆都不同于【Java
的 @ 注释】,因为
前者是给编译器看和解读的 — 充其量是代码正文的旁白注脚。
后者是给运行时
VM
用和执行的 — 这已算是正文指令的一部分了。它们就是两个不同“位面”的东西。
我日常仅用过
std::marker::PhantomPinned
与std::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
添加unsafe
的auto 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
)先后耗费了我不少心神。但总结起来也无非如下几步:
依据前文介绍的
auto trait
扩散规则,对特征trait Send
和泛型类型参数T
,构造初始扩散链条:输入图片说明 依据
trait Send
至trait Sync
的(单向)转换关系,有<&S: Send> → <S: Sync>
。将 #2 代入 #1,进一步完善
trait Send
扩散链条,有输入图片说明 依据
rustc
赋予trait Sync
的语义,集合[T]
被允许跨线程引用与多线程共享又因为
fn quick_sort()
的形参是对Vec<i32>
实例的可修改引用&mut
,所以多个线程被允许并行修改同一个变长数组实例。
你不会以为故事就此结束了吧?难道你没有发觉例程中多线程代码有缺了点儿什么的异样吗?没错!细心的读者可能早就想问:
对单实例变长数组的并行修改,为什么未采用【读写锁RwLock
】或【互斥锁Mutex
】加以同步保护呢?甚至rustc
在编译时连警告提示都没有输出?What's wrong?
好问题!您有心了。快速回答是:虽然Vec<i32>
实例同时被多个线程并行修改不假,但每个线程并行修改的切片却只是同一变长数组内彼此衔接却并不相交的“子段”。所以,在快排序过程中,事实上没有任何数据竞争发生 — 这是彻头彻尾的算法胜利。果真,编程的尽头是数学与算法啊!此外,rustc
能顺利地接受与成功地编译这样的代码也足已破除人们以往对它保守且不变通的刻板印象。
结束语
这次就先分享这一个小知识点。文章里埋的有关Unpin
的坑以后再填。2024搞了一年的“鸿蒙Next ArkTs
”真不容易。哎!天大地大,饭辙最大。我是一颗螺丝钉,甲方爸爸需要什么,我就研究什么。但,年底写篇Rust
知识分享文章压压惊。
相关文章:

【社区投稿】自动特征auto trait的扩散规则
自动特征auto trait的扩散规则 公式化地概括,auto trait marker trait derived trait。其中,等号右侧的marker与derived是在Rustonomicon书中的引入的概念,鲜见于Rust References。所以,若略感生僻,不奇怪。 marker …...
云原生相关的 Go 语言工程师技术路线(含博客网址导航)
要成为一名云原生相关的 Go 语言工程师,需要在 Go 语言、云原生技术栈以及相关的开发和运维工具上建立扎实的基础。下面是一个前字节员工总结的技术路线规划: 1. 掌握 Go 语言基础 深入理解 Go 语言:你需要熟练掌握 Go 的语法、数据结构、并…...

mui框架开发的手机APP——众筹约课类【只有前端,无后端】
点击获取源码...
Python的内存管理
文章目录 1. **内存管理的基本原理**(1)动态内存分配(2)引用计数机制 2. **垃圾回收(Garbage Collection, GC)机制**(1)循环引用问题(2)垃圾回收器的作用 3. …...
VSCode调试
目录 C/C远程本地调试插件配置参考 C/C远程本地调试 测试源码:https://github.com/jrhee17/ssl-study 插件 Remote - SSH C/C 配置 .vscode/launch.json {"version": "0.2.0","configurations": [{"name": "afte…...
Direct Preference Optimization (DPO) 简介与流程解析:中英双语
Direct Preference Optimization (DPO) 简介与流程解析 Direct Preference Optimization (DPO) 是一种基于人类偏好的强化学习优化方法,用于训练语言模型,使其更好地满足用户需求或偏好。本文将详细介绍 DPO 的核心思想、优化流程,并结合代码…...

fisco-bcos手动搭建webase启动注意事项
手动搭建webase-front启动注意事项 Java环境变量:1.8.301时候的错误 一直提示节点连接不上,无法连接chanale端口 这是官方提供的解决办法Help wanted: solution for secp256k1 being disabled Issue #470 FISCO-BCOS/java-sdk Java SDK 2.x连接节点失败…...
ospf 的 状态机详解
OSPF(开放最短路径优先,Open Shortest Path First)协议的状态机是其核心部分之一,用于确保路由器之间的邻接关系(neighbor relationship)建立和路由信息的交换。OSPF的状态机模型由多个状态组成,…...

TP5 动态渲染多个Layui表格并批量打印所有表格
记录: TP5 动态渲染多个Layui表格每个表格设置有2行表头,并且第一行表头在页面完成后动态渲染显示内容每个表格下面显示统计信息可点击字段排序一次打印页面上的所有表格打印页面上多个table时,让每个table单独一页 后端代码示例: /*** Nod…...

spring专题笔记(六):bean的自动装配(自动化注入)-根据名字进行自动装配、根据类型进行自动装配。代码演示,通俗易懂。
目录 一、根据名字进行自动装配--byName 二、根据类型进行自动装配 byType 本文章主要是介绍spring的自动装配机制, 用代码演示spring如何根据名字进行自动装配、如何根据类型进行自动装配。代码演示,通俗易懂。 一、根据名字进行自动装配--byName Us…...

监听器listener
文章目录 监听器( listener)对Application内置对象监听的语法和配置对session内置对象监听的语法和配置 监听器( listener) 对象与对象的关系: 继承关联 tomcat一启动创建的顺序:监听器,config,application(全局初始化参数)&am…...

重温设计模式--10、单例模式
文章目录 单例模式(Singleton Pattern)概述单例模式的实现方式及代码示例1. 饿汉式单例(在程序启动时就创建实例)2. 懒汉式单例(在第一次使用时才创建实例) 单例模式的注意事项应用场景 C代码懒汉模式-经典…...
Flutter动画学习二
如何在 Flutter 中使用自定义动画和剪裁(clipping)实现一个简单的动画效果。 前置知识点学习 AnimationController AnimationController 是 Flutter 动画框架中的一个核心类,用于控制动画的生命周期和状态。它提供了一种灵活的方式来定义动…...
讯飞语音听写WebApi(流式)【React Native版】
假设已有 Base64 编码的音频文件(16kHz, s16le, pcm) 1、获取websocket url import * as CryptoJS from crypto-js;/*** 获取websocket url*/ const getWebSocketUrl () > {const config {// 请求地址hostUrl: "wss://iat-api.xfyun.cn/v2/iat",host: "i…...
【Linux编程】一个基于 C++ 的 TCP 客户端异步(epoll)框架(一))
TcpClient 类的设计与实现:一个基于 C 的 TCP 客户端框架 在现代网络编程中,TCP(传输控制协议)客户端是实现网络通信的基础组件之一。本文将详细介绍一个基于 C 的 TcpClient 类的设计与实现,该类提供了创建 TCP 连接…...
PG备份恢复--pg_dump
pg_dump pg_dump 是一个逻辑备份工具。使用 pg_dump 可以在数据库处于使用状态下进行一致 性的备份,它不会阻塞其他用户对数据库的访问 。 一致性备份是 pg_dump 开始运行时,给数据库打了一个快照,且在 pg_dump 运行过程 中发生的更新将不会被备份。 …...

pikachu靶场搭建详细步骤
一、靶场下载 点我去下载 二、靶场安装 需要的环境: mysqlApaches(直接使用小皮面板Phpstudy:https://www.xp.cn/),启动他们 设置网站,把靶场的路径对应过来 对应数据库的信息 由于没有核对数据库的信…...
HarmonyOS NEXT开发进阶(五):装饰器讲解
一、Provide Consume 父组件与子组件的子组件(官方叫法:后代组件)双向同步数据(即,父组件与后代组件可以相互操作 Provide 修饰的数据) 注意:Provide 与 Consume声明的变量名必须一致。 import {TestChild } from .…...

【编译原理】往年题汇总(山东大学软件学院用)
🌈 个人主页:十二月的猫-CSDN博客 🔥 系列专栏: 🏀编译原理_十二月的猫的博客-CSDN博客 💪🏻 十二月的寒冬阻挡不了春天的脚步,十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前言 2. …...
【漏洞复现】F5 BIG-IP Next Central Manager SQL注入漏洞(CVE-2024-26026)
免责声明 请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,作者不为此承担任何责任。工具来自网络,安全性自测,如有侵权请联系删除。本次测试仅供学习使用,如若非法他用,与平台和本文作…...

python打卡day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...

YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...

Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...

如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
大数据学习(132)-HIve数据分析
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言Ǵ…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...
LRU 缓存机制详解与实现(Java版) + 力扣解决
📌 LRU 缓存机制详解与实现(Java版) 一、📖 问题背景 在日常开发中,我们经常会使用 缓存(Cache) 来提升性能。但由于内存有限,缓存不可能无限增长,于是需要策略决定&am…...

C++ 设计模式 《小明的奶茶加料风波》
👨🎓 模式名称:装饰器模式(Decorator Pattern) 👦 小明最近上线了校园奶茶配送功能,业务火爆,大家都在加料: 有的同学要加波霸 🟤,有的要加椰果…...