quant1x/mpb 把 Rust 进度条库 indicatif 作为唯一规范,
用五套语言实现同一套进度条语义。
| 角色 | 内容 |
|---|---|
| 规范 | Rust indicatif 0.18.6(commit 3adb05c),任何行为以它为准 |
| 移植 | Go / Python / C++ / Zig 四个独立实现,逐模块对照上游 |
| 封装 | Rust 侧同名 mpb crate,只有 pub use indicatif::*;,不做二次实现 |
五个入口共享同一套能力:模板与 ANSI 样式、宽度与换行度量、20Hz 节流与令牌桶、15 秒双指数
平滑估算器、单/多进度条、树形嵌套、println / clear / suspend、u64 边界、
Drop/僵尸行(zombie)算法、内存终端。
| 项 | 值 |
|---|---|
| 上游仓库 | https://github.com/console-rs/indicatif |
| 上游版本 | 0.18.6 |
| 上游 commit | 3adb05c80429415743d8142503c24fa8ee639453(短号 3adb05c,2026-10-06) |
| 上游本地副本 | <UPSTREAM_DIR>,例如 d:/projects/rust/indicatif |
仓库不随附上游的 .rs / examples/ / tests/ / Cargo.toml,移植时直接读 <UPSTREAM_DIR>。
对比两个上游版本:
git -C <UPSTREAM_DIR> --no-pager diff <OLD_COMMIT> HEAD -- src/<OLD_COMMIT> 取 3adb05c 的前一次提交,即可看到 0.18.6 这一批改动。
| 语言 | 入口 | 构建 | 测试 | 示例 | 版本来源 |
|---|---|---|---|---|---|
| Go | doc.go |
go build ./... |
go test ./... |
go run ./examples <name> |
—(无版本清单) |
| Python | __init__.py |
pip install -e . |
python -m pytest |
python -m examples <name> |
setuptools-scm |
| C++ | mpb.h |
cmake + cmake --build |
ctest --test-dir build |
mpb_examples <name> |
git describe(git_tag_info.cmake) |
| Zig | mpb.zig |
zig build |
zig build test |
zig build run -- <name> |
build.zig.zon |
| Rust | lib.rs |
cargo build |
cargo test |
cargo run --example examples -- <name> |
Cargo.toml |
版本号由 quant1x/auto-changelog 在发版时统一写入
Cargo.toml、build.zig.zon 等清单文件,不手工维护。
五种语言的源文件同目录并排(与 mopr 的组织方式一致):
mpb/
├── *.go go.mod
├── *.py pyproject.toml
├── *.h / *.cpp CMakeLists.txt、git_tag_info.cmake
├── *.zig build.zig、build.zig.zon、unicode_width.zig
├── Cargo.toml lib.rs
├── examples/
│ ├── main.{go,py,cpp,zig,rs} 每种语言一个入口,按名字分派
│ └── <name>.{go,py,cpp,zig,rs} 17 个示例,同名同语义
├── scripts/generate.{ps1,sh} 重录 screenshots/
└── screenshots/ 16 条 VHS 录屏
| Rust(上游) | Go | Python | C++ | Zig | 内容 |
|---|---|---|---|---|---|
style.rs |
style.go |
style.py |
style.h / style.cpp |
style.zig |
模板解析、ANSI 样式、宽度度量 |
state.rs |
state.go |
state.py |
state.h / state.cpp |
state.zig |
BarState / ProgressState、出帧、Estimator |
draw_target.rs |
draw_target.go |
draw_target.py |
draw_target.h / draw_target.cpp |
draw_target.zig |
绘制目标、限速、行数调整 |
multi.rs |
multi.go |
multi.py |
multi.h / multi.cpp |
multi.zig |
MultiProgress / MultiState、orphan 行、僵尸行 |
progress_bar.rs |
progress_bar.go |
progress_bar.py |
progress_bar.h / progress_bar.cpp |
progress_bar.zig |
ProgressBar 门面 |
iter.rs / rayon.rs |
iter.go / rayon.go |
iter.py |
iter.h |
—(未移植) | 迭代器包装 |
format.rs |
format.go |
format.py |
format.h / format.cpp |
format.zig |
HumanBytes / HumanDuration / HumanCount 等 |
in_memory.rs / term_like.rs |
in_memory.go / term_like.go |
in_memory.py / term_like.py |
in_memory.h / in_memory.cpp、term_like.h / term_like.cpp |
term_like.zig |
内存终端与终端抽象 |
lib.rs |
doc.go |
__init__.py |
mpb.h |
mpb.zig |
包入口与导出 |
说明:
- Rust 列即上游规范;Rust 侧的同名
mpbcrate 直接 re-export,因此没有独立的模块文件。 - C++ 另有
types.h(共享枚举/时间类型)、utf8.h(UTF-8 编解码)、unicode_width_tables.h(由 Rustunicode-width 0.2.2生成的宽度区间表)。 - Zig 另有
width.zig+unicode_width.zig:宽度算法与同一份unicode-width 0.2.2区间表。 - Rust 的
Drop for BarState在四个移植里的对应实现: Goweakref.go+ finalizer、Pythonweakref+BarState.__del__、 C++std::weak_ptr+ 析构函数、Zig 引用计数 +ProgressBar.deinit()。
package main
import "github.com/quant1x/mpb"
func main() {
pb := mpb.New(1024)
for i := 0; i < 1024; i++ {
pb.Inc(1)
}
pb.FinishWithMessage("done")
}go build ./...
go test ./...pip install -e .from mpb import ProgressBar, ProgressStyle
pb = ProgressBar(1024)
pb.set_style(ProgressStyle("{wide_bar} {pos}/{len}"))
for _ in range(1024):
pb.inc(1)
pb.finish_with_message("done")python -m pytest#include "mpb.h"
int main() {
auto pb = mpb::ProgressBar::make(1024);
pb->set_style(mpb::progress_style_with_template("{wide_bar} {pos}/{len}"));
for (int i = 0; i < 1024; ++i) {
pb->inc();
}
pb->finish_with_message("done");
}# Linux / macOS(GCC / Clang)
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
ctest --test-dir build --output-on-failure# Windows(MSVC)
cmake -S . -B build-msvc -G "Visual Studio 17 2022" -A x64
cmake --build build-msvc --config Release -j
ctest --test-dir build-msvc -C Release --output-on-failure非 git 源码树用 -DMPB_VERSION_OVERRIDE=x.y.z 显式指定版本;-DMPB_BUILD_EXAMPLES=OFF
可以只构建库。
Windows 控制台与 Zig 走同一套处理:输出经 WriteConsoleW 转 UTF-16(绕开 OEM 代码页),
并打开 ENABLE_VIRTUAL_TERMINAL_PROCESSING;短休眠用 mpb::sleep_for
(CREATE_WAITABLE_TIMER_HIGH_RESOLUTION),避免 std::this_thread::sleep_for 落到
15.6ms 粒度。macOS/POSIX 直接使用标准库。
const mpb = @import("mpb");
var pb = mpb.ProgressBar.new(1024);
defer pb.deinit();
for (0..1024) |_| pb.inc(1);
pb.finishWithMessage("done");zig build test # 单元测试
zig build # 构建 mpb_examples[dependencies]
# 发布后:mpb = "0.1";仓库内开发时也可以直接用 path 依赖
mpb = { path = "../mpb" }use mpb::{ProgressBar, ProgressStyle};
let pb = ProgressBar::new(1024);
pb.set_style(ProgressStyle::with_template("{wide_bar} {pos}/{len}").unwrap());
for _ in 0..1024 {
pb.inc(1);
}
pb.finish_with_message("done");cargo test
cargo build --example examplesmpb 没有自己的 feature:in_memory / improved_unicode / futures / rayon / tokio
全部默认打开。mpb::ProgressBar 就是 indicatif::ProgressBar,不是包装类型。
17 个示例与上游 examples/ 同名,统一入口按名字分派。名字使用上游的连字符写法
(multi-tree),五个入口同时接受下划线写法(multi_tree)。
| 示例 | 演示内容 | 录屏 |
|---|---|---|
single |
单条进度条,1024 步 × 5ms | gif |
fastbar |
百万次极快更新下的 20Hz 出帧与耗时统计 | gif |
finebars |
5 条不同粒度/颜色的条并发推进 | gif |
log |
println 打日志不破坏进度条画面 |
gif |
morebars |
一条总条 + 多条子条的嵌套推进 | gif |
multi |
三条并发进度条 + println + insert_after |
— |
multi-tree |
树形嵌套:元素按缩进逐级加入 | gif |
multi-tree-ext |
树形增删 + --bottom-alignment |
gif |
cargo |
模拟 cargo 编译:4 个 worker 抢 crate 队列 | gif |
cargowrap |
把 cargo build --release 的 stderr 喂给 spinner |
gif |
download |
{bytes}/{total_bytes} + 自定义 eta key |
gif |
download-continued |
断点续传:非零起点 + reset_eta() |
gif |
download-speed |
在 download 基础上加 {bytes_per_sec} |
gif |
iterator |
progress() / progress_count() / progress_with() |
gif |
long-spinner |
steady tick + 自定义 spinner 帧 | gif |
tokio |
定时器驱动:每 5ms 一个 tick | gif |
yarnish |
四段式安装输出,最后用 HumanDuration 报耗时 |
gif |
五种语言的运行方式:
go run ./examples multi-tree
python -m examples multi-tree
./build/mpb_examples multi-tree # MSVC 多配置生成器在 build/Release/ 下
zig build run -- multi-tree
cargo run --example examples -- multi-tree带参数的示例(Zig/Cargo 需要 -- 分隔自己的参数):
go run ./examples multi-tree-ext --bottom-alignment
python -m examples multi-tree-ext --bottom-alignment
./build/mpb_examples multi-tree-ext --bottom-alignment
zig build run -- multi-tree-ext -- --bottom-alignment
cargo run --example examples -- multi-tree-ext -- --bottom-alignment重新录制 screenshots/(脚本会自己切到仓库根目录,PowerShell 与 bash 行为一致):
./scripts/generate.ps1 # 全部 16 条
./scripts/generate.ps1 single log # 只重录指定几条./scripts/generate.sh # 全部 16 条
./scripts/generate.sh single log # 只重录指定几条| 类别 | key |
|---|---|
| 进度条 | bar、wide_bar、percent、percent_precise |
| 位置/长度 | pos、len、human_pos、human_len |
| 字节 | bytes、total_bytes、decimal_bytes、decimal_total_bytes、binary_bytes、binary_total_bytes |
| 速率 | per_sec、bytes_per_sec、decimal_bytes_per_sec、binary_bytes_per_sec |
| 时间 | elapsed、elapsed_precise、eta、eta_precise、duration、duration_precise |
| 文本 | msg、wide_msg、prefix、spinner |
| 自定义 | with_key(name, fn)(Rust/Go/Python/C++/Zig 均支持) |
模板语法与上游一致:{key:对齐宽度.样式/替代样式}、{key!} 截断、{{ / }} 转义。
bar 的示例:{bar:40.cyan/blue}、{wide_bar:.red}。
- 终端出帧默认 20Hz(
RateLimiter,MAX_BURST = 20,允许短时突发); - 位置更新另有一层 1ms 令牌桶(
AtomicPosition,MAX_BURST = 10),避免高频inc打爆状态; - ETA/速率用 15 秒半衰期的双指数平滑估算器;
- 非 TTY(管道、重定向、CI)时整条进度条隐藏,与上游一致。
MultiState 用 members + free_set + ordering 维护槽位与屏幕顺序:
- orphan 行:
println的消息先入队列,下一帧出现在所有进度条上方; - 僵尸行:
ProgressBar被释放后,它最后一次绘制的行保留到下一次重绘,然后回收; - 对齐:
Top(默认)/Bottom,删除中间条时决定整体上移还是下移; clear/suspend/println都会正确擦除僵尸行。
| API | Rust | Go | 含义 |
|---|---|---|---|
Index() |
index() -> Option<MultiStateIndex> |
(uint, bool) |
不透明槽位下标,会被 free_set 复用,与屏幕顺序无关 |
VisualIndex() |
visual_index() -> Option<usize> |
(uint, bool) |
屏幕位置(第几行) |
Insert / InsertFromBack |
insert(index, ..) |
index uint |
插入位置 |
需要“第几条 bar”时用 VisualIndex(),不要用 Index()。
| 维度 | Go | Python | C++ | Zig | Rust |
|---|---|---|---|---|---|
| 时间类型 | time.Duration |
float 秒 |
std::chrono::nanoseconds |
i96 纳秒 |
std::time::Duration |
| 迭代器 | iter.go(切片) |
任意可迭代对象 | iter.h |
未移植 | ProgressIterator |
| 宽度表 | go-runewidth |
unicodedata + 手写规则 |
Rust unicode-width 表 |
Rust unicode-width 表 |
unicode-width |
| 生命周期 | finalizer + weak registry | weakref + __del__ |
weak_ptr + 析构 |
引用计数 + deinit() |
Drop |
| 跨平台时序 | 标准库 | 标准库 | platform.cpp(高精度 sleep / 控制台) |
自带高精度 sleep / 控制台处理 | 标准库 + console |
下面这些来自语言/运行时,不是 Rust 语义偏差:
- 错误处理:Python 用异常(
TemplateError、ValueError),Go 用(value, error);MultiProgress.println()/clear()在 Python 返回None。 - 迭代器:Python 支持任意可迭代对象/生成器;Go 只包装切片。
- 格式化 API:Go 的
HumanBytes等是实现String/Format的类型,Python 是普通函数,输出逐字一致。 - 样式拷贝:Python 的
set_style()/style()/with_*按值克隆,避免浅拷贝共享。 - 整数边界:位置/长度按
u64语义处理;Python 在边界处显式取模/钳位,负数抛ValueError。 - 锁:Python 用
RLock,suspend回调内可重入;Go 的 Mutex 非重入。 - 回收时机:Go/Python 依赖运行时,不保证在确定时刻触发;语义与 Rust 的 Drop 一致。
Rust 的这些差异由 std 和 console crate 消化,移植版各自实现,统一按四点检查:
| 检查项 | Go | Python | C++ | Zig |
|---|---|---|---|---|
| 控制台编码 | WriteConsoleW;示例切 UTF-8 代码页 |
TextIOWrapper 自动走 UTF-16 |
WriteConsoleW;示例 ConsoleGuard 切 UTF-8 |
WriteConsoleW |
| VT 处理 | 打开 ENABLE_VIRTUAL_TERMINAL_PROCESSING |
同左(ctypes) |
同左 | 同左 |
| 短休眠 | time.Sleep 本身约 3ms 精度 |
time.sleep 约 3ms 精度 |
mpb::sleep_for 高精度定时器 |
util.sleepMicros 高精度定时器 |
| 终端尺寸 | x/term |
os.get_terminal_size |
GetConsoleScreenBufferInfo |
GetConsoleScreenBufferInfo |
- 控制台默认按 OEM 代码页(中文系统 GBK)解释
WriteFile的字节,UTF-8 的█░/✔/ emoji 会乱码,宽度与补白随之错位 —— 所以控制台句柄必须走WriteConsoleW(UTF-8 先转 UTF-16);示例里的fmt.Print*/std::cout则通过切换输出代码页解决。 - 没有
ENABLE_VIRTUAL_TERMINAL_PROCESSING时,\x1b[2K/\x1b[nA会被 原样打印,画面逐帧堆叠。 - C++ / Zig 的
std::this_thread::sleep_for/Io.sleep受默认 15.6ms 定时器粒度 支配,短休眠改用CREATE_WAITABLE_TIMER_HIGH_RESOLUTION可等待定时器; Go / Python 的运行时本身已经是毫秒以下精度。 - Zig 的自旋提示与 Rust 一致:x86
pause、arm64isb;不使用 arm64yield, 避免 Apple Silicon 的线程节流形成 convoy。
- 宽度开关命名:Rust
Width::set_default_cjk(bool);GoDefaultWidth.SetDefaultCJK(bool); Pythonmpb.DEFAULT_WIDTH.set_default_cjk(bool);C++mpb::Width::set_default_cjk(bool); Zigmpb.setDefaultCjk(bool)。 MultiProgress状态导出:Go 的State字段对应 Rust 0.18.6 的pub state, 访问前需自行加MultiState的锁。
- Zig:
iter/rayon尚未移植;示例里的随机数用固定种子的 LCG 替代rand,cargowrap回放构建输出而不是递归构建,tokio示例用等价的 5ms 循环;InMemoryTerm是 VT100 子集(覆盖 mpb 自身产生的序列)。 - Rust 示例:同样用确定性 LCG 替代
rand;cargowrap会真的执行cargo build --release(因此需要 cargo 且在 Rust 工程目录内运行)。 - 宽度表:Python 用
unicodedata、Go 用go-runewidth,与 Rustunicode-width 0.2.2在 ASCII/Latin/CJK/emoji/方块/盲文等常用区间一致;约 4k 个生僻/未分配码点 (组合记号、Unicode 版本差异)可能有 0/1/2 列差异。C++ 与 Zig 直接使用 Rust 的表。 - C++:极少数阿拉伯/希伯来/印度文字连字仍按字符合成,未复刻
unicode-width的完整连字状态机。
MIT。
