当前位置: 首页 > web >正文

Rust 登堂 之 Sized和不定长类型 DST(七)

在Rust 中类型有多种抽象的分类方式,例如本书之前章节的: 基本类型,集合类型,复合类型等,再比如说,如果从编译器合适能获知类型大小的角度触发,可以分为两类

        定长类型 sized ,这些类型的大小在编译时是一直的。

        不定长类型 unsized ,与定长类型相反,它的大小只有到了程序运行时才能动态获知,这种类型又被称之为 DST

动态大小类型DST

读者大大们之前学过的几乎所有类型,都是固定大小的类型,包括集合 Vec ,String 和 HashMap 等,而动态大小类型刚好与之相反: 编译器无法在编译期得知该类型值的大小,只有到了程序运行时,才能动态获知,对于动态类型,我们使用 DST(dynamically sized types ) 或者 unsized 类型来称呼它。

上述的这些集合虽然底层数据可动态变化,感觉显示动态大小的类型,但是实际上,这些底层数据只是保存在堆上,在栈中还存有一个引用类型,该引用包含了集合的内存地址,元素数目,分配空间信息,通过这些信息,编译器对于该集合的实际大小了若指掌, 最最重要的是: 栈上的引用类型是固定大小的,因此它们依然是固定大小的类型。

正因为编译器无法在编译期获知类型大小,若你试图在代码中直接使用DST类型,将无法通过编译

现在给你一个挑战:  想出几个DST类型,俺厚黑地说一举,估计大部分人都想不出这样的类型,

试图创建动态大小的数组

fn my_function(n: usize) {let array = [123;n];
}

以上代码就会报错(错误输出的捏融并不是因为 DST,但根本原因是类似的),因为 n 在编译器无法得知,而数组类型的一个组成部分就是长度,长度变为动态的,自然类型就变成了 unsized.

切片

切片也是一个典型的DST 类型,具体详情常见另一篇文章: 易混淆的切片和切片引用

str

考虑以下这个类型:str , 感觉有点眼生? 是的,它既不是 String 动态字符串,也不是 &str 字符串切片,而是一个 str ,它是一个动态类型,同时还是 String 和 &str 的底层数据类型,由于 str 是动态类型,因此它的大小直到运行期才知道,下面的代码会因此报错

// error  
let s1: str = "Hello there!";
let s2: str = "How's it goring?";// ok 
let s3: &str = "on?"

Rust 需要明确地知道 一个特定类型的值占据了多少内存空间,同时该类型的所有值都必须使用相同 大小的内存,如果Rust 允许我们使用这种动态类型,那么这两个 str 值就需要占用同样大小的内存,这显然是不现实的 s1 占用了 12直接, s2 占用了 15字节 ,总不至于为了满足同样的内存大小,空白字符去填补字符串吗?

所以,我们只有一条路走,那就是给它们一个固定大小的类型 : &str, 那么为何字符串切片 &str 就是固定大小呢? 因为它的引用存储在栈上,具有固定大小(类似指针),同时它指向的数据存储在堆中,也是已知的大小,在加上 &str 引用中包含有堆上数据内存地址,长度等信息,因此最终可以得出字符串切片是固定大小类型的结论

与&str 类似, String 字符串也是固定大小的类型

正因为 &str 的引用有了底层堆数据的明确信息,它才是固定大小类型,假设如果它没有这些信息呢?那它也将编程一个动态类型,因此,将动态数据固定化的秘诀就是使用引用指向这些动态数据,然后在引用存储相关的内存位置,长度等信息

特征对象

fn foobar_1(thing: &dyn MyThing) {} // ok
fn boobar_2(thing: Box<dyn MyThing>) {} // ok
fn foobar_3(thing: MyThing) {}          // Error!

如上所示,只能通过引用或 Box 的方式来使用特征对象,直接使用将报错!

总结:只能间接使用的DST

Rust中常见的 DST 类型有 str , [T],dyn Trait, 它们都无法单独被使用,必须要通过引用或者 Box来间接使用。

我们之前已经见过,使用 Box 将一个没有固定大小的特征编程一个有固定大小的特征对象,那能否故伎重施,将 str 封装成一个固定大小类型? 留个悬念先,我们来看看 Sized 特征

Sized特征

既然动态类型的问题这么大,那么在使用泛型时,Rust 如何保证我们的泛型参数是固定大小的类型呢?例如以下泛型函数

fn generic<T>(t: T) {//
}

