Languages
Rust
Data Engineering with Rust: Blazing Fast and Reliable Pipelines

Data Engineering with Rust: Blazing Fast and Reliable Pipelines

Rust is rapidly gaining popularity in systems programming, and for good reason. With memory safety guarantees, zero-cost abstractions, and strong concurrency features, Rust is ideal for building scalable and high-performance data engineering pipelines.

πŸš€ Why Rust for Data Engineering?

  • Memory Safety without GC: Rust prevents null pointer dereferencing and data races at compile time.
  • High Performance: Comparable to C++, making it suitable for heavy data processing.
  • Built-in Concurrency: async/await, threads, and channels make parallelization safer and easier.
  • Ecosystem: Great crates like csv, serde, polars, rayon, tokio, and s3 simplify data pipeline development.

🧱 Project Structure

data-pipeline-rust/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ main.rs
β”‚   └── pipeline.rs
β”œβ”€β”€ data/
β”‚   └── input.csv
β”œβ”€β”€ Cargo.toml
└── tests/
    └── pipeline_test.rs

πŸ”§ Step 1: Set Up Your Environment

Cargo.toml

[package]
name = "data_pipeline"
version = "0.1.0"
edition = "2021"
 
[dependencies]
csv = "1.2"
serde = { version = "1.0", features = ["derive"] }
rayon = "1.7"
chrono = "0.4"
log = "0.4"
env_logger = "0.10"
tokio = { version = "1", features = ["full"] }
s3 = "0.30.0"
reqwest = { version = "0.11", features = ["json"] }

πŸ“₯ Step 2: Read and Write CSV

pipeline.rs

use serde::{Deserialize, Serialize};
use std::error::Error;
use std::fs::File;
use std::io::Write;
 
#[derive(Debug, Deserialize, Serialize)]
pub struct Record {
    pub id: u32,
    pub name: String,
    pub value: f32,
}
 
pub fn read_csv(path: &str) -> Result<Vec<Record>, Box<dyn Error>> {
    let mut rdr = csv::Reader::from_path(path)?;
    let mut records = Vec::new();
    for result in rdr.deserialize() {
        let record: Record = result?;
        records.push(record);
    }
    Ok(records)
}
 
pub fn write_csv(path: &str, records: &[Record]) -> Result<(), Box<dyn Error>> {
    let mut wtr = csv::Writer::from_path(path)?;
    for record in records {
        wtr.serialize(record)?;
    }
    wtr.flush()?;
    Ok(())
}

main.rs

mod pipeline;
use pipeline::{read_csv, write_csv};
use rayon::prelude::*;
use log::info;
 
fn main() -> Result<(), Box<dyn std::error::Error>> {
    env_logger::init();
    let mut records = read_csv("data/input.csv")?;
 
    records.par_iter_mut().for_each(|rec| {
        rec.value *= 1.1; // apply transformation
    });
 
    write_csv("data/output.csv", &records)?;
    info!("Pipeline complete!");
    Ok(())
}

βš™οΈ Parallel Data Processing with Rayon

The use of rayon::par_iter_mut() allows efficient parallel transformation of large datasets, improving performance without the complexity of managing threads manually.


☁️ AWS S3 Integration

Upload CSV to S3

use s3::bucket::Bucket;
use s3::creds::Credentials;
use s3::region::Region;
 
pub async fn upload_to_s3(file_path: &str) -> Result<(), Box<dyn std::error::Error>> {
    let creds = Credentials::default()?;
    let region = Region::Custom {
        region: "eu-west-1".into(),
        endpoint: "https://s3.eu-west-1.amazonaws.com".into(),
    };
 
    let bucket = Bucket::new("my-bucket", region, creds)?;
    let contents = std::fs::read(file_path)?;
    let (_, status) = bucket.put_object("output.csv", &contents).await?;
    println!("Uploaded with status: {}", status);
    Ok(())
}

πŸ§ͺ Unit Testing

pipeline_test.rs

use data_pipeline::pipeline::{read_csv, write_csv, Record};
 
#[test]
fn test_value_scaling() {
    let mut rec = Record {
        id: 1,
        name: "Test".to_string(),
        value: 100.0,
    };
    rec.value *= 1.1;
    assert_eq!(rec.value, 110.0);
}

πŸ”„ Async Data Ingestion with Tokio + Reqwest

Use tokio and reqwest to perform non-blocking HTTP downloads.

use tokio::fs::File;
use tokio::io::AsyncWriteExt;
use reqwest;
 
pub async fn fetch_data(url: &str, file_path: &str) -> Result<(), Box<dyn std::error::Error>> {
    let response = reqwest::get(url).await?.bytes().await?;
    let mut file = File::create(file_path).await?;
    file.write_all(&response).await?;
    Ok(())
}

🧠 Final Thoughts

Rust is a powerful language for building safe, fast, and reliable data engineering systems. With strong support for concurrency, a modern package ecosystem, and fine-grained control over resources, Rust provides an excellent alternative to traditional data stack languages. Use it to ingest, transform, validate, store, and deploy data efficiently at scale.

Last updated on April 20, 2025