Rust 学习笔记:高级类型

基于Rust的Polars学习笔记 摘要:基于Rust的Polars的学习笔记 ,高性能数据分析,实时数据管道,嵌入式数据分析,WebAssembly应用 数据库加速层,地理空间数据处理,金融量化研究,生物信息学技术 阅读详情

Rust 学习笔记:高级类型

Rust 类型系统有一些我们到目前为止已经提到但还没有讨论的特性。

我们将从一般地讨论 newtype 开始,并研究为什么 newtype 作为类型是有用的。然后,我们将继续讨论类型别名,这是一个类似于 newtypes 的特性,但在语义上略有不同。我们还将讨论 ! 类型和动态大小的类型。

使用 Newtype 模式实现类型安全和抽象

Newtype 模式可以静态地强制值永远不会混淆,并指示值的单位。

例如,有两个元组结构体:

struct Millimeters(u32);
struct Meters(u32);

Millimeters 和 Meters 结构体在 newtype 中包装了 u32 值。如果我们编写一个函数的形参类型是 Millimeters,那么我们就不能误传 Meters 或 u32 类型。

我们还可以使用 Newtype 模式抽象出类型的一些实现细节,以增强类型安全。新类型可以暴露与私有内部类型的 API 不同的公共 API。

Newtype 模式还可以隐藏内部实现。

例如,我们可以提供 People 类型来包装 HashMap<i32, String>,该 HashMap<i32, String> 存储与其姓名相关联的人员 ID。

struct People(HashMap<i32, String>);

使用 People 的代码将只与我们提供的公共 API 交互,无法直接访问 HashMap。代码不需要知道我们在内部给名称分配了一个 i32 类型的 ID。

Newtype 模式是实现封装以隐藏实现细节的一种轻量级方式。

使用类型别名创建类型同义词

Rust 提供了声明类型别名的能力,可以为现有类型赋予另一个名称。为此,我们使用 type 关键字。

例如,我们可以像这样为 i32 创建别名 Kilometers:

    type Kilometers = i32;

    let x: i32 = 5;
    let y: Kilometers = 5;

    println!("x + y = {}", x + y);

别名 Kilometers 是 i32 的同义词,而不是一个单独的新类型。因为 Kilometers 类型的值将被视为 i32 类型的值,所以可以将这两种类型的值相加,并且可以将 Kilometers 类型的值传递给带有 i32 形参的函数。

然而,使用这种方法,我们无法获得从前面讨论的 Newtype 模式中获得的类型检查好处。换句话说,如果我们在某个地方混淆了 Kilometers 和 i32 的值,编译器不会给我们一个错误。

类型同义词的主要用例是减少重复。例如,我们可能有一个像这样长的类型:

Box<dyn Fn() + Send + 'static>

在函数签名中编写这种冗长的类型,并在整个代码中作为类型注释编写这种类型可能会令人厌烦,而且容易出错。

类型别名通过减少重复使代码更易于管理,为类型别名选择一个有意义的名称也可以帮助传达你的意图。我们为该类型引入了一个名为 Thunk 的别名,之后就可以用更短的别名 Thunk 替换该类型的所有用法。

    type Thunk = Box<dyn Fn() + Send + 'static>;

    let f: Thunk = Box::new(|| println!("hi"));

    fn takes_long_type(f: Thunk) {
        // --snip--
    }

    fn returns_long_type() -> Thunk {
        // --snip--
    }

类型别名通常也与 Result<T, E> 类型一起使用,以减少重复。考虑标准库中的 std::io 模块。I/O 操作通常返回 Result<T, E> 来处理操作失败的情况。这个库有一个 std::io::Error 结构体,它表示所有可能的 I/O 错误。std::io 中的许多函数将返回 Result<T, E>,其中 E 为 std::io::Error,例如 Write trait 中的这些函数:

use std::fmt;
use std::io::Error;

pub trait Write {
    fn write(&mut self, buf: &[u8]) -> Result<usize, Error>;
    fn flush(&mut self) -> Result<(), Error>;

    fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>;
    fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Error>;
}

Result<…, Error> 重复了很多次,因此,std::io 有这样的类型别名声明:

type Result<T> = std::result::Result<T, std::io::Error>;

由于此声明位于 std::io 模块中,因此可以使用完全限定别名 std::io::Result<T>,即 Result<T, E>,其中 E 以 std::io::Error 形式填写。Write trait 函数签名最终看起来像这样:

