【Rust自学】19.5. 高级类型

news2025/4/21 21:28:07

喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)
请添加图片描述

19.5.1.使用newtype模式实现类型安全和抽象

在 19.2. 高级trait 中(具体来说是19.2.6. 使用newtype模式在外部类型上实现外部trait)我们就使用了newtype模式为Vector实现了Display trait。

在19.2.2. 默认泛型参数和运算符重载中我们还写过一个 MillimetersMeters结构体用来分别存储毫米和米的数据,由于两个数据并不能直接相加减也就避免了单位混用的问题。

我们还可以使用newtype模式来抽象出类型还有其他一些特性:

  • 新类型可以公开与私有内部类型的API不同的公共API
  • 新类型还可以隐藏内部实现(在 17.1.2. 封装 中提到过)

19.5.2. 类型别名

Rust 提供了声明类型别名的能力,以便为现有类型提供另一个名称(很像泛型)。

使用了类型别名需要type关键字。例如:

type Kilometers = i32;

我们把Kilometers称为i32近义词。你可以像使用i32那样使用Kilometers

fn main() {
	type Kilometers = i32;

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

    println!("x + y = {}", x + y);
}
  • 因为Kilometersi32是相同的类型,所以我们可以将两种类型的值相加

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

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

在整个代码中将这种冗长的类型写入函数签名和类型注释可能会很烦人并且容易出错。如下例:

    let f: Box<dyn Fn() + Send + 'static> = Box::new(|| println!("hi"));

    fn takes_long_type(f: Box<dyn Fn() + Send + 'static>) {
        // ...
    }

    fn returns_long_type() -> Box<dyn Fn() + Send + 'static> {
        // ...
    }

类型别名通过减少重复使该代码更易于管理,而且一个有意义的名称可以更好地传达意图。我们对上面的代码进行修改:

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

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

    fn takes_long_type(f: Thunk) {
        // ...
    }

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

类型别名也常与Result<T, E>类型一起使用,以减少重复。如下例:

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>;
}

I/O操作通常返回Result<T, E>以处理操作失败的情况。它的std::io::Error表示所有可能的I/O错误。std::io中的许多函数将返回Result<T, E>。其中Estd::io::Error

Result<..., Error>重复了很多次。因此,std::io使用了类型别名:

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

Write特征函数签名最终看起来像这样:

use std::fmt; 

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

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>以及特殊语法,如?符(在 9.3.2. ?运算符 中有讲)。

19.5.3. never类型

Rust 有一个特殊类型叫!,这在类型理论术语中被称为空类型,因为它没有值。我们更喜欢称其为never类型,因为它写在函数返回值类型的位置。

举个例子:

fn bar() -> ! {
	
}

这段代码被解读为“函数bar永不会返回”。从不返回的函数称为发散函数

那么never类型有什么作用呢?让我们以第二章猜数游戏的一段代码为例:

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

这么写没问题,那如果我们这样写呢:

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

这段代码会出问题,因为match两个分支返回值的类型不一样,Rust作为强类型语言必须知道所值的准确类型。guess类型可能是i32&str,而Rust要求guess只能是一种类型。

也就是说,这种写法下 match下的所有分支的返回值类型都得一样

那么回看正确的代码:Ok返回的num类型是u32Err执行的continue返回类型是什么呢?如果是代表没有返回值的单元类型()Rust就无法判断guess的值到底是u32类型还是()类型。

这就是never类型的用武之地: continue有一个返回类型是!。也就是说,当 Rust查看guess的类型时,它会先查看两个match分支,前者的返回值为u32 ,后者的返回值值为!。因为!永远不可能有返回值值,Rust就明白guess的类型是u32

never类型对于panic!宏的作用也是如此。看看unwrap的定义:

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!! ,所以match表达式返回值整体就是T。这段代码之所以有效,是因为panic!不返回值,而是结束程序。

实际上,loop也是!,因为loop执行的无尽循环不会结束,所以就不可能有返回值。然而,如果我们包含一个break ,情况就不是这样了,因为循环在到达break时就会终止。

19.5.4. 动态大小和和Sized trait

Rust 需要了解有关其类型的某些详细信息,例如为特定类型的值分配多少空间。这是得动态大小类型(dynamically sized types) 这个概念有些迷惑人。它有时被称为DSTunsized types,这些类型允许我们使用只能在运行时知道其大小的值来编写代码。

我们使用str(不是&str也不是String)这个动态大小类型为例:

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

在运行时之前我们无法知道字符串有多长,这意味着我们无法创建str类型的变量,所以上面的代码例是不能运行的。

Rust 需要知道为特定类型的任何值分配多少内存,并且同一类型的所有值必须使用相同的内存量。如果Rust允许我们编写这段代码,那么这两个str值将需要占用相同的空间量。但它们的长度不同: s1需要12个字节的存储空间,而s2需要 15 个字节。这就是为什么无法创建保存动态大小类型的变量的原因。

那么我们该怎么办呢?一般来说,将s1s2的类型设为&str而不是str就能解决问题:

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

