rust 进度条应用源码解析

 

indicatif 是一个用于在命令行应用程序中向用户显示进度的Rust库,主要是提供进度条和旋转器以及基本的颜色支持

indicatif 是一个用于在命令行应用程序中向用户显示进度的Rust库,主要是提供进度条和旋转器以及基本的颜色支持

indicatif 架构分析

库的具体结构

crate

项目组织的具体说明

项目的组织结构如下:

.
├── 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
}
  1. 首先,给每个方法加上文档注释,以便在执行 cargo doc 之后可以编译出使用文档
  2. 确定这个类型的构造函数 new,它是 ProgressBar 的关联函数,不是它的方法
  3. 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
         }
     }
    
  4. 还可以在注释中加入测试方法
     /// 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 包,我们可以用两种简单的方式来调用和测试

  1. 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 就可以构建并运行可执行程序

  2. 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
}

这里 selfMultiProgress,它是一个多线程进度条管理器,包含的内容如下:

///管理不同线程的多个进度条。
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: ProgressBarstate 如下:

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.stateArc<...> 类型的,但是 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 都有一个类型参数,它代表了它所保护的数据。数据只能通过 locktry_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 的确切行为没有被指定。但是,这个函数不会在第二次调用时返回(它可能会 panicdeadlock 死锁)

如果这个 mutex 的另一个用户在持有 mutexpanic,那么这个调用将在获取 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 进行突变,请使用MutexRwLockAtomic类型之一。

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> 实现了 SendArc<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> 主要用于:

  1. 希望共享堆上分享的资源可以供程序的多个部分读取
  2. 并且确保共享的资源的析构函数一定能被调用

Trait std::marker::Sync:可以安全地在线程之间共享引用的类型
Trait std::marker::Send:可以跨线程边界传输的类型

cfg 条件编译

条件编译的开关在 Cargo.toml 中设定

[features]
default = []
improved_unicode = ["unicode-segmentation", "unicode-width", "console/unicode-width"]

#[cfg]是条件编译语法,使用属性 cfgcfg_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

  1. mod 类似 #includeuse 类似 let
  2. uselet 一样是绑定关键字。use std::fmt as fmtstd::fmt 绑定为 当前作用域的 fmt
  3. mod my 是声明,rust 将查找 my.rsmy/mod.rs 文件,并将文件内容插入当前文件作用域的 my 模块(命名空间)
  4. extern crate 即是声明也是导入 它用于库的查找与导入,rust将查找到的库的内容导入当前文件的同名空间中