pub trait Write {
    fn write(&mut self, buf: &[u8]) -> Result<usize>;
    fn flush(&mut self) -> Result<()>;

    fn write_all(&mut self, buf: &[u8]) -> Result<()>;
    fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<()>;
}

类型别名在两个方面有帮助:它使代码更容易编写,并在 std::io 中提供一致的接口。因为它是别名,它只是另一个 Result<T, E>,这意味着我们可以使用任何处理 Result<T, E> 的方法,以及特殊语法,如 ? 操作符。

永远不会返回的 never 类型

Rust 有一个特殊的类型叫做 !。这在类型理论中被称为空类型,因为它没有值。

我们更喜欢称它为 never 类型,因为当函数永远不会返回时,它代替返回类型。

下面是一个例子:

fn bar() -> ! {
    // --snip--
}

从不返回的函数称为发散函数。我们不能创建这种 ! 类型的值,所以 bar 函数永远不可能返回。

但是永远不能为其创建值的类型有什么用呢?

考虑以下代码:

        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

匹配分支必须都返回相同的类型。因此,下面的代码不起作用:

    let guess = match guess.trim().parse() {
        Ok(_) => 5,
        Err(_) => "hello",
    };

这段代码中的 guess 类型必须是一个整数和一个字符串,Rust 要求 guess 只有一种类型。

Rust 为什么允许我们从一个分支返回 u32,并拥有以 continue 结尾的另一个分支?continue 返回的是什么呢?

你可能已经猜到了,continue 有一个 ! 值。也就是说,当 Rust 计算 guess 的类型时,它会查看两个匹配分支,前者的值为 u32,后者的值为 !。因为 ! 不能有值,Rust 决定 guess 的类型是 u32。

描述这种行为的正式方式是,类型 ! 的表达式可以被强制转换为任何其他类型。我们可以用 continue 结束这个匹配分支,因为 continue 没有返回值。相反,它将控制移回循环的顶部,所以在 Err 的情况下,我们从不给 guess 赋值。

never 类型对 panic! 宏也很有用。回想一下我们在 Option<T> 值上调用的 unwrap 函数,它产生一个值或者一个 panic:

impl<T> Option<T> {
    pub fn unwrap(self) -> T {
        match self {
            Some(val) => val,
            None => panic!("called `Option::unwrap()` on a `None` value"),
        }
    }
}

Rust 发现 val 的类型是 T,而 panic! 的类型是 !,所以整个匹配表达式的结果是 T。panic! 不会产生值,它结束了程序。在 None 的情况下,我们不会从 unwrap 返回值,所以这段代码是有效的。

最后,loop 有一个 ! 类型。

    print!("forever ");

    loop {
        print!("and ever ");
    }

循环永远不会结束,所以 ! 是表达式的值。然而,如果我们包含一个 break,这就不成立了,因为当循环到达 break 时就会终止。

动态大小类型和 Sized trait

Rust 需要知道关于其类型的某些细节,例如为特定类型的值分配多少空间。这使得它的类型系统中的动态大小类型有点令人困惑。这些类型有时被称为 DST 或 unsized 类型,它们允许我们使用只有在运行时才能知道大小的值来编写代码。

让我们深入研究 str 的动态大小类型的细节。没错,不是&str,而是 str ,它本身就是 DST。直到运行时才知道字符串的长度,这意味着我们不能创建 str 类型的变量,也不能接受 str 类型的参数。

考虑下面的代码,它不起作用:

    let s1: str = "Hello there!";
    let s2: str = "How's it going?";

Rust 需要知道为特定类型的任何值分配多少内存,并且一个类型的所有值必须使用相同数量的内存。这就是为什么不能创建包含动态大小类型的变量的原因。

那么我们该怎么办呢?在这种情况下,你已经知道答案了:我们将 s1 和 s2 类型设置为 &str 而不是 str。切片数据结构只是存储了切片的起始位置和长度。因此,尽管 &T 是存储 T 所在的内存地址的单个值,但 &str 是两个值:str 的地址和长度。因此,我们可以在编译时知道 &str 值的大小:它是 usize 类型大小的两倍。也就是说,我们总是知道 &str 的大小,不管它引用的字符串有多长。

一般来说,这就是 Rust 中使用动态大小类型的方式:它们有一个额外的元数据来存储动态信息的大小。

动态大小类型的黄金法则是,我们必须总是把动态大小类型的值放在某种类型的指针后面。

可以将 str 与各种指针组合:例如,Box<str>或 Rc<str>。

