indicatif
是一个用于在命令行应用程序中向用户显示进度的Rust库,主要是提供进度条和旋转器以及基本的颜色支持
indicatif 架构分析
库的具体结构
项目组织的具体说明
项目的组织结构如下:
.
├── appveyor.yml
├── Cargo.toml # 项目的配置文件
├── examples # 项目的使用demo
│ ├── cargo.rs
│ ├── cargowrap.rs
│ ├── tokio.rs
│ └── yarnish.rs
├── LICENSE
├── README.md
├── src
│ ├── format.rs
│ ├── iter.rs
│ ├── lib.rs # 库文件
│ ├── progress.rs
│ ├── style.rs
│ └── utils.rs
└── tests
└── multi-autodrop.rs # 集成测试文件
库的使用
也可以从 example
中找到使用 indicatif
库的示例,比如 indicatif/single.rs
就展示了库的使用方法。当然,在 src/lib.rs
中有详细的说明
use std::thread;
use std::time::Duration;
use indicatif::ProgressBar;
fn main() {
let pb = ProgressBar::new(1024);
for _ in 0..1024 {
pb.inc(1);
thread::sleep(Duration::from_millis(5));
}
pb.finish_with_message("done");
}
- 通过
use
引入indicatif
库中的ProgressBar
API - 创建一个进度条管理器实例
pb
- 使用管理器操作进度条
pb.inc(1)
、pb.finish_with_message
库的文档
通过 rust doc
可以编译出 indicatif
的库文档,里面详尽的说明了库的使用方法和库所提供的 API
、数据结构和特性trait
indicatif
库最主要的内容如下:
对于 ProgressBar
结构体,它本身的类型是 Struct indicatif::ProgressBar
,它的实现在 src/progress.rs
中定义的
#[derive(Clone)]
pub struct ProgressBar {
state: Arc<RwLock<ProgressState>>,
}
通过 pub struct
的声明,把 ProgressBar
类型作为像外提供的 API
。所以上面是 ProgressBar
结构体的声明。然后给这个结构体装打印的方式
impl fmt::Debug for ProgressBar {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ProgressBar").finish()
}
}
这样 ProgressBar
就有 println!("{}", pb)
的能力了
除了定义 ProgressBar
结构体,确定打印输出的方式以外,还可以给 ProgressBar
类型加上各种 trait 方法集,这就是下面要做的事情
/// Creates a new progress bar with a given length.
///
/// This progress bar by default draws directly to stderr, and refreshes
/// a maximum of 15 times a second. To change the refresh rate set the
/// draw target to one with a different refresh rate.
pub fn new(len: u64) -> ProgressBar {
ProgressBar::with_draw_target(len, ProgressDrawTarget::stderr())
}
/// A convenience builder-like function for a progress bar with a given style.
pub fn with_style(self, style: ProgressStyle) -> ProgressBar {
self.state.write().unwrap().style = style;
self
}
- 首先,给每个方法加上文档注释,以便在执行
cargo doc
之后可以编译出使用文档 - 确定这个类型的构造函数
new
,它是ProgressBar
的关联函数,不是它的方法 - 给
ProgressBar
增加各种方法,如同with_style
一样impl ProgressBar { /// A convenience builder-like function for a progress bar with a given style. pub fn with_style(self, style: ProgressStyle) -> ProgressBar { self.state.write().unwrap().style = style; self } }
- 还可以在注释中加入测试方法
/// Sets the refresh rate of progress bar to `n` updates per seconds. Defaults to 0. /// /// This is similar to `set_draw_delta` but automatically adapts to a constant refresh rate /// regardless of how consistent the progress is. /// /// This parameter takes precedence on `set_draw_delta` if different from 0. /// /// ```rust,no_run /// # use indicatif::ProgressBar; /// let n = 1_000_000; /// let pb = ProgressBar::new(n); /// pb.set_draw_rate(25); // aims at redrawing at most 25 times per seconds. /// ``` /// /// Note that `ProgressDrawTarget` may impose additional buffering of redraws. pub fn set_draw_rate(&self, n: u64) { let mut state = self.state.write().unwrap(); state.draw_rate = n; state.draw_next = state.pos.saturating_add(state.per_sec() / n); }
由此,我们可以看到,要向外提供 ProgressBar
这个 API
,它的结构可以如下:
构建自己的项目
创建 progressbar 项目
我们做一个进度条的项目
cargo new --lib progressbar
得到目录结构如下:
.
├── Cargo.toml
└── src
└── lib.rs
在 lib.rs
中主要写这个库的功能说明。注意用库的说明使用 //!
注释,lib.rs
中主要是导入其他模块提供的 API 资源,并不写具体的实现。
//! 支持markdown语法的文档注释
//! 这个包用于在命令行输出不同风格的进度条
mod progress;
pub use progress::ProgressBar;
在 lib.rs
中创建了 progress
命名空间,并引入了 progress
模块到这个空间中,再把模块中的 ProgressBar
向外公开
增加 progress 模块
在 src/progress.rs
文件中具体实现各种功能。对于具体功能的文档注释,使用 ///
的注释方法。我们在 progress.rs
中实现进度条的功能,它包括进度条的数据和功能
-
数据由结构体定义
struct ProgressBar { state: Arc<RwLock<ProgressState>>, }
-
功能由
impl
特性来定义impl ProgressBar { /// 用给予的长度创建一个新的进度条 /// 这个进度条默认情况下直接绘制到stderr,并且每秒最多刷新15次。 /// 若要更改刷新率,请将绘制目标设置为具有不同刷新率的目标。 pub fn new(len: u64) -> ProgressBar { } /// 将进度条的位置按delta推进 pub fn inc(&self, delta: u64) { } /// 完成进度条并设置消息。 /// ///为了使消息可见,模板中必须有`{msg}`占位符 pub fn finish_with_message(&self, msg: &str) { } }
调用和测试 ProgressBar 包
有了 ProgressBar
包,我们可以用两种简单的方式来调用和测试
- 在
src
中创建可执行程序-
创建
src/bin/epb.rs
,在epb.rs
中创建main()
函数fn main() { let pb = ProgressBar::new(1024); for _ in 0..1024 { pb.inc(1); thread::sleep(Duration::from_millis(5)); } pb.finish_with_message("完成"); }
-
通过
cargo run --bin epb --verbose
就可以构建并运行可执行程序
-
- 在
example
中创建测试代码- 创建
example/epb.rs
,在epb.rs
如第1种情况一样创建main()
函数 - 通过
cargo run --example epb --verbose
构建并运行可执行程序
- 创建
epb 模块的分析
src/bin/epb.rs
的内容如下:
fn main() {
let pb = ProgressBar::new(1024);
for _ in 0..1024 {
pb.inc(1);
thread::sleep(Duration::from_millis(5));
}
pb.finish_with_message("完成");
}
它的框架如下:
后面我们将逐步完善这个 progressbar
模块
finebars 主函数分析
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
let m = MultiProgress::new();
for s in styles.iter() {
let pb = m.add(ProgressBar::new(512));
pb.set_style(ProgressStyle::default_bar()
.template(&format!("prefix:.bold▕bar:▏msg", s.2))
.progress_chars(s.1),
);
pb.set_prefix(s.0);
let wait = Duration::from_millis(thread_rng().gen_range(10, 30));
thread::spawn(move || {
for i in 0..512 {
pb.inc(1);
pb.set_message(&format!(":3%", 100 * i / 512));
thread::sleep(wait);
}
pb.finish_with_message("100%");
});
}
m.join().unwrap();
MultiProgress::new()
indicatif::MultiProgress
使用到了 indicatif::MultiProgress
,其中indicatif
库名,其中的MultiProgress
是类型名或特性名
///管理不同线程的多个进度条。
pub struct MultiProgress {
// 读写锁
state: RwLock<MultiProgressState>,
joining: AtomicBool,
tx: Sender<(usize, ProgressDrawState)>,
rx: Receiver<(usize, ProgressDrawState)>,
}
struct MultiProgress
下面是有关 MultiProgress
结构体的源码
let m = MultiProgress::new();
impl MultiProgress {
// 默认情况下,添加到该对象的进度条会直接绘制到stderr,
// 并且每秒最多刷新15次。要改变刷新率,可以将绘制目标设置为不同刷新率的目标。
pub fn new() -> MultiProgress {
MultiProgress::default()
}
impl Default for MultiProgress {
fn default() -> MultiProgress {
MultiProgress::with_draw_target(ProgressDrawTarget::stderr())
}
}
又涉及到 with_draw_target()
函数
with_draw_target
pub fn with_draw_target(draw_target: ProgressDrawTarget) -> MultiProgress {
//用给定的绘制目标创建一个新的多进度对象。
let (tx, rx) = channel();
MultiProgress {
state: RwLock::new(MultiProgressState {
objects: vec![],
ordering: vec![],
draw_target,
move_cursor: false,
}),
joining: AtomicBool::new(false),
tx,
rx,
}
}
通过 RwLock::new()
创建一个安全的 MultiProgressState
资源的读写锁,并初始化了这个资源
ProgressDrawTarget
绘图操作的目标
告诉进度条或多进度对象要画到哪里。绘制目标是对绘制目标的状态包装器,并在内部优化了向输出设备绘制状态的频率。
impl ProgressDrawTarget {
/// 以每秒最多15次的速度画到缓冲的stderr终端。
/// 这是进度条的默认绘制目标。 更多信息请参见`ProgressDrawTarget::to_term`。
pub fn stderr() -> ProgressDrawTarget {
ProgressDrawTarget::to_term(Term::buffered_stderr(), 15)
}
// 绘制到终端,可以选择特定的刷新率。
// 进度条默认被绘制到终端上,但是如果终端没有用户参与,整个进度条将被隐藏。
// 这样做的目的是为了使管道传输到文件不会在该文件中产生无用的转义码。
pub fn to_term(term: Term, refresh_rate: impl Into<Option<u64>>) -> ProgressDrawTarget {
let rate = refresh_rate.into().map(|x| Duration::from_millis(1000 / x));
ProgressDrawTarget {
kind: ProgressDrawTargetKind::Term(term, None, rate),
}
}
}
这里 to_term()
函数先把refresh_rate
转换成Duration
类型的刷新率,然后再把终端对象term
一并包装在ProgressDrawTarget
结构体中
- 这里
impl Into<Option<u64>>
要求refresh_rate
必须实现Into
特性 refresh_rate.into()
将refresh_rate
转换成Option<u64>
类型Some(v).map(f) -> Some(f(v))
-
Duration
是标准库中的#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct Duration { secs: u64, nanos: u32, // Always 0 <= nanos < NANOS_PER_SEC } #[stable(feature = "duration", since = "1.3.0")] #[inline] #[rustc_const_stable(feature = "duration_consts", since = "1.32.0")] pub const fn from_millis(millis: u64) -> Duration { Duration { secs: millis / MILLIS_PER_SEC, nanos: ((millis % MILLIS_PER_SEC) as u32) * NANOS_PER_MILLI, } }
from_millis
返回Duration
类型的数据,成员是与millis
对应的秒和纳秒-
ProgressDrawTargetKind
enum ProgressDrawTargetKind { Term(Term, Option<ProgressDrawState>, Option<Duration>), Remote(usize, Mutex<Sender<(usize, ProgressDrawState)>>), Hidden, }
for 第一阶段
let styles = [
("Rough bar:", "█ ", "red"),
("Fine bar: ", "█▉▊▋▌▍▎▏ ", "yellow"),
......
("Blocky: ", "█▛▌▖ ", "magenta"),
];
let m = MultiProgress::new();
for s in styles.iter() {
let pb = m.add(ProgressBar::new(512));
}
向 m
中加入新的进程,其中 m.add()
实现如下:
// 添加一个进度条。
// 添加的进度条会将绘制目标更改为远程绘制目标,由多进度对象覆盖自定义`ProgressDrawTarget`设置拦截。
pub fn add(&self, pb: ProgressBar) -> ProgressBar {
let mut state = self.state.write().unwrap();
let object_idx = state.objects.len();
state.objects.push(MultiObject {
done: false,
draw_state: None,
});
state.ordering.push(object_idx);
pb.set_draw_target(ProgressDrawTarget {
kind: ProgressDrawTargetKind::Remote(object_idx, Mutex::new(self.tx.clone())),
});
pb
}
这里 self
是 MultiProgress
,它是一个多线程进度条管理器,包含的内容如下:
///管理不同线程的多个进度条。
pub struct MultiProgress {
// 读写锁
state: RwLock<MultiProgressState>,
joining: AtomicBool,
tx: Sender<(usize, ProgressDrawState)>,
rx: Receiver<(usize, ProgressDrawState)>,
}
add(self, pd)
是将一个进度条实现放到多进度条管理器中
let mut state = self.state.write().unwrap();
这里 state
是 &MultiProgressState
那么分析下一句
let object_idx = state.objects.len();
其中 objects: Vec<MultiObject>
,则 object_idx
就是目前多进度条管理器中放的进度条数目,也是 vec[n]
的索引 n
。再看下一句:
state.objects.push(MultiObject {
done: false,
draw_state: None,
});
相当于 vec[n] = MultiObject{..}
再之后 state.ordering.push(object_idx);
保存当前索引值。然后:
pb.set_draw_target(ProgressDrawTarget {
kind: ProgressDrawTargetKind::Remote(object_idx, Mutex::new(self.tx.clone())),
});
其中 set_draw_target
定义如下:
pub fn set_draw_target(&self, target: ProgressDrawTarget) {
let mut state = self.state.write().unwrap();
state.draw_target.disconnect();
state.draw_target = target;
}
这里 draw_target: ProgressDrawTarget
pub struct ProgressDrawTarget {
kind: ProgressDrawTargetKind,
}
enum ProgressDrawTargetKind {
Term(Term, Option<ProgressDrawState>, Option<Duration>),
Remote(usize, Mutex<Sender<(usize, ProgressDrawState)>>),
Hidden,
}
impl ProgressDrawTarget {
// 适当地断开与绘制目标的连接
fn disconnect(&self) {
match self.kind {
ProgressDrawTargetKind::Term(_, _, _) => {}
ProgressDrawTargetKind::Remote(idx, ref chan) => {
chan.lock()
.unwrap()
.send((
idx,
ProgressDrawState {
lines: vec![],
orphan_lines: 0,
finished: true,
force_draw: false,
move_cursor: false,
ts: Instant::now(),
},
))
.ok();
}
ProgressDrawTargetKind::Hidden => {}
};
}
}
向远程实例发送 ProgressDrawState::finished: true
,然后再state.draw_target = target;`
for 第二阶段
pb.set_style(
ProgressStyle::default_bar()
.template(&format!("prefix:.bold▕bar:.▏msg", s.2))
.progress_chars(s.1),
);
pd.set_style()
首先,set_style
的功能是 self.state.style = style
,其实现如下:
pub fn set_style(&self, style: ProgressStyle) {
self.state.write().unwrap().style = style;
}
这里的 self: ProgressBar
的 state
如下:
pub struct ProgressBar {
state: Arc<RwLock<ProgressState>>,
}
self.state: Arc<...>
-> .write
-> Arc::Deref
-> RwLock<>
-> .write
-> RwLock::wirte
-> LockResult<RwLockWriteGuard<'_, T>>
-> unwrap()
-> RwLockWriteGuard<'_, T>
-> .style
-> RwLockWriteGuard<'_, T>::Deref
-> T: ProgressState
-> .style
-> ProgressState::style: ProgressStyle
-> .style = style
所以 self.state
是 Arc<...>
类型的,但是 Arc
类型没有 write
方法,不过它实现了 Deref
,所以 rust
会到 RwLock
寻找 write
方法
ProgressStyle::default_bar()
ProgressStyle::default_bar()
是它接口中的声明,定义如下
impl ProgressStyle {
// 为bars返回默认的进度条风格
pub fn default_bar() -> ProgressStyle {
let progress_chars = segment("█░");
let char_width = width(&progress_chars);
ProgressStyle {
tick_strings: "⠁⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈ "
.chars()
.map(|c| c.to_string().into())
.collect(),
progress_chars,
char_width,
template: Cow::Borrowed("{wide_bar} {pos}/{len}"),
}
}
}
这里 segment()
函数定义如下:
#[cfg(not(feature = "improved_unicode"))]
fn segment(s: &str) -> Vec<Box<str>> {
s.chars().map(|x| x.to_string().into()).collect()
}
其中 to_string()
的实现如下:
default fn to_string(&self) -> String {
let mut buf = String::new();
buf.write_fmt(format_args!("{}", self));
}
对于 x.to_string().into()
因为函数 segment
的返回类型为 Vec<Box<str>>
所以 into()
会将 Self
转化为 Box<_>
类型
同时,也因为返回类型,所以 collect
也会确定为 Vec<_>
类型
附录
RwLock 读写锁
这种类型的锁允许在任何时候有若干个读者或最多一个写入器。这种锁的写部分通常允许修改底层数据(独占访问),读部分通常允许只读访问(共享访问)。
相比之下,[Mutex
]不区分获得锁的读者或写者,因此会阻止任何等待锁可用的线程。一个RwLock
将允许任何数量的读者获得锁,只要一个写入者不持有该锁。锁的优先级策略取决于底层操作系统的实现,这种类型不保证会使用任何特定的策略。
类型参数T
代表这个锁所保护的数据。要求T
满足[Send
]才能跨线程共享,[Sync
]才能允许通过读者并发访问。从锁定方法返回的RAII守卫实现[Deref
](和[DerefMut
]的write
方法),以允许访问锁的内容。
Poison
RwLock
,像[Mutex
]一样,会在 panic
时被破坏。但是,只有在独占锁定中(写模式)发生 panic
时,RwLock
才会被破坏。如果在任何读取器中发生 panic
,那么锁就不会被破坏。
new
pub fn new(t: T) -> RwLock<T>
创建一个 RwLock<T>
的新实例,它是未锁定的。比如:let clock = RwLock::new(5);
write
pub fn write(&self) -> LockResult<RwLockWriteGuard<'_, T>>
-
用独占的写权限锁定这个rwlock,阻塞当前线程,直到它可以被获取。
-
当其他写入者或其他读取者对该锁有访问权时,该函数不会返回。
-
返回一个RAII守卫,该守卫将在该rwlock的写权限被放弃时放弃该rwlock的写权限。
-
如果RwLock被破坏(poison),这个函数将返回一个错误(当持有独占锁写入者panic时,称RwLock被破坏)。当锁被获取时,将返回一个错误。
-
如果锁已经被当前线程持有,这个函数在调用时可能会
panic
其中:
type LockResult<Guard> = Result<Guard, PoisonError<Guard>>;
RwLockWriteGuard
Struct std::sync::RwLockWriteGuard
RwLockWriteGuard
RAII结构体,用于 drop
时释放锁的独占写权限。
注意,这里 RwLockWriteGuard
实现了 Deref
特性
impl<T: ?Sized> Deref for RwLockReadGuard<'_, T> {
type Target = T;
fn deref(&self) -> &T {
unsafe { &*self.lock.data.get() }
}
}
所以对 RwLockWriteGuard
的解引用可以得到其封装内容的引用
Struct std::sync::Mutex
一个用于保护共享数据的互斥基元。
这个 mutex
将阻止等待可用锁的线程。该 mutex
也可以通过静态初始化或通过新的构造函数来创建。
每个 mutex
都有一个类型参数,它代表了它所保护的数据。数据只能通过 lock
和 try_lock
返回的 RAII
守卫来访问,这就保证了数据只有在 mutex
被锁定时才能被访问。
new
impl Mutex {
pub fn new(t: T) -> Mutex<T>;
}
创建一个未锁定的资源互斥锁,以备使用:
let mutex = Mutex::new(5);
lock
impl Mutex {
pub fn lock(&self) -> LockResult<MutexGuard<'_, T>>;
}
该函数将阻塞本地线程,直到它可以获取 mutex
。返回后,该线程是唯一一个拥有锁的线程。返回一个 RAII
守卫,允许在作用域范围内解锁。当守护超出范围时,mutex
将被解锁。
在已经持有锁的线程中再锁定 mutex
的确切行为没有被指定。但是,这个函数不会在第二次调用时返回(它可能会 panic
或 deadlock
死锁)
如果这个 mutex
的另一个用户在持有 mutex
时 panic
,那么这个调用将在获取 mutex
后返回一个错误。
如果锁已经被当前线程持有,这个函数在调用时可能会 panic
let mutex = Arc::new(Mutex::new(10));
let mutex_clone = Arc::clone(&mutex);
thread::spawn(move || {
*mutex_clone.lock().unwrap() = 100;
}).join().expect("thread::spawn failed");
assert_eq!(*mutex.lock().unwrap(), 100);
Struct std::sync::Arc
一个线程安全的引用计数指针。 Arc
代表原子引用计数。
Arc<T>
类型提供了在堆中分配的 T
类型值的共享所有权。在 Arc
上调用 clone
会产生一个新的 Arc
实例,它指向与源 Arc
相同的堆上分配,同时增加了一个引用计数。当给定分配的最后一个 Arc
指针被销毁时,存储在该分配中的值(通常被称为 “内值”)也会被丢弃。
Rust
中的共享引用默认是不允许改变的,Arc
也不例外:一般来说,你不能在Arc
内部获得一个可突变的引用。如果你需要通过 Arc
进行突变,请使用Mutex
、RwLock
或Atomic
类型之一。
与 Rc<T>
不同,Arc<T>
使用原子操作进行引用计数。这意味着它是线程安全的。缺点是原子操作比普通的内存访问更昂贵。如果你不在线程之间共享引用计数的分配,可以考虑使用 Rc<T>
来降低开销。Rc<T>
是一个安全的默认类型,因为编译器会捕获任何在线程之间发送 Rc<T>
的尝试。然而,一个库可能会选择Arc<T>
,以便给库消费者更多的灵活性。
只要 T
实现了 Send
发送和 Sync
同步,Arc<T>
就会实现发送和同步。为什么不能把一个非线程安全的 T
类型放在Arc<T>
中使其线程安全呢?这在一开始可能有点违背直觉:毕竟Arc<T>
的重点不就是线程安全吗?关键是这样:对同一数据拥有多个所有权的 Arc<T>
类型是线程安全的,但它并没有为其内部数据 T
添加线程安全。考虑 Arc<RefCell<T>>
。RefCell<T>
并没有实现 Sync
,如果Arc<T>
实现了 Send
,Arc<RefCell<T>
也应该是。但这样我们就有问题了。RefCell<T>
不是线程安全的;它使用非原子操作来跟踪借入计数。
最后,这意味着你可能需要将 Arc<T>
与某种 std::sync
类型配对,通常是 Mutex<T>
。
Arc<T> 的作用
我们希望 Arc<T>
在多线程中共享数据 T
,因为 Arc<T>
是多线程安全的(它实现了Sync + Send
)保证多线程读取到的 T
是相同的。但是 Arc<T>
是安全的,但它不能让内部的 T
也是多线程同步的。为了让 T
在多线程间同步,所以给 T
包装上 Mutex<T>
互斥锁
Rc<T>
主要用于:
- 希望共享堆上分享的资源可以供程序的多个部分读取
- 并且确保共享的资源的析构函数一定能被调用
Trait std::marker::Sync
:可以安全地在线程之间共享引用的类型
Trait std::marker::Send
:可以跨线程边界传输的类型
cfg 条件编译
条件编译的开关在 Cargo.toml
中设定
[features]
default = []
improved_unicode = ["unicode-segmentation", "unicode-width", "console/unicode-width"]
#[cfg]
是条件编译语法,使用属性 cfg
和 cfg_attr
以及内置的 cfg
宏对源代码进行有条件的编译
每种形式的条件编译都需要一个配置谓词,它的值是真或假。谓词是下列之一:
- 一个配置选项。如果该选项被设置,则为true,如果未设置,则为false。
all()
,包含一个逗号分隔的配置谓词列表。如果至少有一个谓词为false,则为false。如果没有谓词,则为真。any()
,包含一个用逗号分隔的配置谓词列表。如果至少有一个谓词为真,则为真。如果没有谓词,则为false。not()
,包含一个配置谓词。如果它的谓词为false,则为真,如果它的谓词为真,则为假。
配置选项是已设置或未设置的名称和键值对。名称被写成一个单一的标识符,例如,unix。键值对被写成一个标识符、=
,然后是一个字符串。例如,target_arch = "x86_64"
是一个配置选项。
在键值配置选项集中,键不是唯一的。例如,feature="std"
和 feature="serde"
可以同时设置
Rust 支持条件编译,可通过两种不同的操作实现:
cfg
属性:在属性位置中使用#[cfg(...)]
cfg!
宏:在布尔表达式中使用cfg!(...)
// 根据操作系统引用不同文件的相同模块
#[cfg_attr(target_os = "linux", path = "linux.rs")]
#[cfg_attr(windows, path = "windows.rs")]
mod os;
// 函数仅当操作系统是 Linux 的时候才会编译
#[cfg(target_os = "linux")]
fn do_on_linux() {
println!("You are running linux!")
}
// 函数仅当操作系统不是 Linux 的时候才会编译
#[cfg(not(target_os = "linux"))]
fn do_on_notlinux() {
println!("You are *not* running linux!")
}
条件编译的开关,在 Cargo.toml
文件中的 [features]
节进行设置,比如:
[features]
default = []
secure-password = ["bcrypt"]
Cargo
会给 rustc
传递一个参数:--cfg feature="${feature_name}"
,这些cfg
标志将会决定哪个一个被激活,哪个代码被编译:
#[cfg(feature = "foo")]
mod foo {
}
如果我们使用 cargo build --features "foo"
编译,它将会把--cfg feature="foo"
标志传递给rustc
,并且输出将会有一个foo
模块。如果我们使用常规的cargo build
,没有额外的标志被传递,将不会有foo模块存在。
你可以使用cfg_attr
给基于cfg
的变量设置另外一个属性:#[cfg_attr(a, b)]
,如果a被使用cfg设置过属性,则跟 #[b]
一样。
cfg!
语法扩展允许你在代码中使用这些种类的标志,在编译阶段依据配置设定,这些将会被替换成true或false。
if cfg!(target_os = "macos") || cfg!(target_os = "ios") {
println!("Think Different!");
}
Trait std::iter::Iterator::collect
collect
的实现如下:
pub fn collect<B>(self) -> B
where
B: FromIterator<Self::Item>,
将一个迭代器转化为一个集合
collect()
可以接受任何可迭代的东西,并把它变成一个相关的集合。这是标准库中比较强大的方法之一,在各种情况下使用。
collect()
最基本的使用模式是将一个集合变成另一个集合。你拿一个集合,在上面调用 iter
,做一堆转换,然后在最后用collect()
collect()
也可以创建非典型集合类型的实例。例如,一个 String
可以从 chars
建立,一个 Result<T, E>
项的迭代器可以被收集到 Result<Collection<T>, E>
由于 collect()
是如此的通用,它可能会引起类型推理的问题。因此,collect()
是少数几次你会看到被亲切地称为 “turbofish::<>
” 的语法之一。这有助于推理算法具体了解你要收集到哪个集合。
let chars = ['g', 'd', 'k', 'k', 'n'];
let s: String = chars.iter().map(|&x| x as u8)
.map(|x| (x+1) as char)
.collect();
assert_eq!(s, "hello");
注意,collect()
是要指定收集到集合的类型的,比如:
let doubled = a.iter().map(|x| x*2).collect::<Vec<_>>();
或者
let doubled: Vec<_> = a.iter().map(|x| 2*x).collect();
Trait std::convert::Into
消耗输入值的值到值的转换。与From相反。
我们应该避免实现 Into
,而应该实现 From
。由于标准库中的空白实现,实现 From
会自动提供一个 Into
的实现。
当一个特定的 trait
绑定到泛型函数时,最好使用Into而不是From,以确保只实现Into的类型也能被使用。
注意:这个特质不能失败。如果转换可能失败,请使用 TryInto
。
use、mod、extern crate
mod
类似#include
;use
类似let
use
和let
一样是绑定关键字。use std::fmt as fmt
将std::fmt
绑定为 当前作用域的fmt
mod my
是声明,rust
将查找my.rs
或my/mod.rs
文件,并将文件内容插入当前文件作用域的my
模块(命名空间)extern crate
即是声明也是导入 它用于库的查找与导入,rust
将查找到的库的内容导入当前文件的同名空间中