在上面,Rust 自动添加的特征约束 T: Sized ,表示泛型函数只能用于一切实现了 Sized 特征的类型上,而所有在编译时就能知道其大小的类型,都会自动实现 Sized 特征,例如。。。。 也没啥好例如的,你能想到的几乎所有类型都实现了Sized 特征,除了上面那个坑坑的 str ,哦,还有特征。

每一个特征都是一个可以通过名称来引用的动态大小类型,因此如果先把特征作为具体的类型来传递给函数,你必须将其转换成一个特征对象,诸如 &dyn Trait 或者Box<dyn Trait> 这些引用类型

现在还有一个问题: 假如先在泛型函数中  使用动态数据类型怎么版? 可以使用 ?Sized 特征(不得不说这个明明方式很 Rusty 

fn generic<T: ?Sized>(t: &T) {// snip
}

?Sized 特征用于表明类型 T 既有可能是固定大小的类型,也可能是动态大小的类型,还有一点要注意的是,函数参数类型从 T 编程 &T ,因为 T 可能是动态大小的,因此需要用一个固定大小的指针(引用)来包裹它。

Box<str>

在结束前,再来看看之前遗留的问题:使用 Box 可以将一个动态大小的特征编程一个具有固定大小的特征对象,能否故伎重施,将 str 封装成一个固定大小类型?

先回想下,章节前面的内容介绍过该如何把一个动态大小类型转换成固定大小的类型,使用引用指向这些动态数据,然后在引用中存储相关的内存位置,长度等信息。

好的,根据这个,我们来一起推测,首先,Box<str> 使用了一个引用来指向 str ,嗯,满足了第一个条件,但是第二个条件呢? Box 中有该 str 的长度信息吗? 显然是No ,那为什么特征就是可以编程特征对象? 其实这个还蛮复杂的,简单来说,对于特征对象,编译器无需知道它具体是什么类型,只要知道它能调用哪几个方法即可,因此编译器帮我们实现来剩下的一切。

fn main() {let s1: Box<str> = Box::new("Hello there!"  as str);
}

报错如下

error[E0277]: the size for values of type `str` cannot be known at compilation time
--> src/main.rs:2:24
|
2 |     let s1: Box<str> = Box::new("Hello there!" as str);
|                        ^^^^^^^^ doesn't have a size known at compile-time
|
= help: the trait `Sized` is not implemented for `str`
= note: all function arguments must have a statically known size

提示得很清晰,不知道 str 的大小,因此无法使用这种语法进行 Box 进装,但是你可以这么做:

let s1: Box<str> = "Hello there!".into();

主动转换成 str 的方式不可行,但是可以让编译器来帮我们完成,只要告诉它我们需要的类型即可。

http://www.xdnf.cn/news/19411.html

相关文章:

  • leetcode刷题记录08——top100题里的5道中等题
  • Vue基础知识-methods事件绑定(@事件名和v-on:事件名)和常用事件修饰(.prevent/.stop/.once/.enter)
  • Coze源码分析-API授权-删除令牌-后端源码
  • 【15】VisionMaster入门到精通——--通信--TCP通信、UDP通信、串口通信、PLC通信、ModBus通信
  • 鸿蒙ArkTS 核心篇-16-循环渲染(组件)
  • lvgl模拟器 被放大 导致显示模糊问题
  • Notepad++使用技巧1
  • 日志ELK、ELFK、EFK
  • 快速学习和掌握Jackson 、Gson、Fastjson
  • AI + 行业渗透率报告:医疗诊断、工业质检领域已进入规模化落地阶段
  • GD32入门到实战20--定时器
  • 【LeetCode】大厂面试算法真题回忆(122) —— 篮球比赛
  • react性能优化有哪些
  • SSR降级CSR:高可用容灾方案详解
  • Android中handler机制
  • 【Android】JSONObject和Gson的使用
  • HTTP的概念、原理、工作机制、数据格式和REST
  • 《C++——makefile》
  • 三重积分的性质
  • 【MATLAB绘图进阶教程】(2-6)动态绘图制作详解与例程,包括drawnow、pause、getframe、video write等命令
  • 机器学习时间序列算法进行随机划分数据是不合适的!
  • Dify1.8.0最新版本安装教程:Ubuntu25.04系统本地化安装部署Dify详细教程
  • 移动零,leetCode热题100,C++实现
  • IP-Guard支持修改安全区域密级文字和密级级数
  • 嵌入式学习日记(38)HTTP
  • Java学习笔记-多线程基础
  • Kafka 4.0 生产者配置全解析与实战调优
  • Go语言流式输出实战:构建高性能实时应用
  • 数据结构(力扣刷题)
  • 蜂窝通信模组OpenCPU的介绍