事实上,你以前已经见过这种情况,但使用的是不同的动态大小类型:trait。

每个 trait 都是一个动态大小的类型,我们可以通过 trait 的名字来引用它。之前我们提到要使用 trait 作为 trait 对象,我们必须把它们放在一个指针后面,比如 &dyn Trait、Box<dyn Trait> 或者 Rc<dyn Trait>。

为了使用 DST, Rust 提供了 Sized trait 来确定类型的大小在编译时是否已知。对于在编译时已知大小的所有内容,都会自动实现此 trait。

此外,Rust 隐式地为每个泛型函数添加了 Sized 约束。也就是说,一个像这样的泛型函数定义:

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

实际上是这样:

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

默认情况下,泛型函数只对编译时已知大小的类型起作用。

但是,你可以使用以下特殊语法来放宽此限制:

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

使用 ?Sized 约束的 trait 意味着“T 可能是也可能不是 Sized”,这个符号覆盖了泛型类型在编译时必须具有已知大小的默认值。具有此含义的 ?Trait 语法仅适用于 Sized,而不适用于任何其他 trait。

还请注意,我们将 t 形参的类型从 T 转换为 &T,因为该类型可能不实现 Sized,我们需要在某种指针后面使用它。在本例中,我们选择了一个引用。

代码的自由之旅:用AI写出无版权烦恼的Verilog 他们还基于这个数据集,训练了一个名叫“FreeV”的AI模型,不仅能写出好用的Verilog代码,还把版权侵权的风险降到了仅3%。为了让AI安全地写Verilog,研究团队提出了三个“法宝”:一个检测AI是否会生成侵权代码的测试方法、一个干净的Verilog数据集FreeSet,还有一个基于FreeSet训练的AI模型FreeV。AI的记忆力超强,能记住训练时见过的代码。测试发现,一些用Verilog数据训练过的AI,比如VeriGen,生成侵权代码的概率从9%涨到了15%,说明它们可能学了不该学的代码。 阅读详情

相关推荐

GD32F105RBT6 keil工程模板

创建GD32F105RBT6的keil初始基础工程,未来的所有外设驱动都是基于这个基础工程进行扩展。

RMDYBW的博客 1926

Rust 入门 泛型和特征-深入特征 (十五)

有时,我们会需要让某个特征A 能使用另一个特征B 的功能( 另一种形式的特征约束) ,这种情况下,不仅仅要为类型实现特征A ,还要为类型实现特征B 才行,这就是基特征( super trait) .例如有一个特征OutlinePrint , 它有一个方法, 能够对当前的实现类型进行格式化输出:println!println!println!println!println!

m0_48050264的博客 2472

Transformers 中的 Softmax 可以并行加速么?

通过融合前两个计算步骤,降低执行Reduce_max和Reduce_sum等操作的通信开销。利用共享内存来存储临时数据,以减少对全局内存的访问成本。

stephen147的博客 1234

Rust 登堂 之 深入Rust 类型(六)

何为 newtype?简单来说,就是使用元组结构体的方式将已有的类型包裹起来 struct Meters(u32) ,那么此处 Meters 就是一个 newtype为何需要 newtype?Rust 这多如繁星的Old 类型满足不了我们吗?这是因为自定义类型可以让我们给出更具有意义和可读性的类型名,例如与其使用u32 作为距离的单位类型,我们可以使用 Meters ,它的可读性要好得多对于某些场景,只有 newtype 可以很好解决隐藏内部类型的细节一箩筐的理由 ~~~ 让我们先从第二点将其。

m0_48050264的博客 966

毕设分享|基于16单片机的指纹识别电子密码锁设计

我们今天的工作生活中,很多的场合都需要身份的认证,而传统的基于标志的身份认证技术由于受到证件伪造以及密码破解等手段的威胁,逐渐表现得有些力不从心。怎样保护自己的重要文件资料,如何保证自己的隐私不被泄露?在传统的身份认证中,我们往往使用密码加密法,但是这种方法只是"防君子不防小人"。在高明的黑客眼里,由几个字符组成的密码脆弱得不堪一击。现在,科技的发展让我们有了新的选择——生物识别技术。将生物识别技术应用于笔记本、门锁等方面,可以对文件、财产起保护作用,并且可以进行身份识别。

Sen_091的博客 1021

LLM的MCP协议:初识MCP,搭建第一个MCP Server

