Polars के साथ रस्ट डेटा प्रोसेसिंग
- रस्ट को क्या अलग बनाता है
- वेरिएबल और म्यूटेबिलिटी
- बुनियादी डेटा प्रकार
- स्ट्रिंग्स: दो प्रकार
- फंक्शन्स
- कंट्रोल फ्लो
- ओनरशिप (स्वामित्व): मुख्य विचार
- स्ट्रक्ट्स: कस्टम डेटा प्रकार
- एनम्स और पैटर्न मैचिंग
- Option और Result: कोई नल्स नहीं, कोई साइलेंट एरर नहीं
- ? ऑपरेटर: एरर हैंडलिंग शॉर्टहैंड
- सामान्य कलेक्शंस
- Cargo: रस्ट का बिल्ड टूल और पैकेज मैनेजर
- ध्यान देने योग्य बातें:
- Polars क्या है
- दो मुख्य प्रकार
- सब कुछ एक Result रिटर्न करता है
- डेटा पढ़ना और लिखना
- स्कीमा: अनुबंध
- चयन और फ़िल्टरिंग
- जॉइन्स: टेबल्स को जोड़ना
- ईगर बनाम लेज़ी: बड़ा अंतर
- सामान्य ऑपरेशंस चीट शीट
- Polars क्यों (pandas / Spark के मुकाबले)
- यह रस्ट बेसिक्स से कैसे जुड़ता है
- डेमो
- एक जानबूझकर अपूर्ण CSV
- अनुमानित स्कीमा के साथ ईगर रीड (आसान, खतरनाक रास्ता)
- स्पष्ट स्कीमा (विश्वसनीयता का पाठ)
- यह रस्ट + Polars का बिंदु है
- प्रथम-श्रेणी चिंता के रूप में एरर हैंडलिंग
- एक टेस्ट के साथ इसे लॉक करें
- जानबूझकर नल्स को संभालना (गलती से नहीं)
- सेक्शन टेकअवे
रस्ट को क्या अलग बनाता है
- कंपाइल और तेज़ — नेटिव मशीन कोड में कंपाइल होता है, कोई रनटाइम/GC नहीं
- मेमोरी सुरक्षित — कंपाइलर आपके प्रोग्राम के चलने से पहले ही बग्स की पूरी श्रेणियों (नल पॉइंटर एरर, डेटा रेस) को रोकता है
- मजबूत, स्टेटिकली टाइप्ड — हर वैल्यू का एक टाइप होता है जो कंपाइल समय पर ज्ञात होता है; कंपाइलर बेमेल को जल्दी पकड़ लेता है
वेरिएबल और म्यूटेबिलिटी
वेरिएबल डिफ़ॉल्ट रूप से इम्यूटेबल (अपरिवर्तनीय) होते हैं। आप mut के साथ म्यूटेबिलिटी चुनते हैं।
let x = 5; // इम्यूटेबल -- दोबारा असाइन नहीं किया जा सकता
let mut y = 10; // म्यूटेबल
y = 20; // `mut` की वजह से ठीक है
// x = 6; // कंपाइल एरर: `x` को दो बार असाइन नहीं किया जा सकता
const MAX: u32 = 100_000; // कांस्टेंट: हमेशा इम्यूटेबल, टाइप ज़रूरी है
यह डिफ़ॉल्ट सामान्य अपेक्षा को पलट देता है: आप पहले ही बता देते हैं कि क्या बदलने की अनुमति है, जिससे कोड को समझना आसान हो जाता है।
बुनियादी डेटा प्रकार
स्केलर प्रकार
- पूर्णांक (Integers):
i32,i64,u32,u64… (i = साइन्ड, u = अनसाइन्ड; नंबर = बिट्स)।i32डिफ़ॉल्ट है। - फ्लोट्स:
f64(डिफ़ॉल्ट),f32 - बूलियन:
bool->true/false - कैरेक्टर:
char-> एक एकल यूनिकोड कैरेक्टर, सिंगल कोट्स में
let count: i64 = 42;
let price: f64 = 19.99;
let is_ready: bool = true;
let letter: char = 'A';कंपाउंड प्रकार
- टपल (Tuple): मिश्रित प्रकारों का निश्चित आकार का समूह
- ऐरे (Array): निश्चित आकार, सभी एक ही प्रकार के
let person: (i32, f64, char) = (30, 5.9, 'M');
let height = person.1; // इंडेक्स द्वारा एक्सेस -> 5.9
let nums: [i32; 3] = [1, 2, 3]; // 3 i32s की ऐरे
let first = nums[0]; // -> 1
स्ट्रिंग्स: दो प्रकार
&str— एक "स्ट्रिंग स्लाइस", आमतौर पर एक निश्चित/उधार लिया गया स्ट्रिंग लिटरलString— एक ओन्ड (स्वामित्व वाली), बढ़ने योग्य स्ट्रिंग जिसे आप संशोधित कर सकते हैं
let literal: &str = "hello"; // निश्चित टेक्स्ट
let mut owned: String = String::from("hello");
owned.push_str(", world"); // बढ़ सकती है क्योंकि यह ओन्ड है
फंक्शन्स
fnके साथ घोषित- पैरामीटर प्रकार अनिवार्य हैं; रिटर्न टाइप
->के बाद आता है - अंतिम एक्सप्रेशन (बिना सेमीकोलन के) रिटर्न वैल्यू है
fn add(a: i32, b: i32) -> i32 {
a + b // कोई सेमीकोलन नहीं = यह रिटर्न वैल्यू है
}
fn greet(name: &str) { // कोई `->` नहीं का मतलब है कि यह कुछ भी रिटर्न नहीं करता
println!("Hello, {name}!");
}
fn main() {
let sum = add(2, 3); // हर प्रोग्राम main() से शुरू होता है
println!("Sum: {sum}");
greet("Aziz");
}नोट: println! एक मैक्रो है (इसे ! से पहचाना जा सकता है), फंक्शन नहीं।
कंट्रोल फ्लो
if / else (यह एक एक्सप्रेशन है!)
let n = 7;
if n % 2 == 0 {
println!("even");
} else {
println!("odd");
}
// क्योंकि `if` एक वैल्यू रिटर्न करता है, आप इसके साथ असाइन कर सकते हैं:
let label = if n > 5 { "big" } else { "small" };लूप्स
// loop: जब तक आप `break` न करें, हमेशा चलता रहता है
let mut i = 0;
loop {
if i >= 3 { break; }
i += 1;
}
// while
let mut c = 3;
while c > 0 {
println!("{c}");
c -= 1;
}
// for: सबसे सामान्य -- रेंज या कलेक्शन पर इटरेट करें
for k in 0..3 { // 0, 1, 2 (अंत-अनन्य)
println!("k = {k}");
}ओनरशिप (स्वामित्व): मुख्य विचार
रस्ट की मुख्य विशेषता। तीन नियम:
- प्रत्येक वैल्यू का एक ओनर (मालिक) होता है
- एक समय में केवल एक ही ओनर होता है
- जब ओनर स्कोप से बाहर जाता है, तो वैल्यू साफ़ कर दी जाती है
let s1 = String::from("hi");
let s2 = s1; // ओनरशिप s2 में चली जाती है (MOVES)
// println!("{s1}"); // एरर: s1 अब मान्य नहीं है
// किसी अन्य फंक्शन को ओनरशिप लिए बिना वैल्यू का उपयोग करने देने के लिए,
// आप इसे & (रेफरेंस) के साथ *उधार (borrow)* लेते हैं:
fn length(s: &String) -> usize {
s.len() // s को पढ़ता है, इसका मालिक नहीं है
}
let word = String::from("rust");
let n = length(&word); // इसे उधार दें; `word` बाद में भी उपयोग योग्य है
यही वह चीज़ है जो रस्ट को बिना गारबेज कलेक्टर के मेमोरी सुरक्षा की गारंटी देने देती है। यह वह हिस्सा है जिसे समझने में सबसे अधिक समय लगता है।
स्ट्रक्ट्स: कस्टम डेटा प्रकार
struct Order {
id: i64,
amount: f64,
shipped: bool,
}
let o = Order { id: 1, amount: 42.5, shipped: true };
println!("Order {} costs {}", o.id, o.amount);एनम्स और पैटर्न मैचिंग
एनम्स एक वैल्यू को कई वेरिएंट्स में से एक होने देते हैं; match प्रत्येक को संभालता है।
enum Status {
Pending,
Shipped,
Cancelled,
}
let s = Status::Shipped;
match s {
Status::Pending => println!("waiting"),
Status::Shipped => println!("on the way"),
Status::Cancelled => println!("nope"),
}match का संपूर्ण (exhaustive) होना ज़रूरी है — हर केस को संभालें वरना कोड कंपाइल नहीं होगा। यह एक और तरीका है जिससे कंपाइलर आपको चीज़ें भूलने से रोकता है।
Option और Result: कोई नल्स नहीं, कोई साइलेंट एरर नहीं
रस्ट में null नहीं होता। इसके बजाय:
Option— एक वैल्यू जो या तोSome(x)है याNoneResult— या तोOk(x)याErr(e)(यह Polars उदाहरणों में सभी एरर हैंडलिंग का आधार है)
fn divide(a: f64, b: f64) -> Option<f64> {
if b == 0.0 { None } else { Some(a / b) }
}
match divide(10.0, 2.0) {
Some(result) => println!("Got {result}"),
None => println!("Can't divide by zero"),
}? ऑपरेटर: एरर हैंडलिंग शॉर्टहैंड
Result पर, ? का मतलब है
"मुझे वैल्यू दें, या इस फंक्शन से एरर रिटर्न करें।"
use std::num::ParseIntError;
fn parse_and_double(text: &str) -> Result<i32, ParseIntError> {
let n = text.parse::<i32>()?; // यदि पार्स विफल होता है, तो Err रिटर्न करें
Ok(n * 2) // अन्यथा जारी रखें
}यही कारण है कि read_orders(...)? स्पष्ट रूप से पढ़ता है: ? चुपचाप किसी भी विफलता को आगे बढ़ा देता है, बजाय इसके कि एक बड़ा मैच ब्लॉक मजबूर किया जाए।
सामान्य कलेक्शंस
Vec— बढ़ने योग्य सूची (पायथन सूची की तरह)HashMap— की/वैल्यू मैप (पायथन डिक्ट की तरह)
let mut v: Vec<i32> = Vec::new();
v.push(1);
v.push(2);
for item in &v { println!("{item}"); }
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert("alice", 10);
scores.insert("bob", 7);Cargo: रस्ट का बिल्ड टूल और पैकेज मैनेजर
ज़रूरी चीज़ें:
cargo new my_project # नया प्रोजेक्ट बनाएं
cargo build # कंपाइल करें
cargo run # कंपाइल + रन करें
cargo test # टेस्ट चलाएं
cargo add polars # Cargo.toml में डिपेंडेंसी जोड़ेंडिपेंडेंसीज़ ("crates" कहलाती हैं) Cargo.toml में घोषित की जाती हैं और crates.io से ली जाती हैं।
ध्यान देने योग्य बातें:
- ओनरशिप / उधार लेना —
&औरmutका नृत्य। शुरुआत में इससे लड़ने की उम्मीद रखें; अभ्यास के साथ यह समझ में आ जाता है। - दो स्ट्रिंग प्रकार (
Stringबनाम&str) —.to_string()याString::from(...)के साथ कन्वर्ट करें। - डिफ़ॉल्ट रूप से इम्यूटेबल —
mutको भूल जाना सबसे आम शुरुआती एरर है। - कंपाइलर आपका मित्र है — रस्ट के एरर मैसेज असामान्य रूप से अच्छे हैं। उन्हें पढ़ें; वे अक्सर आपको सटीक समाधान बताते हैं।
- मैक्रोज़ बनाम फंक्शन्स —
println!,vec!,df!के अंत में!होता है और ये सामान्य फंक्शन्स से थोड़ा अलग व्यवहार करते हैं।
Polars क्या है
- टैबुलर डेटा (रो और कॉलम) के साथ काम करने के लिए एक डेटाफ्रेम लाइब्रेरी — कोड में स्प्रेडशीट या डेटाबेस टेबल के बारे में सोचें
- रस्ट में लिखा गया, Apache Arrow (एक कॉलम-आधारित मेमोरी फॉर्मेट) पर निर्मित
- कॉलम-आधारित: डेटा को रो के बजाय कॉलम के अनुसार स्टोर करता है — यही कारण है कि कॉलम ऑपरेशन और एनालिटिक्स तेज़ होते हैं
- डिफ़ॉल्ट रूप से मल्टीथ्रेडेड: आपके पूछे बिना आपके सभी CPU कोर का उपयोग करता है
- रस्ट से सीधे, और बाइंडिंग के माध्यम से पायथन से उपलब्ध
दो मुख्य प्रकार
Series— डेटा का एक एकल कॉलम, सभी एक ही प्रकार केDataFrame— Series का एक संग्रह; टेबल स्वयं
use polars::prelude::*;
// एक Series एक नामित कॉलम है।
let s = Series::new("amount".into(), &[42.5, 17.0, 9.99]);
// एक DataFrame कॉलम से बनाया जाता है। df! मैक्रो आसान तरीका है।
let df = df!(
"order_id" => &[1, 2, 3],
"amount" => &[42.5, 17.0, 9.99],
)?;
println!("{df}");नोट df! के अंत में ! है — यह एक मैक्रो है, जैसे println! और =vec!=।
सब कुछ एक Result रिटर्न करता है
लगभग हर Polars ऑपरेशन विफल हो सकता है (खराब प्रकार, गायब कॉलम, खराब फाइलें), इसलिए यह PolarsResult रिटर्न करता है। इसीलिए आप वर्कशॉप में हर जगह ? देखते हैं — यह एरर्स को चुपचाप गुजरने देने के बजाय आगे बढ़ाता है।
fn build() -> PolarsResult<DataFrame> {
let df = df!("a" => &[1, 2, 3])?; // ? अनरैप करता है या एरर रिटर्न करता है
Ok(df)
}यह सीधे रस्ट के Result और ? से जुड़ता है: खराब डेटा एक ऐसी एरर बन जाता है जिसे आपको संभालना होगा, न कि एक साइलेंट =NaN=।
डेटा पढ़ना और लिखना
एजेंडा से चार फॉर्मेट:
// CSV इन
let df = CsvReadOptions::default()
.with_has_header(true)
.try_into_reader_with_file_path(Some("orders.csv".into()))?
.finish()?;
// Parquet आउट
let mut file = std::fs::File::create("orders.parquet")?;
ParquetWriter::new(&mut file).finish(&mut df)?;
// Parquet इन
let mut f = std::fs::File::open("orders.parquet")?;
let df = ParquetReader::new(&mut f).finish()?;मुख्य विचार: Parquet फाइल के अंदर स्कीमा और प्रकार स्टोर करता है, इसलिए इसे वापस पढ़ने के लिए किसी अनुमान की आवश्यकता नहीं होती। CSV टेक्स्ट है और इसे अनुमानित किया जाना चाहिए या एक स्पष्ट स्कीमा दिया जाना चाहिए।
स्कीमा: अनुबंध
एक Schema प्रत्येक कॉलम का नाम और प्रकार पहले ही घोषित करता है। किसी रीडर को एक दें और खराब डेटा कॉलम को दूषित करने के बजाय ज़ोर से विफल हो जाएगा।
let mut schema = Schema::default();
schema.with_column("order_id".into(), DataType::Int64);
schema.with_column("amount".into(), DataType::Float64);सामान्य DataType=s: =Int64, Float64, String, Boolean, =Date=।
चयन और फ़िल्टरिंग
आप एक्सप्रेशंस के साथ ऑपरेशंस का वर्णन करते हैं — col(...) एक कॉलम को संदर्भित करता है, और आप ट्रांसफॉर्मेशन को चेन करते हैं।
let result = df
.clone()
.lazy()
.filter(col("status").eq(lit("shipped"))) // मैचिंग रो रखें
.select([col("order_id"), col("amount")]) // कॉलम चुनें
.collect()?; // इसे चलाएं
col("x")— कॉलम x को संदर्भित करेंlit("shipped")— तुलना करने के लिए एक लिटरल वैल्यू.eq,.gt,.lt— एक्सप्रेशंस पर तुलना ऑपरेटर
जॉइन्स: टेबल्स को जोड़ना
एक साझा की (key) पर दो डेटाफ्रेम से रो का मिलान करें।
let joined = orders.join(
&customers,
["customer_id"], // बाएं टेबल में की
["customer_id"], // दाएं टेबल में की
JoinArgs::new(JoinType::Inner), // Inner / Left / Anti / ...
None,
)?;जानने योग्य जॉइन प्रकार:
Inner— केवल वे रो जो दोनों में मैच करती हैंLeft— सभी बाएं रो, जहां कोई मैच नहीं वहां नलAnti— बिना मैच वाले बाएं रो (डेटा-गुणवत्ता जांच के रूप में बढ़िया)
ईगर बनाम लेज़ी: बड़ा अंतर
- ईगर (Eager) — प्रत्येक ऑपरेशन तुरंत चलता है (
DataFrame)। सरल, छोटे डेटा और अन्वेषण के लिए अच्छा। - लेज़ी (Lazy) — आप एक क्वेरी प्लान बनाते हैं, और
.collect()तक कुछ भी नहीं चलता है। Polars फिर पूरे प्लान को ऑप्टिमाइज़ करता है (फिल्टर को नीचे धकेलना, केवल आवश्यक कॉलम पढ़ना)।
// लेज़ी: scan_* और .lazy() एक LazyFrame रिटर्न करते हैं -- एक प्लान, अभी डेटा नहीं।
let plan = LazyCsvReader::new(PlPath::new("orders.csv"))
.with_has_header(true)
.finish()?
.filter(col("status").eq(lit("shipped")))
.select([col("order_id"), col("amount")]);
println!("{}", plan.clone().explain(true)?); // प्लान का निरीक्षण करें
let df = plan.collect()?; // अब यह चलता है
explain(true) ऑप्टिमाइज़्ड प्लान को प्रिंट करता है — आप देख सकते हैं कि इंजन ने कोई भी कंप्यूट खर्च करने से पहले क्या करने का निर्णय लिया।
सामान्य ऑपरेशंस चीट शीट
df.height(); // रो की संख्या
df.width(); // कॉलम की संख्या
df.column("amount")?; // एक कॉलम प्राप्त करें (Series)
df.head(Some(5)); // पहली 5 रो
df.get_column_names(); // कॉलम के नाम
df.column("amount")?.dtype();// कॉलम का डेटा प्रकार
Polars क्यों (pandas / Spark के मुकाबले)
- pandas के मुकाबले — बहुत तेज़, मल्टीथ्रेडेड, लेज़ी ऑप्टिमाइज़ेशन, बेहतर मेमोरी व्यवहार; प्रकार सख्त हैं (कम साइलेंट सरप्राइज़)
- Spark के मुकाबले — सिंगल-मशीन वर्कलोड के लिए किसी क्लस्टर की आवश्यकता नहीं; कई "हमें Spark की ज़रूरत है" नौकरियां वास्तव में "pandas एक बॉक्स पर बहुत धीमा था" होती हैं
- Polars आपको वितरित-सिस्टम ओवरहेड के बिना प्रदर्शन और शुद्धता देता है
यह रस्ट बेसिक्स से कैसे जुड़ता है
PolarsResultऔर?= रस्ट काResult+?ऑपरेटर- जॉइन में
&customers= उधार लेना (ओनरशिप लिए बिना पढ़ना) - Parquet लिखते समय
&mut df= एक म्यूटेबल उधार df!,col!स्टाइल मैक्रोज़ =!मैक्रो सिंटैक्स- स्कीमा और
DataType= रस्ट का "सब कुछ का एक ज्ञात प्रकार है" विचार, टेबल कॉलम पर लागू
डेमो
एक जानबूझकर अपूर्ण CSV
मिश्रित-प्रकार के कॉलम, एक नल, और एक खराब रो वाली फाइल का उपयोग करें:
order_id,customer_id,amount,status
1,100,42.50,shipped
2,101,,pending
3,102,17.00,shipped
4,bad_id,9.99,shippedरो 4 में एक गैर-संख्यात्मक customer_id है। एक ढीले पाइपलाइन में यह एक साइलेंट NaN या ऑब्जेक्ट कॉलम बन जाता है। हम चाहते हैं कि यह ज़ोरदार हो।
अनुमानित स्कीमा के साथ ईगर रीड (आसान, खतरनाक रास्ता)
use polars::prelude::*;
fn main() -> PolarsResult<()> {
let df = CsvReadOptions::default()
.with_has_header(true)
.try_into_reader_with_file_path(Some("orders.csv".into()))?
.finish()?;
println!("{df}");
Ok(())
}यह काम करता है — लेकिन अनुमान ने एक नमूना देखा और प्रकारों का अनुमान लगाया। एक अलग फाइल पर, या अधिक रो पर, अनुमान बदल सकता है। अनुमान सुविधाजनक और गैर-निर्धारित है; वह संयोजन ही है जो प्रोडक्शन में आपको परेशान करता है।
स्पष्ट स्कीमा (विश्वसनीयता का पाठ)
अनुमान लगाना बंद करें। अनुबंध बताएं:
use polars::prelude::*;
use std::sync::Arc;
fn read_orders(path: &str) -> PolarsResult<DataFrame> {
let mut schema = Schema::default();
schema.with_column("order_id".into(), DataType::Int64);
schema.with_column("customer_id".into(), DataType::Int64);
schema.with_column("amount".into(), DataType::Float64);
schema.with_column("status".into(), DataType::String);
CsvReadOptions::default()
.with_has_header(true)
.with_schema(Some(Arc::new(schema)))
.try_into_reader_with_file_path(Some(path.into()))?
.finish()
}अब customer_id को Int64 घोषित किया गया है। खराब रो (bad_id) अब टेक्स्ट के रूप में नहीं निकल सकती — Polars एक Err रिटर्न करता है, न कि चुपचाप दूषित कॉलम। विफलता रीड समय पर होती है, स्पष्ट कारण के साथ, न कि तीन ट्रांसफॉर्मेशन बाद।
यह रस्ट + Polars का बिंदु है
- स्कीमा कोड है — यह किसी भी अन्य अनुबंध की तरह वर्शन्ड, रिव्यूड और टेस्टेड है
finish()PolarsResultरिटर्न करता है। पार्स विफलता को गलती से अनदेखा करने का कोई तरीका नहीं है —?आपको इसे संभालने या आगे बढ़ाने के लिए मजबूर करता है- एक डायनामिकली टाइप्ड पाइपलाइन से तुलना करें जहां एक खराब पार्स
NaNबन जाता है और चुपचाप आगे बढ़ता है। यहां, टाइप सिस्टम और एरर टाइप चुप्पी को असंभव बना देते हैं।
प्रथम-श्रेणी चिंता के रूप में एरर हैंडलिंग
दोनों व्यवहार दिखाएं ताकि दर्शक अंतर महसूस कर सकें:
fn main() {
match read_orders("orders.csv") {
Ok(df) => println!("Loaded {} rows\n{df}", df.height()),
Err(e) => eprintln!("CSV failed its contract: {e}"),
}
}एक पाइपलाइन में, Err का मतलब है कि काम यहां रुक जाता है, ज़ोर से, एक मैसेज के साथ — सुबह 3 बजे नहीं, चालीस मिलियन रो अंदर।
एक टेस्ट के साथ इसे लॉक करें
विश्वसनीयता विषय को ठोस बनाया गया — एक टेस्ट जो अनुबंध का दावा करता है, ताकि एक खराब अपस्ट्रीम फाइल CI में विफल हो जाए, न कि प्रोडक्शन में:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn schema_is_enforced() {
let df = read_orders("tests/data/orders_good.csv").unwrap();
assert_eq!(df.height(), 3);
assert_eq!(
df.column("amount").unwrap().dtype(),
&DataType::Float64
);
}
#[test]
fn bad_types_are_rejected() {
// `bad_id` वाली फाइल चुपचाप लोड नहीं होनी चाहिए।
assert!(read_orders("tests/data/orders_bad.csv").is_err());
}
}bad_types_are_rejected एक टेस्ट में पूरा दर्शन है: हम दावा करते हैं कि खराब डेटा विफल हो जाता है। अधिकांश पाइपलाइन वह टेस्ट कभी नहीं लिखती हैं क्योंकि उनके स्टैक में, खराब डेटा विफल नहीं होता — यह फैलता है।
जानबूझकर नल्स को संभालना (गलती से नहीं)
रो 2 पर खाली amount एक वास्तविक नल है। यह तय करें कि इसका क्या मतलब है, बजाय इसके कि अनुमान को तय करने दें:
use polars::prelude::*;
fn parse_options() -> CsvParseOptions {
CsvParseOptions::default()
.with_null_values(Some(NullValues::AllColumns(
vec!["".into(), "NA".into(), "null".into()].into(),
)))
}परिचालन स्पष्टता: नल्स कोड में एक प्रलेखित निर्णय हैं, न कि पार्सर जो करना चाहता था उसका एक आर्टिफैक्ट।
सेक्शन टेकअवे
- CSV डिफ़ॉल्ट रूप से अनटाइप्ड और असुरक्षित है — हर रीड को एक सीमा के रूप में मानें जिसे वैलिडेट किया जाना चाहिए
- स्पष्ट स्कीमा "आशा है कि यह पार्स हो जाएगा" को "यह पार्स होता है या एरर देता है" में बदल देते हैं — सुविधा पर निर्धारण
PolarsResultविफलता को अनदेखा करना कंपाइल-समय पर असंभव बना देता है- एक टेस्ट (
bad_types_are_rejected) पूरे विश्वसनीयता थीसिस को प्रदर्शित करता है - रस्ट + Polars यहां मायने रखता है क्योंकि यह तेज़ है, बल्कि इसलिए क्योंकि यह साइलेंट डेटा भ्रष्टाचार को संरचनात्मक रूप से कठिन बनाता है

