Introduction to Output in Rust
Rust provides several ways to output data, from simple console printing to complex formatted output and file I/O. Understanding these mechanisms is crucial for debugging, user interaction, and data persistence. Rust's output system is built around macros and traits that provide flexibility, type safety, and performance.
1. Console Output Macros
Basic Printing Macros
fn main() {
// println! - prints with newline
println!("Hello, World!");
// print! - prints without newline
print!("Hello, ");
print!("World!");
// Output: Hello, World! (on same line)
// eprintln! - prints to stderr with newline (for errors)
eprintln!("Error: Something went wrong!");
// eprint! - prints to stderr without newline
eprint!("Processing... ");
eprint!("Done!");
}
Formatted Output with println! and print!
fn main() {
let name = "Alice";
let age = 30;
let height = 5.8;
// Basic formatting
println!("Name: {}, Age: {}, Height: {}", name, age, height);
// Positional arguments
println!("{0} is {1} years old. {0} loves Rust!", name, age);
// Named arguments
println!("{name} is {age} years old", name = "Bob", age = 25);
// Formatting numbers
println!("Decimal: {} Hex: {:X} Octal: {:o}", 255, 255, 255);
println!("Binary: {:b}", 42);
// Padding and alignment
println!("|{:5}|", 42); // Right align, width 5: "| 42|"
println!("|{:<5}|", 42); // Left align: "|42 |"
println!("|{:^5}|", 42); // Center: "| 42 |"
println!("|{:*^5}|", 42); // Center with * fill: "|**42*|"
// Precision for floats
let pi = 3.14159265359;
println!("Pi: {:.2}", pi); // 2 decimal places: "3.14"
println!("Pi: {:.5}", pi); // 5 decimal places: "3.14159"
// Debug formatting
let vec = vec![1, 2, 3];
println!("Debug: {:?}", vec); // [1, 2, 3]
println!("Pretty: {:#?}", vec); // Formatted with newlines
// Display vs Debug
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
let p = Point { x: 10, y: 20 };
println!("Debug: {:?}", p); // Point { x: 10, y: 20 }
println!("Pretty: {:#?}", p); // Point {
// x: 10,
// y: 20,
// }
}
2. Custom Display and Debug Formatting
Implementing Display Trait
use std::fmt;
struct Person {
name: String,
age: u32,
}
// Implement Display for custom formatting
impl fmt::Display for Person {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} ({})", self.name, self.age)
}
}
// Custom Debug implementation
impl fmt::Debug for Person {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Person")
.field("name", &self.name)
.field("age", &self.age)
.finish()
}
}
fn main() {
let person = Person {
name: String::from("Alice"),
age: 30,
};
println!("Display: {}", person); // Alice (30)
println!("Debug: {:?}", person); // Person { name: "Alice", age: 30 }
}
Advanced Formatter Features
use std::fmt;
struct RGB {
r: u8,
g: u8,
b: u8,
}
impl fmt::Display for RGB {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Handle width and alignment specifications
let hex = format!("#{:02X}{:02X}{:02X}", self.r, self.g, self.b);
if let Some(width) = f.width() {
match f.align() {
Some(fmt::Alignment::Left) => write!(f, "{:<width$}", hex, width = width),
Some(fmt::Alignment::Right) => write!(f, "{:>width$}", hex, width = width),
Some(fmt::Alignment::Center) => write!(f, "{:^width$}", hex, width = width),
None => write!(f, "{:width$}", hex, width = width),
}
} else {
write!(f, "{}", hex)
}
}
}
fn main() {
let color = RGB { r: 255, g: 128, b: 0 };
println!("{}", color); // #FF8000
println!("{:10}", color); // "#FF8000 " (right aligned)
println!("{:<10}", color); // "#FF8000 " (left aligned)
println!("{:^10}", color); // " #FF8000 " (centered)
}
3. String Formatting
format! Macro
fn main() {
// Basic string formatting
let name = "Alice";
let age = 30;
let message = format!("{} is {} years old", name, age);
println!("{}", message);
// Building complex strings
let parts = vec!["Hello", "world", "from", "Rust"];
let sentence = parts.join(" ");
println!("{}", sentence);
// Format with conditional logic
let numbers = vec![1, 2, 3, 4, 5];
let formatted = numbers.iter()
.map(|n| format!("{}", n))
.collect::<Vec<String>>()
.join(", ");
println!("Numbers: {}", formatted);
// Using format! for error messages
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err(format!("Cannot divide {} by zero", a))
} else {
Ok(a / b)
}
}
match divide(10.0, 0.0) {
Ok(result) => println!("Result: {}", result),
Err(e) => println!("Error: {}", e),
}
}
String Builders and Performance
use std::fmt::Write;
fn main() {
// Efficient string building
let mut output = String::new();
for i in 0..10 {
writeln!(&mut output, "Line {}", i).unwrap();
}
println!("{}", output);
// Using write! macro
let mut buffer = String::new();
write!(&mut buffer, "Hello, ").unwrap();
write!(&mut buffer, "World!").unwrap();
write!(&mut buffer, " The answer is {}.", 42).unwrap();
println!("{}", buffer);
// Performance comparison: format! vs push_str
let mut result = String::new();
// Fast: pre-allocate capacity
let mut fast = String::with_capacity(1000);
for i in 0..100 {
fast.push_str(&format!("Item {}\n", i));
}
// Even faster: use write! macro
let mut faster = String::with_capacity(1000);
for i in 0..100 {
writeln!(&mut faster, "Item {}", i).unwrap();
}
}
4. File Output
Basic File Writing
use std::fs::File;
use std::io::prelude::*;
use std::io::BufWriter;
fn main() -> std::io::Result<()> {
// Simple file write
let mut file = File::create("output.txt")?;
file.write_all(b"Hello, World!\n")?;
file.write_all("Line 2\n".as_bytes())?;
// Using write! macro with files
let mut file = File::create("formatted.txt")?;
write!(&mut file, "Name: {}\nAge: {}\n", "Alice", 30)?;
// Buffered writing for better performance
let file = File::create("buffered.txt")?;
let mut writer = BufWriter::new(file);
for i in 0..1000 {
writeln!(writer, "Line {}", i)?;
}
writer.flush()?; // Ensure all data is written
// Appending to file
use std::fs::OpenOptions;
let mut file = OpenOptions::new()
.write(true)
.append(true)
.create(true)
.open("log.txt")?;
writeln!(file, "Log entry at {}", chrono::Local::now())?;
Ok(())
}
Writing Structured Data
use std::fs::File;
use std::io::{Write, BufWriter};
use serde::{Serialize, Deserialize};
use serde_json;
use csv;
// JSON output
#[derive(Serialize, Deserialize, Debug)]
struct User {
id: u32,
name: String,
email: String,
active: bool,
}
fn write_json() -> std::io::Result<()> {
let users = vec![
User {
id: 1,
name: "Alice".to_string(),
email: "[email protected]".to_string(),
active: true,
},
User {
id: 2,
name: "Bob".to_string(),
email: "[email protected]".to_string(),
active: false,
},
];
// Write pretty JSON
let file = File::create("users.json")?;
let writer = BufWriter::new(file);
serde_json::to_writer_pretty(writer, &users)?;
// Write compact JSON
let file = File::create("users.min.json")?;
let writer = BufWriter::new(file);
serde_json::to_writer(writer, &users)?;
Ok(())
}
// CSV output
fn write_csv() -> std::io::Result<()> {
let file = File::create("users.csv")?;
let mut writer = csv::Writer::from_writer(file);
writer.write_record(&["ID", "Name", "Email", "Active"])?;
writer.write_record(&["1", "Alice", "[email protected]", "true"])?;
writer.write_record(&["2", "Bob", "[email protected]", "false"])?;
writer.flush()?;
Ok(())
}
// Binary output
fn write_binary() -> std::io::Result<()> {
use std::io::Write;
let mut file = File::create("data.bin")?;
// Write different data types
let number: u32 = 42;
let float: f64 = 3.14159;
let bytes = &[1, 2, 3, 4, 5];
file.write_all(&number.to_le_bytes())?; // Write as little-endian
file.write_all(&float.to_le_bytes())?;
file.write_all(bytes)?;
Ok(())
}
fn main() -> std::io::Result<()> {
write_json()?;
write_csv()?;
write_binary()?;
Ok(())
}
5. Advanced Output Formatting
Table Formatting
fn print_table() {
let data = vec![
("Alice", 30, "Engineer"),
("Bob", 25, "Designer"),
("Charlie", 35, "Manager"),
("Diana", 28, "Developer"),
];
// Calculate column widths
let name_width = data.iter().map(|(n, _, _)| n.len()).max().unwrap_or(4).max(4);
let age_width = 3; // "Age" width
let role_width = data.iter().map(|(_, _, r)| r.len()).max().unwrap_or(4).max(4);
// Print header
println!("┌─{:─^name_width$}─┬─{:─^age_width$}─┬─{:─^role_width$}─┐", "", "", "",
name_width = name_width + 2, age_width = age_width + 2, role_width = role_width + 2);
println!("│ {:^name_width$} │ {:^age_width$} │ {:^role_width$} │",
"Name", "Age", "Role",
name_width = name_width, age_width = age_width, role_width = role_width);
println!("├─{:─^name_width$}─┼─{:─^age_width$}─┼─{:─^role_width$}─┤", "", "", "",
name_width = name_width + 2, age_width = age_width + 2, role_width = role_width + 2);
// Print data rows
for (name, age, role) in data {
println!("│ {:name_width$} │ {:age_width$} │ {:role_width$} │",
name, age, role,
name_width = name_width, age_width = age_width, role_width = role_width);
}
// Print footer
println!("└─{:─^name_width$}─┴─{:─^age_width$}─┴─{:─^role_width$}─┘", "", "", "",
name_width = name_width + 2, age_width = age_width + 2, role_width = role_width + 2);
}
// Using external crate for tables
use prettytable::{Table, Row, Cell};
use prettytable::format;
fn print_table_pretty() {
let mut table = Table::new();
table.set_format(*format::consts::FORMAT_BOX_CHARS);
table.add_row(Row::new(vec![
Cell::new("Name"),
Cell::new("Age"),
Cell::new("Role"),
]));
table.add_row(Row::new(vec![
Cell::new("Alice"),
Cell::new("30"),
Cell::new("Engineer"),
]));
table.add_row(Row::new(vec![
Cell::new("Bob"),
Cell::new("25"),
Cell::new("Designer"),
]));
table.printstd();
}
fn main() {
println!("Custom table:");
print_table();
println!("\nPretty table:");
print_table_pretty();
}
Progress Indicators
use std::thread;
use std::time::Duration;
// Simple progress bar
fn simple_progress_bar() {
let total = 50;
print!("Progress: [");
for i in 0..total {
print!("=");
std::io::stdout().flush().unwrap();
thread::sleep(Duration::from_millis(50));
}
println!("] Done!");
}
// Animated spinner
fn spinner() {
let frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
let mut i = 0;
for _ in 0..100 {
print!("\rLoading... {}", frames[i]);
std::io::stdout().flush().unwrap();
thread::sleep(Duration::from_millis(50));
i = (i + 1) % frames.len();
}
println!("\rLoading... Done! ");
}
// Using indicatif crate for better progress bars
use indicatif::{ProgressBar, ProgressStyle};
fn advanced_progress() {
let pb = ProgressBar::new(100);
pb.set_style(
ProgressStyle::default_bar()
.template("[{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta})")
.unwrap()
.progress_chars("#>-"),
);
for i in 0..100 {
// Do some work
thread::sleep(Duration::from_millis(20));
pb.inc(1);
}
pb.finish_with_message("Done!");
}
fn main() {
simple_progress_bar();
println!();
spinner();
println!();
advanced_progress();
}
6. Logging and Debug Output
Simple Logging
use std::fs::OpenOptions;
use std::io::Write;
use std::time::SystemTime;
enum LogLevel {
Info,
Warning,
Error,
}
impl std::fmt::Display for LogLevel {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
LogLevel::Info => write!(f, "INFO"),
LogLevel::Warning => write!(f, "WARNING"),
LogLevel::Error => write!(f, "ERROR"),
}
}
}
struct Logger {
file: Option<std::fs::File>,
}
impl Logger {
fn new(log_file: Option<&str>) -> std::io::Result<Self> {
let file = if let Some(path) = log_file {
Some(OpenOptions::new()
.create(true)
.append(true)
.open(path)?)
} else {
None
};
Ok(Logger { file })
}
fn log(&mut self, level: LogLevel, message: &str) -> std::io::Result<()> {
let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S");
let log_entry = format!("[{}] {}: {}\n", timestamp, level, message);
// Write to console
match level {
LogLevel::Error => eprint!("{}", log_entry),
_ => print!("{}", log_entry),
}
// Write to file if configured
if let Some(file) = &mut self.file {
file.write_all(log_entry.as_bytes())?;
}
Ok(())
}
}
fn main() -> std::io::Result<()> {
let mut logger = Logger::new(Some("app.log"))?;
logger.log(LogLevel::Info, "Application started")?;
logger.log(LogLevel::Warning, "Low disk space")?;
logger.log(LogLevel::Error, "Failed to connect to database")?;
Ok(())
}
Using the log crate
use log::{info, warn, error, debug, LevelFilter};
use env_logger;
fn init_logging() {
env_logger::Builder::new()
.filter(None, LevelFilter::Debug)
.format(|buf, record| {
use std::io::Write;
writeln!(
buf,
"[{} {}:{}] {}",
chrono::Local::now().format("%Y-%m-%d %H:%M:%S"),
record.file().unwrap_or("unknown"),
record.line().unwrap_or(0),
record.args()
)
})
.init();
}
fn main() {
init_logging();
info!("Application starting up");
debug!("Debug information: {:?}", vec![1, 2, 3]);
warn!("This is a warning");
error!("An error occurred");
// The output will be:
// [2024-01-01 12:34:56 src/main.rs:25] Application starting up
// [2024-01-01 12:34:56 src/main.rs:26] Debug information: [1, 2, 3]
// [2024-01-01 12:34:56 src/main.rs:27] This is a warning
// [2024-01-01 12:34:56 src/main.rs:28] An error occurred
}
7. Network Output
HTTP Requests and Responses
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
// Simple HTTP server
fn handle_client(mut stream: TcpStream) -> std::io::Result<()> {
let mut buffer = [0; 1024];
stream.read(&mut buffer)?;
let response = "HTTP/1.1 200 OK\r\n\
Content-Type: text/html\r\n\
\r\n\
<html><body><h1>Hello from Rust!</h1></body></html>";
stream.write_all(response.as_bytes())?;
stream.flush()?;
Ok(())
}
fn start_server() -> std::io::Result<()> {
let listener = TcpListener::bind("127.0.0.1:8080")?;
println!("Server running on http://127.0.0.1:8080");
for stream in listener.incoming() {
match stream {
Ok(stream) => {
std::thread::spawn(|| {
if let Err(e) = handle_client(stream) {
eprintln!("Error handling client: {}", e);
}
});
}
Err(e) => {
eprintln!("Connection failed: {}", e);
}
}
}
Ok(())
}
// HTTP client using reqwest (in Cargo.toml: reqwest = { version = "0.11", features = ["blocking"] })
use reqwest::blocking::Client;
fn make_request() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
let response = client
.get("https://api.github.com/repos/rust-lang/rust")
.header("User-Agent", "Rust-Client")
.send()?;
println!("Status: {}", response.status());
println!("Headers: {:#?}", response.headers());
let body = response.text()?;
println!("Body: {}", body);
Ok(())
}
fn main() -> std::io::Result<()> {
// Start server in a separate thread for demo
std::thread::spawn(|| {
if let Err(e) = start_server() {
eprintln!("Server error: {}", e);
}
});
// Give server time to start
std::thread::sleep(std::time::Duration::from_secs(1));
// Make client request
if let Err(e) = make_request() {
eprintln!("Request failed: {}", e);
}
Ok(())
}
8. Color Output
ANSI Color Codes
// ANSI color codes
const RESET: &str = "\x1b[0m";
const BLACK: &str = "\x1b[30m";
const RED: &str = "\x1b[31m";
const GREEN: &str = "\x1b[32m";
const YELLOW: &str = "\x1b[33m";
const BLUE: &str = "\x1b[34m";
const MAGENTA: &str = "\x1b[35m";
const CYAN: &str = "\x1b[36m";
const WHITE: &str = "\x1b[37m";
// Background colors
const BG_RED: &str = "\x1b[41m";
const BG_GREEN: &str = "\x1b[42m";
const BG_YELLOW: &str = "\x1b[43m";
const BG_BLUE: &str = "\x1b[44m";
// Text styles
const BOLD: &str = "\x1b[1m";
const UNDERLINE: &str = "\x1b[4m";
const REVERSE: &str = "\x1b[7m";
fn color_demo() {
println!("{}Red text{}", RED, RESET);
println!("{}Green text{}", GREEN, RESET);
println!("{}Blue text{}", BLUE, RESET);
println!("{}{}Bold red text{}", BOLD, RED, RESET);
println!("{}{}Underlined green text{}", UNDERLINE, GREEN, RESET);
println!("{}Background red{}", BG_RED, RESET);
println!("{}{}Bold yellow on blue background{}", BOLD, BG_BLUE, RESET);
// Rainbow text
let text = "RAINBOW";
let colors = [RED, YELLOW, GREEN, CYAN, BLUE, MAGENTA];
for (i, c) in text.chars().enumerate() {
print!("{}{}", colors[i % colors.len()], c);
}
println!("{}", RESET);
}
// Using colored crate (in Cargo.toml: colored = "2")
use colored::*;
fn colored_crate_demo() {
println!("{}", "Red text".red());
println!("{}", "Green text".green());
println!("{}", "Blue text".blue());
println!("{}", "Bold text".bold());
println!("{}", "Underlined".underline());
println!("{}", "Italic".italic());
println!("{}", "Red on blue".red().on_blue());
println!("{}", "Bold yellow".yellow().bold());
// Complex formatting
let value = 42;
println!("The answer is {}!",
value.to_string().green().bold());
// Conditional coloring
let statuses = vec![200, 404, 500, 200, 301];
for status in statuses {
match status {
200 => println!("{}", status.to_string().green()),
301 | 302 => println!("{}", status.to_string().yellow()),
400..=499 => println!("{}", status.to_string().red()),
500..=599 => println!("{}", status.to_string().red().bold()),
_ => println!("{}", status.to_string().white()),
}
}
}
fn main() {
println!("ANSI Colors:");
color_demo();
println!("\nColored Crate:");
colored_crate_demo();
}
9. Redirecting Output
Capturing Output
use std::io::{self, Write};
struct CaptureOutput {
buffer: Vec<u8>,
}
impl Write for CaptureOutput {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.buffer.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
fn main() {
// Capture stdout temporarily
let mut captured = CaptureOutput { buffer: Vec::new() };
{
// Redirect stdout to our capture
let original_stdout = std::io::stdout();
let mut handle = original_stdout.lock();
// Write directly to capture
writeln!(&mut captured, "This is captured").unwrap();
writeln!(&mut captured, "Another line").unwrap();
}
// Convert captured bytes to string
let captured_output = String::from_utf8(captured.buffer).unwrap();
println!("Captured output:\n{}", captured_output);
// Using std::process::Output
use std::process::Command;
let output = Command::new("echo")
.arg("Hello from command")
.output()
.expect("Failed to execute command");
println!("Command stdout: {}", String::from_utf8_lossy(&output.stdout));
println!("Command stderr: {}", String::from_utf8_lossy(&output.stderr));
}
10. Performance Optimization
Buffered vs Unbuffered Output
use std::io::{self, Write, BufWriter};
use std::time::Instant;
fn unbuffered_write() -> io::Result<()> {
let start = Instant::now();
let mut file = std::fs::File::create("unbuffered.txt")?;
for i in 0..100_000 {
writeln!(file, "Line {}", i)?;
}
println!("Unbuffered: {:?}", start.elapsed());
Ok(())
}
fn buffered_write() -> io::Result<()> {
let start = Instant::now();
let file = std::fs::File::create("buffered.txt")?;
let mut writer = BufWriter::new(file);
for i in 0..100_000 {
writeln!(writer, "Line {}", i)?;
}
writer.flush()?;
println!("Buffered: {:?}", start.elapsed());
Ok(())
}
fn main() -> io::Result<()> {
// Performance comparison
unbuffered_write()?;
buffered_write()?;
// Memory buffer for speed
let mut buffer = Vec::with_capacity(1024 * 1024); // 1MB buffer
// Write to memory buffer first
for i in 0..100_000 {
writeln!(&mut buffer, "Line {}", i)?;
}
// Then write to file in one go
let start = Instant::now();
std::fs::write("memory_buffered.txt", &buffer)?;
println!("Memory buffered write: {:?}", start.elapsed());
Ok(())
}
11. Serialization Output
JSON Serialization
use serde::{Serialize, Deserialize};
use serde_json::{json, Value};
#[derive(Serialize, Deserialize, Debug)]
struct Data {
id: u32,
name: String,
tags: Vec<String>,
metadata: std::collections::HashMap<String, Value>,
}
fn json_examples() -> Result<(), Box<dyn std::error::Error>> {
// Creating JSON directly
let json_data = json!({
"id": 1,
"name": "Example",
"tags": ["rust", "json", "serialization"],
"metadata": {
"created": "2024-01-01",
"version": 1.0
}
});
println!("JSON from macro:\n{}", serde_json::to_string_pretty(&json_data)?);
// From struct
let data = Data {
id: 2,
name: "Structured Data".to_string(),
tags: vec!["test".to_string(), "example".to_string()],
metadata: {
let mut map = std::collections::HashMap::new();
map.insert("priority".to_string(), json!("high"));
map.insert("count".to_string(), json!(42));
map
},
};
println!("\nJSON from struct:\n{}", serde_json::to_string_pretty(&data)?);
// Stream JSON to file
let file = std::fs::File::create("data.json")?;
serde_json::to_writer_pretty(file, &data)?;
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
json_examples()?;
Ok(())
}
YAML and TOML Output
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
struct Config {
server: ServerConfig,
database: DatabaseConfig,
logging: LoggingConfig,
}
#[derive(Serialize, Deserialize, Debug)]
struct ServerConfig {
host: String,
port: u16,
workers: usize,
}
#[derive(Serialize, Deserialize, Debug)]
struct DatabaseConfig {
url: String,
pool_size: u32,
timeout_seconds: u64,
}
#[derive(Serialize, Deserialize, Debug)]
struct LoggingConfig {
level: String,
file: String,
}
fn output_formats() -> Result<(), Box<dyn std::error::Error>> {
let config = Config {
server: ServerConfig {
host: "127.0.0.1".to_string(),
port: 8080,
workers: 4,
},
database: DatabaseConfig {
url: "postgresql://localhost/mydb".to_string(),
pool_size: 10,
timeout_seconds: 30,
},
logging: LoggingConfig {
level: "info".to_string(),
file: "app.log".to_string(),
},
};
// YAML output
let yaml = serde_yaml::to_string(&config)?;
println!("YAML:\n{}", yaml);
std::fs::write("config.yaml", yaml)?;
// TOML output
let toml = toml::to_string_pretty(&config)?;
println!("\nTOML:\n{}", toml);
std::fs::write("config.toml", toml)?;
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
output_formats()?;
Ok(())
}
Conclusion
Rust provides a comprehensive set of tools for output operations:
- Console Output:
println!,print!,eprintln!for basic output - Formatted Output: Format strings with precision, alignment, and padding
- Custom Formatting: Implementing
DisplayandDebugtraits - File I/O: Buffered and unbuffered writing, appending, binary output
- Structured Data: JSON, YAML, TOML, CSV serialization
- Logging: Built-in and external logging frameworks
- Network Output: HTTP servers and clients
- Visual Output: Progress bars, colored text, tables
- Performance: Buffered writing for optimal performance
The choice of output method depends on:
- Performance requirements: Use buffered writers for large data
- Format requirements: Structured vs unstructured data
- User experience: Progress indicators and colored output
- Debugging needs: Logging with different levels
- Data persistence: File I/O with various formats
Rust's output system is designed to be both powerful and safe, with compile-time checking of format strings and proper resource management through ownership and borrowing.