MCP(Model Context Protocol),是一个开发的协议,标准化了应用程序如何为大模型提供上下文。MCP提供了一个标准的为LLM提供数据、工具的方式,使用MCP会更容易的构建Agent或者是基于LLM的复杂工作流。

Criss@陈磊 2万+

Rust 学习笔记:高级 trait

Rust 学习笔记:高级 trait

ProgramNovice的博客 952

FISCO BCOS 系列(一)—— 搭建第一个区块链网络&配置控制台指南

本文详细介绍了FISCO BCOS区块链网络的搭建与配置过程,包括环境准备、单机四节点网络搭建、控制台配置使用等核心内容。从系统要求、依赖安装开始,逐步指导如何通过脚本快速搭建区块链网络,配置控制台连接,并演示了账户管理、合约部署调用等基础操作。同时提供了多节点网络配置、常见问题解决方案以及生产环境安全建议,为开发者提供了从入门到进阶的完整指南。

分享AI、区块链技术及应用,未来已来 808

Rust 学习笔记:高级函数和闭包

Rust 学习笔记:高级函数和闭包

ProgramNovice的博客 2093

Linux ARM平台开发系列讲解(LED) 2.1.1 如何点亮一个LED之添加设备树信息

1. 设备树添加设备信息 我拿到的开发板上有两颗LED供用户编程使用,分别是GPIO5_IO8和GPIO5_IO9,设备信息如下,不同板子的设备定义不同,根据自己的实际情况进行添加。 2. 编译设备树 根据自己拿到的SDK,执行编译命令,我这里拿到的SDK编译脚本为: ./build.sh kernel 3. 使用uuu工具烧录新编译好的内核 因为imx8 SDK设备树和内核是统一编译成一个镜像,所以只需要烧录内核,设备树文件也会烧录。 sudo uuu -b emmc imx-kernel.bin

DSMGUOGUO的博客 918

Python 爬虫教程:财经新闻实时抓取(最新资讯 + 关键词提取)

在当今信息爆炸的时代,财经新闻的实时获取对于投资者、研究人员以及普通大众来说都具有重要的价值。通过 Python 爬虫技术,我们可以高效地抓取财经新闻,并提取其中的关键信息,以便更好地进行数据分析和决策支持。本文将详细介绍如何使用 Python 编写一个财经新闻实时抓取系统,并结合关键词提取技术,帮助读者快速掌握 Python 爬虫的核心技能。

yansideyucsdn的博客 1049

Rust学习笔记

​这是我观看杨旭老师的Rust教程的笔记,笔记中省略了一些细节部分,如有不理解的请大家观看杨旭老师的视频。

qq_59294755的博客 2189

在 PFCG 中完成 SAP Fiori 对象分配:把 Business Catalog、Space、Page 与授权一次讲透

摘要:SAP Fiori权限配置的关键在于理解Business Catalog与Space的分层授权逻辑。传统SAP GUI中直接分配事务码的方式不适用于Fiori环境,必须通过PFCG为角色分配Catalog(提供应用访问权限)和Space(控制首页展示结构)。Catalog会自动解析所需OData服务授权,而Space则决定用户在Launchpad中看到的导航布局。常见错误包括直接分配Page而非其所属Space,或仅配置Space而忽略Catalog。最佳实践是优先使用Catalog驱动授权提议,再在

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 327

【Rust 学习笔记】Rust 基础数据类型介绍——字符和字符串类型

