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

特研究

相关推荐
kiiila1 分钟前
【Qt】编辑框/按钮控件---实现HelloWorld
开发语言·qt
Dcy_ASK4 分钟前
认识Python语言
开发语言·python
cwtlw1 小时前
CSS学习记录20
前端·css·笔记·学习
数据小小爬虫1 小时前
Python爬虫获取AliExpress商品详情
开发语言·爬虫·python
小爬虫程序猿1 小时前
利用Python爬虫速卖通按关键字搜索AliExpress商品
开发语言·爬虫·python
一朵好运莲1 小时前
React引入Echart水球图
开发语言·javascript·ecmascript
紫罗兰盛开1 小时前
分布式调度框架学习笔记
笔记·学习
Eiceblue1 小时前
使用Python获取PDF文本和图片的精确位置
开发语言·python·pdf
xianwu5431 小时前
反向代理模块。开发
linux·开发语言·网络·c++·git
Kobebryant-Manba1 小时前
kafka基本概念
分布式·学习·kafka