A custom CSV parser built from scratch without external CSV libraries, demonstrating manual file parsing and data structure design.
- File I/O: Reading files line by line with
BufReader - String Parsing: Splitting and processing CSV format
- Iterator Methods: Using
enumerate(),position(),iter() - Option Handling: Working with
Option<T>for safe access - Error Propagation: Using
?operator forResulttypes - Struct Design: Creating ergonomic APIs for data access
- Parse CSV files into structured data
- Header extraction
- Row-wise access
- Column lookup by name
- Type-safe record iteration
- Automatic data cleaning (trimming whitespace)
cargo runThe program reads text.csv and parses it into Record structs.
name,age,city
Alice,30,London
Bob,25,Paris
mike, 30
sarah, r, lagosfn from_file(path: &str) -> std::io::Result<Csv> {
let f = File::open(path)?;
let reader = BufReader::new(f);
let mut headers = Vec::<String>::new();
let mut rows = Vec::<Vec<String>>::new();
for (i, res) in reader.lines().enumerate() {
let line = res?;
let cols = line.split(",").map(|x| x.to_string()).collect();
if i == 0 {
headers = cols;
} else {
rows.push(cols);
}
}
Ok(Csv { headers, rows })
}fn get(&self, row: usize, cols: &str) -> Option<&str> {
let idx = self.headers.iter().position(|x| x == cols)?;
self.rows.get(row)?.get(idx).map(|s| s.as_str())
}fn get_records(&self) -> Vec<Record> {
let mut records = Vec::new();
for row in &self.rows {
if row.len() < 3 {
continue;
}
if let Ok(age) = row[1].parse::<u32>() {
records.push(Record::new(row[0].clone(), age, row[2].clone()));
}
}
records
}#[derive(Debug)]
struct Record {
name: String,
age: u32,
city: String,
}- BufReader: Efficient line-by-line file reading
- Iterator Chaining: Combining
enumerate(),map(),collect() - Option Chaining: Using
?inOptioncontexts - Early Return Patterns: Using
?for error propagation - Data Validation: Skipping malformed rows gracefully
- String Ownership: When to use
&str,String,.clone() - Position Method: Finding index of elements in collections
#[test]
fn test_get() {
let csv = Csv::from_file("text.csv").unwrap();
assert_eq!(csv.get(0, "name").unwrap(), "Alice");
assert_eq!(csv.get(0, "city").unwrap(), "London");
assert!(csv.get(0, "invalid_col").is_none());
assert!(csv.get(99, "name").is_none());
}
#[test]
fn test_from_file() {
let csv = Csv::from_file("text.csv").unwrap();
assert!(!csv.rows.is_empty());
}- Support quoted fields with commas
- Handle escaped quotes
- Different delimiter support (tabs, pipes)
- Generic record type with macros
- Streaming API for large files
- Write CSV functionality
- Better error types with
thiserror - Iterator-based API instead of collecting to Vec
- Column type inference
Status: ✅ Completed | Difficulty: Intermediate