مقدمة: لماذا لا يسمح Rust بتحويل السلسلة النصية إلى رقم ببساطة؟
إذا كنت قادماً من Python أو JavaScript، فأنت معتاد على السحر مثل int("42") أو parseInt("42"). في Rust، لن تنجح هذه الخدعة — سيطلب منك المترجم تحديد النوع الذي تريد تحويل السلسلة النصية إليه بالضبط. لماذا؟ لأن Rust هي لغة لا تغفر الإهمال. إنها تجبرك على التفكير في الحالات الحدودية: ماذا لو كانت السلسلة النصية تحتوي على شيء غير رقمي؟ ماذا لو كان الرقم كبيراً جداً بالنسبة للنوع المختار؟
في هذه المقالة، سنتناول السؤال الأكثر شيوعاً على Stack Overflow بين المبتدئين في Rust: كيفية تحويل String إلى int. ستحصل ليس فقط على كود جاهز، بل على فهم كامل لما يحدث تحت الغطاء.
طريقة parse() — الأداة الرئيسية للتحويل
الطريقة الأساسية لتحويل سلسلة نصية إلى رقم في Rust هي طريقة parse()، المتاحة للنوع String و &str. وهي مطبقة عبر trait FromStr وتعيد Result<T, E>.
مثال أساسي: String → i32
fn main() { let number_string = String::from("42"); let number: i32 = number_string.parse().unwrap(); println!("الرقم: {}", number); // الإخراج: الرقم: 42}لاحظ استخدام unwrap(). هذه طريقة سريعة "لاستخراج" القيمة من Result، لكنها خطيرة: إذا لم تكن السلسلة النصية رقماً، سيصاب البرنامج بالذعر ويسقط.
معالجة الأخطاء الصحيحة
في الكود الحقيقي، يجب ألا تستخدم unwrap() دون تفكير. إليك الطريقة الصحيحة:
use std::num::ParseIntError;
fn main() { let input = String::from("hello"); match input.parse::<i32>() { Ok(number) => println!("نجاح: {}", number), Err(e) => println!("خطأ في التحويل: {}", e), }}أو صيغة أكثر إيجازاً باستخدام if let:
fn main() { let input = String::from("2024"); if let Ok(year) = input.parse::<i32>() { println!("السنة: {}", year); } else { println!("فشل تحليل السلسلة النصية"); }}الأنواع الصحيحة المتاحة للتحويل
يقدم Rust مجموعة غنية من الأنواع الصحيحة. يمكنك تحويل السلسلة النصية إلى أي منها:
i8,i16,i32,i64,i128— أعداد صحيحة بإشارةu8,u16,u32,u64,u128— أعداد صحيحة بدون إشارةisize,usize— الحجم يعتمد على البنية
مثال على التحويل إلى أنواع مختلفة:
fn main() { let val = "255"; let a: u8 = val.parse().unwrap(); // 255 let b: i16 = val.parse().unwrap(); // 255 let c: u32 = val.parse().unwrap(); // 255 // لكن هذا سيسبب خطأ — 255 لا يسع في i8 // let d: i8 = val.parse().unwrap(); // PANIC!}تقنيات متقدمة: from_str والأنواع المخصصة
طريقة parse() تستدعي داخلياً from_str() من trait FromStr. يمكنك تطبيق هذا trait على الهياكل الخاصة بك:
use std::str::FromStr;use std::num::ParseIntError;
#[derive(Debug)]struct Version { major: u32, minor: u32,}
impl FromStr for Version { type Err = ParseIntError; fn from_str(s: &str) -> Result<Self, Self::Err> { let parts: Vec<&str> = s.split('.').collect(); if parts.len() != 2 { // ننشئ خطأنا الخاص، لكن للمثال نستخدم ParseIntError return Err("1.0".parse::<u32>().unwrap_err()); } let major = parts[0].p