Rust学习之Features

Rust学习之Features

    • [一 什么是 Features](#一 什么是 Features)
    • [二 默认 feature](#二 默认 feature)
    • [三 简单的features应用示例](#三 简单的features应用示例)
    • [四 可选(optional)的依赖](#四 可选(optional)的依赖)
    • [五 依赖的特性](#五 依赖的特性)
      • [5.1 在依赖表中指定](#5.1 在依赖表中指定)
      • [5.2 在features表中指定](#5.2 在features表中指定)
    • [六 命令行中特性控制](#六 命令行中特性控制)
    • [七 特性统一路径](#七 特性统一路径)
    • [八 其它](#八 其它)
      • [8.1 相互排斥特性](#8.1 相互排斥特性)
      • [8.2 观察启用特性](#8.2 观察启用特性)
      • [8.3 Feature resolver version 2](#8.3 Feature resolver version 2)

本文是学习Solana 程序库合约(SPL)的Rust 预先知识部分,需要有Rust基础

本文学习课程为https://doc.rust-lang.org/cargo/reference/features.html 。下面的内容为一些简单记录。

下面的内容中,feature和特性会交叉出现,但是均指同一概念。

一 什么是 Features

Features 是用来表达条件编译或者条件依赖的机制。

定义在Cargo.toml中的[features]表中的features 可以启用或者不启用。在构建时通过命令行参数--features来启用需要的特性,作为依赖启用特性时,直接在Cargo.toml中定义。

基本的features块定义为:

toml 复制代码
[features]
# Defines a feature named `webp` that does not enable any other features.
webp = []

在Rust中使用webp特性的代码示例:

rust 复制代码
// This conditionally includes a module which implements WEBP support.
#[cfg(feature = "webp")]
pub mod webp;

// 下面是不启用"no-entrypoint"才包含entrypoint模块的定义
#[cfg(not(feature = "no-entrypoint"))]
mod entrypoint;

特性可以包含其它特性

toml 复制代码
[features]
bmp = []
png = []
ico = ["bmp", "png"]
webp = []

// 注意,下面bmp和png的定义顺序任意,可以放在ico的下面

二 默认 feature

默认程序时不启用任何特性的,但是我们可以定义程序默认启用的features

toml 复制代码
[features]
default = ["ico", "webp"]
bmp = []
png = []
ico = ["bmp", "png"]
webp = []

三 简单的features应用示例

接着上面的Cargo.toml定义,一个简单的应用示例:

rust 复制代码
fn main() {
    #[cfg(feature = "webp")]
    println!("Hello webp!");

    #[cfg(not(feature = "webp"))]
    println!("Hello not webp!");

    #[cfg(feature = "ico")]
    println!("Hello ico!");

    #[cfg(feature = "gif")]
    println!("Hello gif!");
}

运行结果如下:

bash 复制代码
$ cargo run
Hello not webp!
Hello ico!

$ cargo run --features webp
Hello webp!
Hello ico!

默认时仅启用了default未启用,所以上面cargo run输出了ico

注意:就算指定了--features参数,默认特性还是会启用。关闭它有两种方法:

  • 命令行参数使用--no-default-features
  • 作为依赖库,在定义时,设定default-features = false选项。

因此,我们如果在cargo run时不想启用默认特性,运行如下命令:

bash 复制代码
$ cargo run --features webp --no-default-features
Hello webp!

注意:原文提到了要小心默认特性设置,它通常启用了一些方便用户使用常用功能。但万一用户不想启用这些功能时,需要在所有依赖定义中限定default-features = false.特别当一个包被多处依赖时,每处定义都要指定default-features = false.

四 可选(optional)的依赖

依赖库也可以标记为optional,它意味着默认情况下不会被编译。例如如下定义:

toml 复制代码
[dependencies]
gif = { version = "0.11.1", optional = true }

默认时gif依赖是未启用的,那怎么启用它呢?其实上面的定义同时隐式定义了一个gif特性,类似如下定义:

toml 复制代码
[features]
gif = ["dep:gif"]

当然,我们无需手动写出上面的定义,但是如果你不想使用默认名称(和库名相同),就得手写一个了,如果你手写了,那么隐式特性就不存在了。例如如下示例:

toml 复制代码
[dependencies]
ravif = { version = "0.6.3", optional = true }
rgb = { version = "0.8.25", optional = true }

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

上面的定义中,用户只能选择avif特性,阻止用户单独选择ravif或者rgb,因为也许这两者是必须同时启用的。

切记,采用上面的方式时,依赖必须是optional

五 依赖的特性

5.1 在依赖表中指定

在定义外部依赖的同时还可以同时指定启用的特性,例如:

toml 复制代码
[dependencies]
# Enables the `derive` feature of serde.
serde = { version = "1.0.118", features = ["derive"] }
flate2 = { version = "1.0.3", default-features = false, features = ["zlib"] }

注意:上面提到过,就算指定了依赖的features(不是feature,因为可以指定启用多个特性,所以为一数组,是带复数的s),其默认特性依然是开启的,因此我们必须手动关掉它,正如flate2定义。当然,如果别的地方同时也用到了flate2,那么无法保证其默认特性是关闭的,前面提到过原因。

5.2 在features表中指定

依赖的特性也可以在features表中定义(上面的是在dependencies表中定义),语法为package-name/feature-name,示例如下:

toml 复制代码
[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"]

可以看到,这种方式定义时,关闭默认特性还是在dependencies表中进行。

注意当依赖为可选依赖时,package-name/feature-name语法还会同时启用该依赖,然而有时你却不想这么做,那么可以使用如下语法

package-name?/feature-name 。这样只有在其它别处启用该依赖后定义的特性才会被启用。示例如下:

toml 复制代码
[dependencies]
serde = { version = "1.0.133", optional = true }
rgb = { version = "0.8.25", optional = true }

[features]
serde = ["dep:serde", "rgb?/serde"]

上面的定义中,启用serde特性会启用 serde 依赖库,但是只有在其它地方启用rgb依赖库了它才会启用rgbserde特性,例如我们定义了一个新的特性,其它即启用了rgb又包含了上面定义的serde特性。

(下面代码未验证,仅为个人猜想)

toml 复制代码
[features]
serde = ["dep:serde", "rgb?/serde"]
all= ["dep:rgb","serde"]

六 命令行中特性控制

  • --features FEATURES : 指定启用的特性,注意可以指定多个特性,它是一个列表,使用逗号或者空格分隔。如果使用空格分隔,注意在所有特性整体名称上加上双引号。例如我们接最初的toml及程序定义,运行示例:

    bash 复制代码
    $ cargo run --features webp gif
    Hello webp!
    Hello ico!
    
    $ cargo run --features "webp gif"
    Hello webp!
    Hello ico!
    Hello gif!
    
    // 下面webp,gif中逗号前后可以有空格
    $ cargo run --features webp,gif --no-default-features 
    Hello webp!
    Hello gif!
  • --all-features: 启用所有特性

  • --no-default-features: 不启用默认特性

七 特性统一路径

当一个依赖在多个包中使用时,Cargo会使用所有启用特性的统一路径来标记它,从而确保只有一份单一的代码被使用(无重复代码)。

如下图所示:

此时,构建my-package时会启用winapi的四个特性。

特性中有一个共识是增加性,也就是启用一个特性不会关闭已有的功能,并且任意特性之间都可以联合使用。

例如 ,当你想支持no_std环境时,不要使用no_std特性(不要做减法),而是使用一个std特性来启用std库。示例代码如下:

rust 复制代码
#![no_std]

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

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

上面的代码中,如果启用std特性,就使用std库,同时启用相应的函数。在revm (Rust EVM实现)中能见到大量这种用法,因为区块链运行环境并不一定支持std,所以统一使用#![no_std].

八 其它

8.1 相互排斥特性

通常不要这么设计(因为在确保任意特性联合都可以安全使用),但万一存在两个排斥特性时,需要进行检测并给出编译错误,例如

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

三种替代方案为:

  • 分离不同功能到不同的包
  • 使用其中一个包替换另一个
  • 重构代码消除排斥性特性。

8.2 观察启用特性

cargo tree命令可以观察哪些特性被启用了,主要有这么几种用法

  • cargo tree -e features: 待研究
  • cargo tree -f "{p} {f}": 待研究
  • cargo tree -e features -i foo: 待研究

8.3 Feature resolver version 2

Feature resolver version 2

特研究

相关推荐
湫ccc38 分钟前
《Python基础》之字符串格式化输出
开发语言·python
Red Red41 分钟前
网安基础知识|IDS入侵检测系统|IPS入侵防御系统|堡垒机|VPN|EDR|CC防御|云安全-VDC/VPC|安全服务
网络·笔记·学习·安全·web安全
mqiqe1 小时前
Python MySQL通过Binlog 获取变更记录 恢复数据
开发语言·python·mysql
AttackingLin1 小时前
2024强网杯--babyheap house of apple2解法
linux·开发语言·python
Ysjt | 深2 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++
ephemerals__2 小时前
【c++丨STL】list模拟实现(附源码)
开发语言·c++·list
码农飞飞2 小时前
深入理解Rust的模式匹配
开发语言·后端·rust·模式匹配·解构·结构体和枚举
一个小坑货2 小时前
Rust 的简介
开发语言·后端·rust