rich_rust
Beautiful terminal output for Rust, inspired by Python's Rich.
TL;DR
The Problem
Building beautiful terminal UIs in Rust is tedious. You either:
- Write raw ANSI escape codes (error-prone, unreadable)
- Use low-level crates that require boilerplate for simple things
- Miss features like automatic terminal capability detection, tables, progress bars
The Solution
rich_rust brings Python Rich's ergonomic API to Rust: styled text, tables, panels, progress bars, syntax highlighting, and more. Zero unsafe code, automatic terminal detection.
Why Use rich_rust?
| Feature | rich_rust | Raw ANSI | colored | termion |
|---|---|---|---|---|
Markup syntax ([bold red]text[/]) |
Yes | No | No | No |
| Tables with auto-sizing | Yes | No | No | No |
| Panels and boxes | Yes | No | No | No |
| Progress bars & spinners | Yes | No | No | No |
| Syntax highlighting | Yes | No | No | No |
| Markdown rendering | Yes | No | No | No |
| Auto color downgrade | Yes | No | Partial | No |
| Unicode width handling | Yes | No | No | Partial |
Quick Example
use *;
Output:
Success! Operation completed.
Error: File not found
─────────────────── Configuration ───────────────────
┌─────────────────────── Users ───────────────────────┐
│ Name │ Role │
├────────┼────────┤
│ Alice │ Admin │
│ Bob │ User │
└────────────────────────────────────────────────────┘
┌─────────── Greeting ───────────┐
│ Hello, World! │
└────────────────────────────────┘
Design Philosophy
1. Zero Unsafe Code
The entire codebase uses safe Rust. No segfaults, no data races, no undefined behavior.
2. Python Rich Compatibility
API and behavior closely follow Python Rich. If you know Rich, you know rich_rust. The RICH_SPEC.md documents every behavioral detail.
3. Renderable Extensibility
Instead of Python's duck typing, rich_rust uses explicit render methods and an optional measurement trait:
use ;
use ;
use Segment;
;
Renderables expose render(...) -> Vec<Segment>. Implement RichMeasure to
participate in layout width calculations.
4. Automatic Terminal Detection
rich_rust detects terminal capabilities at runtime:
- Color support (4-bit, 8-bit, 24-bit truecolor)
- Terminal dimensions
- Unicode support
- Legacy Windows console
Colors automatically downgrade to what the terminal supports.
5. Minimal Dependencies
Core functionality has few dependencies. Optional features (syntax highlighting, markdown) are behind feature flags to keep compile times fast.
Comparison vs Alternatives
| Feature | rich_rust | Python Rich | colored | termcolor | owo-colors |
|---|---|---|---|---|---|
| Language | Rust | Python | Rust | Rust | Rust |
| Markup parsing | [bold]text[/] |
[bold]text[/] |
No | No | No |
| Tables | Yes | Yes | No | No | No |
| Panels/Boxes | Yes | Yes | No | No | No |
| Progress bars | Yes | Yes | No | No | No |
| Trees | Yes | Yes | No | No | No |
| Syntax highlighting | Yes (syntect) | Yes (Pygments) | No | No | No |
| Markdown | Yes | Yes | Yes | No | No |
| JSON pretty-print | Yes | Yes | No | No | No |
| Color downgrade | Auto | Auto | Partial | Yes | No |
| Zero unsafe | Yes | N/A | Yes | Yes | Yes |
| No runtime | Yes | No (Python) | Yes | Yes | Yes |
| Single binary | Yes | No | Yes | Yes | Yes |
When to use rich_rust:
- You want Python Rich's features in Rust
- You need tables, panels, or progress bars
- You want markup syntax for styling
- You're building CLI tools that need beautiful output
When to use alternatives:
colored: Simple color-only needs, minimal dependenciestermcolor: Cross-platform color with Windows supportowo-colors: Zero-allocation, const colors- Python Rich: You're writing Python
Installation
From crates.io
With Optional Features
# Syntax highlighting
# Markdown rendering
# JSON pretty-printing
# All features
From Source
Cargo.toml
[]
= "0.1"
# Or with features:
= { = "0.1", = ["full"] }
Quick Start
1. Create a Console
use *;
let console = new;
2. Print Styled Text
// Using markup syntax
console.print;
// Using explicit style
console.print_styled;
// Plain text (no markup parsing)
console.print_plain;
3. Create a Table
let mut table = new
.title
.with_column
.with_column;
table.add_row_cells;
table.add_row_cells;
console.print_renderable;
4. Create a Panel
let panel = from_text
.title
.subtitle
.width;
console.print_renderable;
5. Print a Rule
// Simple rule
console.rule;
// Rule with title
console.rule;
// Styled rule
let rule = with_title
.style
.align_left;
console.print_renderable;
Feature Reference
Markup Syntax
| Markup | Effect |
|---|---|
[bold]text[/] |
Bold text |
[italic]text[/] |
Italic text |
[underline]text[/] |
Underlined text |
[red]text[/] |
Red foreground |
[on blue]text[/] |
Blue background |
[bold red on white]text[/] |
Combined styles |
[#ff0000]text[/] |
Hex color |
[rgb(255,0,0)]text[/] |
RGB color |
[color(196)]text[/] |
256-color palette |
Style Attributes
new
.bold
.italic
.underline
.strikethrough
.dim
.reverse
.foreground
.background
Color Systems
| System | Colors | Detection |
|---|---|---|
| Standard | 16 | Basic terminals |
| 256-color | 256 | Most modern terminals |
| Truecolor | 16M | iTerm2, Windows Terminal, etc. |
Box Styles
from_text.rounded // ╭─╮ (default)
from_text.square // ┌─┐
from_text.heavy // ┏━┓
from_text.double // ╔═╗
from_text.ascii // +-+
Progress Bars
let bar = new
.completed
.total
.width;
console.print_renderable;
Trees
let mut root = new;
root.add_child;
root.add_child;
let tree = new;
console.print_renderable;
Syntax Highlighting (requires syntax feature)
use *;
let code = r#"fn main() { println!("Hello"); }"#;
let syntax = new
.line_numbers
.theme;
console.print_renderable;
Markdown Rendering (requires markdown feature)
use *;
let md = new;
console.print_renderable;
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Console │
│ (Central coordinator: options, rendering, I/O) │
└─────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Renderables │
│ (Text, Table, Panel, Rule, Tree, Progress, Syntax, etc.) │
│ Expose render() + optional RichMeasure for sizing │
└─────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Segments │
│ (Atomic unit: text + optional style + control codes) │
└─────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ ANSI Codes + Output │
│ (Style diffing, escape sequences, terminal write) │
└─────────────────────────────────────────────────────────────┘
Module Structure
src/
├── lib.rs # Crate root, prelude
├── color.rs # Color system (4/8/24-bit)
├── style.rs # Style attributes (bold, italic, etc.)
├── segment.rs # Atomic rendering unit
├── text.rs # Rich text with spans
├── markup/ # Markup parser ([bold]...[/])
├── measure.rs # Width measurement protocol
├── console.rs # Console I/O coordinator
├── terminal.rs # Terminal detection
├── cells.rs # Unicode cell width
├── box.rs # Box drawing characters
└── renderables/
├── align.rs # Alignment
├── columns.rs # Multi-column layout
├── padding.rs # Padding
├── panel.rs # Boxed panels
├── progress.rs # Progress bars, spinners
├── rule.rs # Horizontal rules
├── table.rs # Tables with auto-sizing
├── tree.rs # Hierarchical trees
├── syntax.rs # Syntax highlighting (optional)
├── markdown.rs # Markdown rendering (optional)
└── json.rs # JSON pretty-print (optional)
Troubleshooting
Colors not showing
Symptom: Text prints without colors in terminal.
Causes & Fixes:
- Piped output: Colors disabled when stdout isn't a TTY. Use
FORCE_COLOR=1env var. - Terminal doesn't support colors: Try a modern terminal (iTerm2, Windows Terminal).
- TERM variable: Ensure
TERMis set correctly (xterm-256color, etc.).
Unicode characters garbled
Symptom: Box characters display as ? or mojibake.
Fixes:
- Use
.ascii()variant:Panel::from_text("...").ascii() - Set terminal encoding to UTF-8
- Use a font with box-drawing characters (most monospace fonts have them)
Table columns too wide/narrow
Symptom: Table layout doesn't fit terminal.
Fixes:
- Get terminal width:
console.width() - Set explicit column widths:
Column::new("...").width(20) - Set min/max widths:
Column::new("...").min_width(10).max_width(40)
Markup not parsing
Symptom: [bold]text[/] prints literally.
Fixes:
- Use
console.print()notconsole.print_plain() - Check for unbalanced brackets
- Escape literal brackets:
\[not markup\]
Windows console issues
Symptom: Escape codes visible or wrong colors on Windows.
Fixes:
- Use Windows Terminal (modern) instead of cmd.exe
- Enable virtual terminal processing:
SetConsoleModewithENABLE_VIRTUAL_TERMINAL_PROCESSING - rich_rust auto-detects this, but old cmd.exe may not support it
Limitations
- No animations: Static output only; no live updating (use a TUI framework for that)
- No input: This is an output library; use
crosstermordialoguerfor input - No async: Rendering is synchronous; wrap in
spawn_blockingif needed - No HTML export: Terminal-only output (no HTML/SVG export like Python Rich)
- Feature parity: Some Python Rich features not yet implemented (Live, Layout, Logging handler)
FAQ
Q: How does this compare to Python Rich?
A: rich_rust aims for API compatibility where it makes sense. The markup syntax, renderables, and color system are nearly identical. Differences exist where Rust's type system or performance characteristics warrant them.
Q: Is this production-ready?
A: It's in active development (v0.1.x). Core features work well, but the API may change. Pin your version in Cargo.toml.
Q: Can I use this in a TUI application?
A: rich_rust is for styled output, not interactive TUIs. For interactive apps, use ratatui, cursive, or tui-rs (which can potentially use rich_rust for styled text rendering).
Q: Why not just use Python Rich via PyO3?
A: Native Rust has no Python runtime dependency, compiles to a single binary, and avoids FFI overhead. If you're already in Rust, stay in Rust.
Q: How do I contribute?
A: See the "About Contributions" section below.
Q: What's the minimum Rust version?
A: Rust 2024 edition (nightly required currently). Check rust-toolchain.toml for specifics.
About Contributions
Please don't take this the wrong way, but I do not accept outside contributions for any of my projects. I simply don't have the mental bandwidth to review anything, and it's my name on the thing, so I'm responsible for any problems it causes; thus, the risk-reward is highly asymmetric from my perspective. I'd also have to worry about other "stakeholders," which seems unwise for tools I mostly make for myself for free. Feel free to submit issues, and even PRs if you want to illustrate a proposed fix, but know I won't merge them directly. Instead, I'll have Claude or Codex review submissions via gh and independently decide whether and how to address them. Bug reports in particular are welcome. Sorry if this offends, but I want to avoid wasted time and hurt feelings. I understand this isn't in sync with the prevailing open-source ethos that seeks community contributions, but it's the only way I can move at this velocity and keep my sanity.
License
MIT License. See LICENSE for details.