Rust的字符类型char会以32位值表示单个Unicode字符。Rust会对单独的字符使用char类型,而对字符串和文本流使用UTF-8编码。因此后面介绍的String会将其文本表示为UTF-8字节序列,而不是字符数组。字符字面量是使用单引号括起来的字符,比如 ‘8’ 或这 ‘j’。还可以使用全角 Unicode 字符。与字节字面量字符Rust字符字面量单引号(')‘\’’反斜杠(\)‘\\’换行(lf)‘\n’回车(cr)‘\r’制表符(tab)‘\t’

从善若水的博客 1475

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Rust 学习笔记:关于高级 trait 的练习题

Rust 学习笔记:关于高级 trait 的练习题

ProgramNovice的博客 323

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

Rust 学习笔记:基本语法和数据类型

Rust 学习笔记:基本语法和数据类型

ProgramNovice的博客 1618

学习笔记三—— Rust 的数据类型

类型类别可变示例说明i32u8整数✅默认整数是 i32f64f32浮点数✅默认浮点是 f64bool布尔❌truefalse条件判断char字符❌'中''🚀'Unicode 字符&str字符串❌"Tom"字面量,只读String字符串✅可变、堆分配tuple复合✅打包不同类型array复合✅[1, 2, 3]长度固定,同类型struct自定义✅建模现实对象enum自定义✅多种状态管理ZST特殊零大小类型,占位用!

pumpkin84514的博客 1030

Rust 学习笔记:闭包

Rust 学习笔记:闭包

ProgramNovice的博客 1504

Rust 学习笔记:比较数值

Rust 学习笔记:比较数值

ProgramNovice的博客 794

【Rust 学习笔记】Rust 基础数据类型介绍——固定宽度的数值类型

类型简要说明给定位宽的有符号整数和无符号整数与机器字(32bit、64bit)一样大的有符号整数和无符号整数f32、f64单精度IEEE浮点数和双精度IEEE浮点数bool布尔值charUnicode字符,32位宽(4字节)()单元元组(空元组)元组(允许混合类型)指向堆中值的拥有型指针共享引用和可变引用,非拥有型指针,其生命周期不能超出引用目标StringUTF-8字符串,动态分配大小&str对str的引用,指向UTF-8文本的非拥有型指针[f64;4]、[u8;

从善若水的博客 1534

Rust 学习笔记:Trait

Rust 学习笔记:Trait

ProgramNovice的博客 1716

【Rust 学习笔记】Rust 基础数据类型介绍——指针、元组和布尔类型

元组是各种类型的值的一个序列,序列中的每个元素用逗号分隔开并包裹在一个圆括号中。例如是一个元组,其第一个元素是一个静态分配的字符串,第二个元素是一个整数,它的类型是。给定一个元组值 t ,可以通过 t.0 、t.1 等访问其元素。元组有点儿像数组,即这两种类型都表示值的有序序列。元组的每个元素可以是不同的类型,而数组的元素必须都是相同的类型;元组只允许用常量作为索引,例如 t.4。不能通过写成 t.i 或者 t[i] 的形式来获取第 i 个元素;

从善若水的博客 1743

Rust学习笔记(下)

笔记的内容主要参考与《Rust 程序设计语言》,一些也参考了《通过例子学 Rust》和《Rust语言圣经》。Rust学习笔记分为上中下,其它两个地址在Rust学习笔记(上)和Rust学习笔记(中)。

Fishermen_sail的博客 1416

Rust学习笔记_08——String

Rust 语言中的字符串类型是其数据类型的核心部分之一,用于处理文本数据。在 Rust 中,字符串是不可变的(immutable)默认情况下,但可以声明为可变(mutable)。Rust 使用 UTF-8 编码来处理字符串,这意味着它可以处理几乎任何语言的字符。字符串类型和字面量字符串是两个不同的概念,尽管它们都与文本数据相关,但它们在内存管理、可变性和用途上有所不同。字符串切片是对字符串的某一部分的不可变引用。字符串切片是 UTF-8 编码的,并且通常从字符串字面量或。是可增长的,可以通过方法如。

LuckyLay的博客 1410

Rust 学习笔记:Stream

Rust 学习笔记:Streams

ProgramNovice的博客 1289

Rust学习笔记_16——Vector

索引访问可能会导致运行时错误(例如,如果索引超出范围),所以通常会使用。预先分配容量可以避免在添加元素时频繁地重新分配内存。是标准库中提供的动态数组类型,它是最常用的数据结构之一。宏是一个方便的语法糖,用于初始化包含指定元素的。表示数组中元素的类型。返回一个默认初始化的。,没有分配任何内存空间。创建一个具有指定容量的。在Rust编程语言中,方法在指定位置插入元素。

LuckyLay的博客 1778

【Rust 学习笔记】Rust 基础数据类型介绍——数组、向量和切片

类型简要说明给定位宽的有符号整数和无符号整数与机器字(32bit、64bit)一样大的有符号整数和无符号整数f32、f64单精度IEEE浮点数和双精度IEEE浮点数bool布尔值charUnicode字符,32位宽(4字节)()单元元组(空元组)元组(允许混合类型)指向堆中值的拥有型指针共享引用和可变引用,非拥有型指针,其生命周期不能超出引用目标StringUTF-8字符串,动态分配大小&str对str的引用,指向UTF-8文本的非拥有型指针[f64;4]、[u8;

从善若水的博客 1803
上一篇: Rust 学习笔记:高级函数和闭包
下一篇: Rust 学习笔记:宏
UestcXiye
博客等级 码龄8年 9029粉丝 1306原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

UestcXiye

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值