العمل مع CSV في Rust: المكتبات، أمثلة الكود وأفضل الممارسات
CSV (قيم مفصولة بفواصل) — أحد أكثر التنسيقات شيوعاً لتخزين البيانات الجدولية. في نظام Rust البيئي، توجد مكتبة قوية ومرنة csv، والتي بالاشتراك مع crate serde تتيح قراءة وكتابة وتحويل بيانات CSV بسهولة. في هذه المقالة، سنشرح بالتفصيل كيفية العمل مع CSV في Rust، بدءاً من التحليل الأساسي وصولاً إلى تسلسل الهياكل المعقدة.
تثبيت وإضافة التبعيات
للبدء، أضف التبعيات التالية إلى ملف Cargo.toml:
[dependencies]csv = "1.3"serde = { version = "1.0", features = ["derive"] }توفر مكتبة csv واجهة برمجة تطبيقات منخفضة المستوى للقراءة والكتابة، بينما توفر serde إلغاء تسلسل تلقائي لصفوف CSV إلى هياكل Rust.
قراءة ملف CSV سطراً بسطر
أبسط طريقة لقراءة CSV هي استخدام Reader من crate csv. لنلق نظرة على مثال مع ملف users.csv:
use csv::Reader;use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> { let mut rdr = Reader::from_path("users.csv")?;
for result in rdr.records() { let record = result?; println!("{:?}", record); } Ok(())}يقوم هذا الكود بطباعة كل سطر على شكل متجه من السلاسل النصية StringRecord. ولكن عملياً، غالباً ما يكون العمل مع البيانات المُنمطة أكثر فائدة.
إلغاء التسلسل إلى هياكل باستخدام serde
لنعلن عن هيكل يتوافق مع أعمدة CSV:
use serde::Deserialize;
#[derive(Debug, Deserialize)]struct User { name: String, age: u32, city: String,}الآن يمكننا قراءة السجلات مباشرة إلى كائنات User:
use csv::Reader;use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> { let mut rdr = Reader::from_path("users.csv")?;
for result in rdr.deserialize() { let user: User = result?; println!("{} ({} سنة) من {}", user.name, user.age, user.city); } Ok(())}تقوم المكتبة تلقائياً بمطابقة رؤوس CSV مع حقول الهيكل. إذا كان ترتيب أو أسماء الأعمدة مختلفة، استخدم الخاصية #[serde(rename = "...")].
كتابة ملف CSV
يتم إنشاء ملف CSV باستخدام Writer. كتابة البيانات المنظمة عبر serde:
use csv::Writer;use serde::Serialize;use std::error::Error;
#[derive(Debug, Serialize)]struct Product { id: u32, name: String, price: f64,}
fn main() -> Result<(), Box<dyn Error>> { let mut wtr = Writer::from_path("products.csv")?;
let products = vec![ Product { id: 1, name: "حاسوب محمول".into(), price: 75000.0 }, Product { id: 2, name: "فأرة".into(), price: 1500.0 }, ];
for product in &products { wtr.serialize(product)?; }
wtr.flush()?; println!("تم كتابة ملف products.csv بنجاح"); Ok(())}لاحظ: يقوم Writer تلقائياً بإضافة الرؤوس بناءً على أسماء حقول الهيكل.
تخصيص الفواصل وعلامات الاقتباس
افتراضياً، يتم استخدام الفاصلة وعلامات الاقتباس المزدوجة. إذا كان ملفك يستخدم الفاصلة المنقوطة (مثلاً في الإعدادات المحلية الأوروبية)، قم بتخصيص ReaderBuilder:
use csv::ReaderBuilder;
let mut rdr = ReaderBuilder::new() .delimiter(b';') .has_headers(true) .from_path("data.tsv")?;وبالمثل، يتم تخصيص WriterBuilder للكتابة.