Rust使用feature特性和条件编译,以及常用feature使用说明

Cargo Feature 是非常强大的机制,可以为大家提供条件编译和可选依赖的高级特性,可以为你省下不少的代码量来判断操作系统和条件编译等功能。rust官方条件编译文档:Conditional compilation - The Rust Reference

features特性

Featuure 可以通过 Cargo.toml 中的 [features] 部分来定义:其中每个 feature 通过列表的方式指定了它所能启用的其他 feature 或可选依赖。

假设我们有一个 2D 图像处理库,然后该库所支持的图片格式可以通过以下方式启用:

[features]
# 定义一个feature并启动: webp, 但它并没有启用其它 feature
webp = []

当定义了 webp 后,我们就可以在代码中通过 cfg 表达式来进行条件编译(cfg表达式讲解说明:https://xiaoshen.blog.csdn.net/article/details/137040041?spm=1001.2014.3001.5502)。例如项目中的 lib.rs 可以使用以下代码对 webp 模块进行条件引入:

#[cfg(feature = "webp")]
pub mod webp;

#[cfg(feature = "webp")] 的含义是:只有在 webp feature 被定义后,以下的 webp 模块才能被引入进来。由于我们之前在 [features] 里定义了 webp,因此以上代码的 webp 模块会被成功引入。

在 Cargo.toml 中定义的 feature 会被 Cargo 通过命令行参数 --cfg 传给 rustc,最终由后者完成编译:rustc --cfg ...。若项目中的代码想要测试 feature 是否存在,可以使用 cfg属性 或 cfg 宏。

之前我们提到了一个 feature 还可以开启其他 feature,举个例子,例如 ICO 图片格式包含 BMP 和 PNG,因此当 ICO 图片格式被启用后,它还得确保启用 BMP 和 PNG 格式:

[features]
bmp = []
png = []
ico = ["bmp", "png"]
webp = []

对此,我们可以理解为: bmp 和 png 是开启 ico 的先决条件。

Feature 名称可以包含来自 Unicode XID standard 定义的字母,允许使用 _ 或 0-9 的数字作为起始字符,在起始字符后,还可以使用 -、+ 或 . 。

但是我们还是推荐按照 http://crates.io 的方式来设置 Feature 名称 : crate.io 要求名称只能由 ASCII 字母数字、_、- 或 + 组成。

default feature

默认情况下,所有的 feature 都会被自动禁用,可以通过 default 来启用它们:

[features]
default = ["ico", "webp"]
bmp = []
png = []
ico = ["bmp", "png"]
webp = []

使用如上配置的项目被构建时,default feature 首先会被启用,然后它接着启用了 ico 和 webp feature,当然我们还可以关闭 default

  • --no-default-features 命令行参数可以禁用 default feature
  • default-features = false 选项可以在依赖声明中指定
当你要去改变某个依赖库的  default 启用的 feature 列表时(例如觉得该库引入的 feature 过多,导致最终编译出的文件过大),需要格外的小心,因为这可能会导致某些功能的缺失

系统自带feature 

target_arch:目标的 CPU 架构

示例值:

"x86"
"x86_64"
"mips"
"powerpc"
"powerpc64"
"arm"
"aarch64"

target_feature:当前编译目标可用的每个平台特性

示例值:

"avx"
"avx2"
"crt-static"
"rdrand"
"sse"
"sse2"
"sse4.1"

target_os :目标操作系统

示例值:

"windows"
"macos"
"ios"
"linux"
"android"
"freebsd"
"dragonfly"
"openbsd"
"netbsd"
"none" (typical for embedded targets)

target_family :目标的更通用的描述,例如目标通常所属的操作系统或体系结构的系列。

示例值:

"unix"
"windows"
"wasm"

可选依赖

当依赖被标记为 "可选 optional" 时,意味着它默认不会被编译。假设我们的 2D 图片处理库需要用到一个外部的包来处理 GIF 图片: 

[dependencies]
gif = { version = "0.11.1", optional = true }

这种可选依赖的写法会自动定义一个与依赖同名的 feature,也就是 gif feature,这样一来,当我们启用 gif feautre时,该依赖库也会被自动引入并启用:例如通过 --feature gif 的方式启用 feauture。

注意:目前来说,[fetuare] 中定义的 feature 还不能与已引入的依赖库同名。但是在 nightly 中已经提供了实验性的功能用于改变这一点: namespaced features

当然,我们还可以通过显式定义 feature 的方式来启用这些可选依赖库,例如为了支持 AVIF 图片格式,我们需要引入两个依赖包,由于 AVIF 是通过 feature 引入的可选格式,因此它依赖的两个包也必须声明为可选的:

[dependencies]
ravif = { version = "0.6.3", optional = true }
rgb = { version = "0.8.25", optional = true }

[features]
avif = ["ravif", "rgb"]

之后,avif feature 一旦被启用,那这两个依赖库也将自动被引入。

注意:我们之前也讲过条件引入依赖的方法,那就是使用平台相关的依赖,与基于 feature 的可选依赖不同,它们是基于特定平台的可选依赖。

依赖库自身的 feature

就像我们的项目可以定义 feature 一样,依赖库也可以定义它自己的 feature、也有需要启用的 feature 列表,当引入该依赖库时,我们可以通过以下方式为其启用相关的 features :

[dependencies]
serde = { version = "1.0.118", features = ["derive"] }

以上配置为 serde 依赖开启了 derive feature,还可以通过 default-features = false 来禁用依赖库的 default feature :

[dependencies]
flate2 = { version = "1.0.3", default-features = false, features = ["zlib"] }

这里我们禁用了 flate2 的 default feature,但又手动为它启用了 zlib feature。

注意:这种方式未必能成功禁用  default,原因是可能会有其它依赖也引入了  flate2,并且没有对  default 进行禁用,那此时  default 依然会被启用。

除此之外,还能通过下面的方式来间接开启依赖库的 feature :

[dependencies]
jpeg-decoder = { version = "0.1.20", default-features = false }

[features]
# Enables parallel processing support by enabling the "rayon" feature of jpeg-decoder.
parallel = ["jpeg-decoder/rayon"]

如上所示,我们定义了一个 parallel feature,同时为其启用了 jpeg-decoder 依赖的 rayon feature。

通过命令行参数启用feature

以下的命令行参数可以启用指定的 feature :

  • --features FEATURES: 启用给出的 feature 列表,可以使用逗号或空格进行分隔,若你是在终端中使用,还需要加上双引号,例如 --features "foo bar"。 若在工作空间中构建多个 package,可以使用 package-name/feature-name 为特定的成员启用 features
  • --all-features: 启用命令行上所选择的所有包的所有 features
  • --no-default-features: 对选择的包禁用 default featue

feature同一化

feature 只有在定义的包中才是唯一的,不同包之间的 feature 允许同名。因此,在一个包上启用 feature 不会导致另一个包的同名 feature 被误启用。

当一个依赖被多个包所使用时,这些包对该依赖所设置的 feature 将被进行合并,这样才能确保该依赖只有一个拷贝存在,这个过程就被称之为同一化

这里,我们使用 winapi 为例来说明这个过程。首先,winapi 使用了大量的 features;然后我们有两个包 foo 和 bar 分别使用了它的两个 features,那么在合并后,最终 winapi 将同时启四个 features :

由于这种不可控性,我们需要让 启用feature = 添加特性 这个等式成立,换而言之,启用一个 feature 不应该导致某个功能被禁止。这样才能的让多个包启用同一个依赖的不同features。

例如,如果我们想可选的支持 no_std 环境(不使用标准库),那么有两种做法:

  • 默认代码使用标准库的,当该 no_std feature 启用时,禁用相关的标准库代码
  • 默认代码使用非标准库的,当 std feature 启用时,才使用标准库的代码

前者就是功能削减,与之相对,后者是功能添加,根据之前的内容,我们应该选择后者的做法:

#![no_std]

#[cfg(feature = "std")]
extern crate std;

#[cfg(feature = "std")]
pub fn function_that_requires_std() {
    // ...
}

彼此互斥的feature

某极少数情况下,features 之间可能会互相不兼容。我们应该避免这种设计,因为如果一旦这么设计了,那你可能需要修改依赖图的很多地方才能避免两个不兼容 feature 的同时启用。

如果实在没有办法,可以考虑增加一个编译错误来让报错更清晰:

#[cfg(all(feature = "foo", feature = "bar"))]
compile_error!("feature \"foo\" and feature \"bar\" cannot be enabled at the same time");

当同时启用 foo 和 bar 时,编译器就会爆出一个更清晰的错误:feature foo 和 bar 无法同时启用。

总之,我们还是应该在设计上避免这种情况的发生,例如:

  • 将某个功能分割到多个包中
  • 当冲突时,设置 feature 优先级,cfg-if 包可以帮助我们写出更复杂的 cfg 表达式

检视已解析的features

在复杂的依赖图中,如果想要了解不同的 features 是如何被多个包多启用的,这是相当困难的。好在 cargo tree 命令提供了几个选项可以帮组我们更好的检视哪些 features 被启用了:

cargo tree -e features ,该命令以依赖图的方式来展示已启用的 features,包含了每个依赖包所启用的特性:

$ cargo tree -e features
test_cargo v0.1.0 (/Users/sunfei/development/rust/demos/test_cargo)
└── uuid feature "default"
    ├── uuid v0.8.2
    └── uuid feature "std"
        └── uuid v0.8.2

cargo tree -f "{p} {f}" 命令会提供一个更加紧凑的视图:

% cargo tree -f "{p} {f}"
test_cargo v0.1.0 (/Users/sunfei/development/rust/demos/test_cargo) 
└── uuid v0.8.2 default,std

cargo tree -e features -i foo,该命令会显示 features 会如何"流入"指定的包 foo 中:

cargo tree -e features -i uuid
uuid v0.8.2
├── uuid feature "default"
│   └── test_cargo v0.1.0 (/Users/sunfei/development/rust/demos/test_cargo)
│       └── test_cargo feature "default" (command-line)
└── uuid feature "std"
    └── uuid feature "default" (*)

该命令在依赖图较为复杂时非常有用,使用它可以让你了解某个依赖包上开启了哪些 features 以及其中的原因。

大家可以查看官方的 cargo tree 文档获取更加详细的使用信息。

Feature解析器V2版本

我们还能通过以下配置指定使用 V2 版本的解析器( resolver ):

[package]
name = "my-package"
version = "1.0.0"
resolver = "2"

V2 版本的解析器可以在某些情况下避免 feature 同一化的发生,具体的情况在这里有描述,下面做下简单的总结:

  • 为特定平台开启的 features 且此时并没有被构建,会被忽略
  • Build-dependencies 和 proc-macros 不再跟普通的依赖共享 features
  • Dev-dependencies 的 features 不会被启用,除非正在构建的对象需要它们(例如测试对象、示例对象等)

对于部分场景而言,feature 同一化确实是需要避免的,例如,一个构建依赖开启了 std feature,而同一个依赖又被用于 no_std 环境,很明显,开启 std 将导致错误的发生。

说完优点,我们再来看看 V2 的缺点,其中增加编译构建时间就是其中之一,原因是同一个依赖会被构建多次(每个都拥有不同的 feature 列表)。

由于此部分内容可能只有极少数的用户需要,因此我们并没有对其进行扩展,如果大家希望了解更多关于 V2 的内容,可以查看 官方文档

构建脚本

构建脚本可以通过 CARGO_FEATURE_<name> 环境变量获取启用的 feauture 列表,其中 <name> 是 feature 的名称,该名称被转换成大全写字母,且 - 被转换为 _

required-features

该字段可以用于禁用特定的 Cargo Target:当某个 feature 没有被启用时,查看这里获取更多信息。

SemVer兼容性

启用一个 feautre 不应该引入一个不兼容 SemVer 的改变。例如,启用的 feature 不应该改变现有的 API,因为这会给用户造成不兼容的破坏性变更。 如果大家想知道哪些变化是兼容的,可以参见官方文档。

总之,在新增/移除 feature 或可选依赖时,你需要小心,因此这些可能会造成向后不兼容性。更多信息参见这里,简单总结如下:

  • 在发布 minor 版本时,以下通常是安全的:
  • 新增 feature 或可选依赖
  • 修改某个依赖的 features
  • 在发布 minor 时,以下操作应该避免:
  • 移除 feature 或可选依赖
  • 将现有的公有代码放在某个 feature 之后
  • 从 feature 列表中移除一个 feature

feature文档和发现

将你的项目支持的 feature 信息写入到文档中是非常好的选择:

  • 我们可以通过在 lib.rs 的顶部添加文档注释的方式来实现。例如 regex 就是这么做的。
  • 若项目拥有一个用户手册,那也可以在那里添加说明,例如 serde.rs。
  • 若项目是二进制类型(可运行的应用服务,包含 fn main 入口),可以将说明放在 README 文件或其他文档中,例如 sccache。

特别是对于不稳定的或者不该再被使用的 feature 而言,它们更应该被放在文档中进行清晰的说明。

当构建发布到 docs.rs 上的文档时,会使用 Cargo.toml 中的元数据来控制哪些 features 会被启用。查看 docs.rs 文档获取更多信息。

如何发现features

若依赖库的文档中对其使用的 features 做了详细描述,那你会更容易知道他们使用了哪些 features 以及该如何使用。

当依赖库的文档没有相关信息时,你也可以通过源码仓库的 Cargo.toml 文件来获取,但是有些时候,使用这种方式来跟踪并获取全部相关的信息是相当困难的。

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

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

相关文章

GeometryInstance点击改变颜色

目录 项目地址实现效果核心代码 项目地址 https://github.com/zhengjie9510/webgis-demo 实现效果 核心代码 // Draw different instances each with a unique color const rectangleInstance new Cesium.GeometryInstance({geometry: new Cesium.RectangleGeometry({recta…

EFCore的空迁移(EFCore操作已存在的数据库表,不影响其中的数据)

背景&#xff1a;EFCore默认的会自动创建数据表&#xff0c;但是有时又是DBFirst&#xff0c;数据库写好了要用现成的表。这个时候就需要进行一些特殊的操作了 1、写出跟要对接数据库的实体类 比如我的表是这样创建的 create table mail_test (user_id bigint auto_increment …

【Entity Framework】EF中DbSet类详解

【Entity Framework】EF中DbSet类详解 文章目录 【Entity Framework】EF中DbSet类详解一、概述二、定义DbSet2.1 具有DbSet属性的DbContext2.2 具有 IDbSet 属性的 DbContext 2.3 具有 IDbSet 属性的 DbContext三、DbSet属性四、DbSet方法五、DbContext动态生成DbSet 一、概述 …

医院消防巡检系统革新:凡尔码平台二维码技术引领安全升级

医院消防巡检&#xff0c;传统依赖手工记录&#xff0c;效率和准确性受限。凡尔码平台的二维码消防巡检系统&#xff0c;以创新技术颠覆传统&#xff0c;实现即时、精准的安全管理&#xff0c;确保医院消防安全无虞。 凡尔码平台的消防巡检系统不仅提升了医院安全管理的效率&a…

基于SpringBoot + Vue实现的校园失物招领系统设计与实现+毕业论文

介绍 系统包含用户和管理员两个角色 用户&#xff1a;登录、注册、留言板、公告信息、失物招领、失物认领、寻物启事、个人中心、我发布的失物信息、我的失物认领、我发布的寻物启事、寻物启事留言等功能。 管理员&#xff1a;登录、基础数据管理、系统管理、留言板管理、失物信…

Linux项目自动化构建工具-make/ makefile及其应用:多文件编写第一个linux程序:进度条(懒人学习必备博文!!!)

目录 1.前言--make/makefile的引入 2.快速上手make/makefile---自动化构建 3.关于依赖关系和依赖方法 4.自动化清理 为什么我们执行编译的时候&#xff0c;make一下就好&#xff0c;清理却要使用make clean? 5. make/makefile是如何知道当前目录下可执行文件是否为最新 6.文件…

SQLServer sys.default_constraints介绍

sys.default_constraints 是 SQL Server 的系统视图&#xff0c;它包含了数据库中所有默认约束的信息。默认约束是数据库对象&#xff08;如表中的列&#xff09;的约束&#xff0c;它为列定义了一个默认值&#xff0c;当在插入新行时没有为该列提供值时&#xff0c;将使用这个…

集合嵌套,Collections,斗地主案例,日志框架

文章目录 集合嵌套List嵌套ListList嵌套MapMap嵌套Map Collections类方法排序 sort 乱序 shuffle 斗地主案例需求思路代码 日志框架介绍优势体系结构Logback概述快速入门配置详解 集合嵌套 List嵌套List public static void main(String[] args){//一个年级有许多班级&#xf…

spring boot 整合j2cache 基础操作

spring boot 整合缓存的内容呢 已经学了好久了 那么 今天 我们开始学习 j2cache 这个技术 并不是一个缓存 而是一个框架 我们可以将其他缓存配到这套框架上来 那么 我们就还是弄最熟悉的 ehcache redis进行整合 首先 我们启动 redis 然后 我们打开项目 pom.xml 注入依赖 …

【2024系统架构设计】案例分析- 2 系统开发基础

目录 一 基础知识 二 真题 一 基础知识 1 结构化的需求分析 结构化特点:自顶向下,逐步分解,面向数据。 三大模型:

springboot通过threadLocal+参数解析器实现保存当前用户登录信息

首先先介绍一下threadLocal ThreadLocal 线程局部变量&#xff0c;创建一个线程变量后&#xff0c;针对这个变量可以让每个线程拥有自己的变量副本&#xff0c;每个线程是访问的自己的副本&#xff0c;与其他线程的相互独立。 大致知道threadLocal就可以了&#xff0c;然后我…

web基础07-Vue

目录 一、Vue 1.概述 2.MVC与MVVM 3.快速入门 4.Vue工程的创建 &#xff08;1&#xff09;基于vue-cli &#xff08;2&#xff09;基于Vite&#xff08;推荐&#xff09; 5.Vue3核心语法 6.setup &#xff08;1&#xff09;概述 &#xff08;2&#xff09;返回值方式…

CTF题型 php://filter特殊编码绕过小汇总

CTF题型 php://filter特殊编码绕过小汇总 文章目录 CTF题型 php://filter特殊编码绕过小汇总特殊编码base64编码string过滤器iconv字符集 例题1.[Newstarctf 2023 week2 include]2.[Ctfshow web 117] php://filter 是一个伪协议&#xff0c;它允许你读取经过过滤器处理的数据流…

Java中的Object类解析与应用探究

作为一名对技术充满热情的学习者&#xff0c;我一直以来都深刻地体会到知识的广度和深度。在这个不断演变的数字时代&#xff0c;我远非专家&#xff0c;而是一位不断追求进步的旅行者。通过这篇博客&#xff0c;我想分享我在某个领域的学习经验&#xff0c;与大家共同探讨、共…

基于单片机自行车码表系统设计

**单片机设计介绍&#xff0c;基于单片机自行车码表系统设计 文章目录 一 概要二、功能设计三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机自行车码表系统设计主要涵盖了硬件设计、软件设计以及功能实现等多个方面。以下是对该设计概要的详细描述&#xff1a…

Sql注入靶场环境搭建

文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 例如&#xff1a;随着人工智能的不断发展&#xff0c;机器学习这门技术也越来越重要&#xff0c;很多人都开启了学习机器…

全国青少年软件编程(Python)等级考试三级考试真题2023年12月——持续更新.....

青少年软件编程&#xff08;Python&#xff09;等级考试试卷&#xff08;三级&#xff09; 分数&#xff1a;100 题数&#xff1a;38 一、单选题(共25题&#xff0c;共50分) 1.一个非零的二进制正整数&#xff0c;在其末尾添加两个“0”&#xff0c;则该新数将是原数的&#xf…

PS从入门到精通视频各类教程整理全集,包含素材、作业等(3)

PS从入门到精通视频各类教程整理全集&#xff0c;包含素材、作业等 最新PS以及插件合集&#xff0c;可在我以往文章中找到 由于阿里云盘有分享次受限制和文件大小限制&#xff0c;今天先分享到这里&#xff0c;后续持续更新 中级教程 https://www.alipan.com/s/unii5YxtM8B 提…

【计算机考研】408复习资料汇总+分享

王道对于当作408的参考书还是不错的 所以&#xff0c;矮子里面挑高个&#xff0c;大家将就一下算了&#xff0c;很多人说天勤的讲义也可以&#xff0c;只能说你去用一下试试就知道了&#xff0c;天勤除了数据结构的代码还可以&#xff0c;其他的比不过王道的资料。所以&#x…

视频监控联网平台的评价指标体系

目录 一、视频应用系统评价指标体系的设计思路 &#xff08;一&#xff09;、明确评价目标和原则 &#xff08;二&#xff09;、确定评价指标 &#xff08;三&#xff09;、收集和处理数据 &#xff08;四&#xff09;、建立评价模型 &#xff08;五&#xff09;、进行综…