切片数据结构只存储切片的起始位置和长度。因此,虽然&T是一个存储了内存地址的单个值T位于, &str是两个值(在 4.5. 切片(Slice) 有讲):

  • str的地址(usize)
  • str的长度(usize)

因此,我们可以在编译时知道&str值的大小:它是usize长度的两倍。也就是说,我们总是知道&str的大小,无论它引用的字符串有多长。

一般来说,Rust中使用动态大小类型的最好方式是:它们有一个额外的元数据来存储动态信息的大小。动态大小类型的黄金法则是,我们必须始终将动态大小类型的值放在某种指针后面

我们可以将str与各种指针组合:例如Box<str>Rc<str>。而trait实际上也是动态大小类型。为了使用动态大小类型,Rust提供了Sized trait来确定类型的大小在编译时是否已知。对于编译时大小已知的所有内容,都会自动实现此trait。此外,Rust隐式地​​为每个泛型函数添加了Sized trait。

也就是说,像这样的通用函数定义:

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

它的实际写法是:

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

默认情况下,泛型函数仅适用于编译时大小已知的类型。但是也可以使用?Sized特殊语法来放宽此限制:

fn generic<T: ?Sized>(t: &T) {
    // ...
}
  • ?Sized意味着“ T可能实现也可能没实现Sized trait”,也就是T可能是动态大小类型也可能不是。这种表示方法不需要泛型类型在编译时必须具有已知大小这个默认条件。有这种含义的?Trait语法仅适用于Sized trait ,没有任何其他trait。
  • 我们将t参数的类型从泛型T切换为&T。因为类型可能没实现Sized trait,就是动态大小类型,所以我们需要用指针包裹动态大小类型。

使用动态大小类型的最好场景是与trait配合时:有时候我们会要求某些数据必须实现某些trait或是指定的生命周期,但不知道具体是什么类型,所以就可以使用指针包裹动态类型的写法。如下例:

type Job = Box<dyn FnOnce() + Send + 'static>;

这个例子就使用了类型别名指针包裹动态类型的写法,Job可以是任何同时能实现FnOnce() trait、Send trait和'static生命周期的类型

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

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

相关文章

113,【5】 功防世界 web unseping

进入靶场 代码审计 <?php // 高亮显示当前 PHP 文件的源代码&#xff0c;方便开发者查看代码结构和内容 highlight_file(__FILE__);// 定义一个名为 ease 的类 class ease {// 私有属性 $method&#xff0c;用于存储要调用的方法名private $method;// 私有属性 $args&…

leetCode刷题-图、回溯相关

岛屿数量 class Solution { private:int mi;int mj; public:int numIslands(vector<vector<char>>& grid) {mi grid.size() - 1; // i的范围 0~mimj grid[0].size() - 1; // j的范围 0~mjint landnum 0;bool sea false;do {pair<int, int> res …

Windows编程:下载与安装 Visual Studio 2010

本节前言 在写作本节的时候&#xff0c;本来呢&#xff0c;我正在写的专栏&#xff0c;是 MFC 专栏。而 VS2010 和 VS2019&#xff0c;正是 MFC 学习与开发中&#xff0c;可以使用的两款软件。然而呢&#xff0c;如果你去学习 Windows API 知识的话&#xff0c;那么&#xff0…

OpenEuler学习笔记(十八):搭建企业云盘服务

要在 OpenEuler 上搭建企业云盘&#xff0c;可借助一些开源软件来实现&#xff0c;以下以 Nextcloud 为例详细介绍搭建步骤。Nextcloud 是一款功能丰富的开源云存储解决方案&#xff0c;支持文件共享、同步、协作等多种功能。 1. 系统环境准备 确保 OpenEuler 系统已更新到最…

什么是三层交换技术?与二层有什么区别?

什么是三层交换技术&#xff1f;让你的网络飞起来&#xff01; 一. 什么是三层交换技术&#xff1f;二. 工作原理三. 优点四. 应用场景五. 总结 前言 点个免费的赞和关注&#xff0c;有错误的地方请指出&#xff0c;看个人主页有惊喜。 作者&#xff1a;神的孩子都在歌唱 大家好…

Ollama+deepseek+Docker+Open WebUI实现与AI聊天

1、下载并安装Ollama 官方网址&#xff1a;Ollama 安装好后&#xff0c;在命令行输入&#xff0c; ollama --version 返回以下信息&#xff0c;则表明安装成功&#xff0c; 2、 下载AI大模型 这里以deepseek-r1:1.5b模型为例&#xff0c; 在命令行中&#xff0c;执行&…

Linux生成自签证书【Nginx】

&#x1f468;‍&#x1f393;博主简介 &#x1f3c5;CSDN博客专家   &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01…

网络安全 | 加密技术揭秘:保护数据隐私的核心

网络安全 | 加密技术揭秘&#xff1a;保护数据隐私的核心 一、前言二、对称加密技术2.1 原理2.2 优点2.3 缺点2.4 应用场景 三、非对称加密技术3.1 原理3.2 优点3.3 缺点3.4 应用场景 四、哈希函数4.1 原理4.2 优点4.3 缺点4.4 应用场景 五、数字签名5.1 原理5.2 优点5.3 缺点5…

使用服务器部署DeepSeek-R1模型【详细版】

文章目录 引言deepseek-r1IDE或者终端工具算力平台体验deepseek-r1模型总结 引言 在现代的机器学习和深度学习应用中&#xff0c;模型部署和服务化是每个开发者面临的重要任务。无论是用于智能推荐、自然语言处理还是图像识别&#xff0c;如何高效、稳定地将深度学习模型部署到…

DirectX11 With Windows SDK--02 顶点/像素着色器的创建、顶点缓冲区

Direct3D 11 总结 —— 4 绘制三角形_direct绘制三角形-CSDN博客 DirectX11 With Windows SDK--02 顶点/像素着色器的创建、顶点缓冲区 - X_Jun - 博客园 练习题 粗体字为自定义题目 尝试交换三角形第一个和第三个顶点的数据&#xff0c;屏幕将显示什么&#xff1f;为什么&…

第二次连接k8s平台注意事项

第二次重新打开集群平台 1.三台机子要在VMware打开 2.MobaBXterm连接Session 3.三个机子docker重启 systemctl restart docker4.主节点进行平台链接 docker pull kubeoperator/kubepi-server[rootnode1 home]# docker pull kubeoperator/kubepi-server [rootnode1 home]# # 运…

Mybatis篇

1&#xff0c;什么是Mybatis &#xff08; 1 &#xff09;Mybatis 是一个半 ORM&#xff08;对象关系映射&#xff09;框架&#xff0c;它内部封装了 JDBC&#xff0c;开发时只需要关注 SQL 语句本身&#xff0c;不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁…

三维粒子滤波(Particle Filter)MATLAB例程,估计三维空间中匀速运动目标的位置(x, y, z),提供下载链接

三维粒子滤波(Particle Filter)MATLAB例程,估计三维空间中匀速运动目标的位置(x, y, z) 文章目录 介绍功能运行结果代码介绍 本 MATLAB 代码实现了三维粒子滤波( P a r t i c l e F i l t e

设计模式Python版 享元模式

文章目录 前言一、享元模式二、享元模式示例 前言 GOF设计模式分三大类&#xff1a; 创建型模式&#xff1a;关注对象的创建过程&#xff0c;包括单例模式、简单工厂模式、工厂方法模式、抽象工厂模式、原型模式和建造者模式。结构型模式&#xff1a;关注类和对象之间的组合&…

从0开始,来看看怎么去linux排查Java程序故障

一&#xff0c;前提准备 最基本前提&#xff1a;你需要有liunx环境&#xff0c;如果没有请参考其它文献在自己得到local建立一个虚拟机去进行测试。 有了虚拟机之后&#xff0c;你还需要安装jdk和配置环境变量 1. 安装JDK&#xff08;以OpenJDK 17为例&#xff09; 下载JDK…

【MySQL】centos 7 忘记数据库密码

vim /etc/my.cnf文件&#xff1b; 在[mysqld]后添加skip-grant-tables&#xff08;登录时跳过权限检查&#xff09; 重启MySQL服务&#xff1a;sudo systemctl restart mysqld 登录mysql&#xff0c;输入mysql –uroot –p&#xff1b;直接回车&#xff08;Enter&#xff09; 输…

区块链项目孵化与包装设计:从概念到市场的全流程指南

区块链技术的快速发展催生了大量创新项目&#xff0c;但如何将一个区块链项目从概念孵化成市场认可的产品&#xff0c;是许多团队面临的挑战。本文将从孵化策略、包装设计和市场落地三个维度&#xff0c;为你解析区块链项目成功的关键步骤。 一、区块链项目孵化的核心要素 明确…

【数据科学】一个强大的金融数据接口库:AKShare

文章目录 1. AKShare 简介2. 安装 AKShare3. AKShare 核心功能3.1 获取股票数据3.2 获取股票实时数据3.3 获取基金数据3.4 获取期货数据3.5 获取外汇数据3.6 获取数字货币数据 4. 数据处理与存储5. 实战案例6. 总结 AKShare 是一个开源的金融数据接口库&#xff0c;它提供了简单…

Ubuntu 22.04系统安装部署Kubernetes v1.29.13集群

Ubuntu 22.04系统安装部署Kubernetes v1.29.13集群 简介Kubernetes 的工作流程概述Kubernetes v1.29.13 版本Ubuntu 22.04 系统安装部署 Kubernetes v1.29.13 集群 1 环境准备1.1 集群IP规划1.2 初始化步骤&#xff08;各个节点都需执行&#xff09;1.2.1 主机名与IP地址解析1.…

Java项目: 基于SpringBoot+mybatis+maven+mysql实现的智能学习平台管理系(含源码+数据库+毕业论文)

一、项目简介 本项目是一套基于SpringBootmybatismavenmysql实现的智能学习平台管理系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、…