Rust: شرائح Rust: طرق عرض البيانات الفعالة وشرائح السلاسل

آخر تحديث: 2026-08-26

تُعد «الشريحة» نوعًا خاصًا من المراجع في لغة Rust — فهي لا تشير إلى مجموعة البيانات بأكملها، بل إلى جزء متجاور منها.

تتيح لك تقنية «التقطيع» «استخراج» جزء من البيانات بأمان لمعالجته دون الحاجة إلى نسخه.


1. ما ستتعلمه



2. الرسوم التخطيطية المفاهيمية

100%
flowchart LR
    subgraph "Heap memory"
        HEAP["'H','e','l','l','o','','R','u','s','t','!'"]
    end
    subgraph "String s"
        S_PTR["ptr"] --> HEAP
        S_LEN["len: 12"]
        S_CAP["cap: 12"]
    end
    subgraph "&str slice"
        SL_PTR["ptr"] -->|"Pointing Forward5Byte"| HEAP
        SL_LEN["len: 5"]
    end


3. قصة محلل بيانات

(1) المشكلة: عملية استخراج المعلومات من السجلات بطيئة للغاية

ماريا هي محللة بيانات في إحدى شركات التجارة الإلكترونية. وهي بحاجة إلى استخراج عناوين IP من كمية هائلة من سجلات الخادم:

"أحتاج إلى طريقة لاستخراج البيانات تتيح لي 'الاطلاع عليها فقط، دون نسخها' — تمامًا مثل تمرير إصبعي على سطر من النص في إحدى الصحف دون الحاجة إلى تدوينه."

(2) حلول لشرائح الصدأ

RUST
fn main() {
    let log_line = "192.168.1.1 - - [01/Jul/2026:12:00:00] \"GET /index.html\"";

    // Slices -- do not copy, they just "point to" a portion of the original string
    let ip = &log_line[0..13];           // "192.168.1.1"
    let date = &log_line[20..38];        // "01/Jul/2026:12:00:00"
    let path = &log_line[50..62];        // "/index.html"

    println!("IP: {}", ip);
    println!("Date: {}", date);
    println!("Path: {}", path);

    // Key Points: No strings were copied! All slices point to different areas of log_line
    println!("The original logs are still available: {}", log_line);
}

الشريحة تشبه «نافذة» تُظهر منظورًا مختلفًا للبيانات الأصلية. لا يحدث أي نسخ ولا تخصيص للذاكرة — ولا توجد أي تكلفة إضافية. وهذا أمر بالغ الأهمية عند التعامل مع البيانات الضخمة.



4. مبدأ التجزئة

(1) نموذج الذاكرة

RUST
let s = String::from("Hello, Rust!");
let slice = &s[0..5];  // "Hello"
100%
graph TB
    subgraph "String s"
        S_ptr[ptr ──→ H e l l o ,   R u s t !]
        S_len[len: 12]
        S_cap[cap: 12]
    end
    subgraph "&str slice"
        SL_ptr[ptr ──→ H e l l o]
        SL_len[len: 5]
    end
    S_ptr -.-> heap[Load the data]
    SL_ptr -.-> heap
ميزة شريحة String بالكامل شريحة &s[0..5]
الذاكرة ptr + len + cap (3 بايت) ptr + len (2 بايت)
الملكية مملوكة مستعارة (مُشار إليها)
هل يتم نسخ البيانات لا تنسخ
نطاق الوصول السلسلة بأكملها "Hello" (5 بايت)

(2) قواعد بناء الجملة في «Scope»

الصيغة المعنى مثال
[0..5] من 0 إلى 5 (باستثناء 5) "Hello"
[..5] من البداية حتى 5 "Hello"
[5..] من 5 حتى النهاية ", Rust!"
[..] السلسلة بأكملها "Hello, Rust!"

(3) مرجع سريع لأنواع الشرائح

نوع الشريحة الصيغة الحجم الوصف
شريحة سلسلة &str 16 بايت ptr + len (مؤشر FAT)
تقطيع المصفوفات &[T] 16 بايت ptr + len (مؤشر fat)
مرجع المصفوفة &[T; N] 8 بايت مؤشر فقط (الطول معروف وقت التحويل البرمجي)
شريحة متغيرة &mut [T] 16 بايت مؤشر + الطول (عناصر قابلة للتعديل)


5. أمثلة على التقطيع

(1) ▶ المثال:تقطيع السلاسل (مستوى الصعوبة ⭐)

RUST
// ============================================
// Basic Usage of String Slicing
// ============================================

fn main() {
    let s = String::from("Hello, Rust World!");

    // Various Slicing Methods
    let hello = &s[..5];           // "Hello"
    let rust = &s[7..11];          // "Rust"
    let world = &s[12..];          // "World!"
    let full = &s[..];             // All

    println!("hello: '{}'", hello);
    println!("rust: '{}'", rust);
    println!("world: '{}'", world);
    println!("full: '{}'", full);

    // String literals are, by their very nature, &str
    let literal: &str = "Create a slice directly";
    let first_word = &literal[..2];
    println!("Literal Slicing: '{}'", first_word);
}

الناتج:

TEXT 📖 للعرض فقط
hello: 'Hello'
rust: 'Rust'
world: 'World!'
full: 'Hello, Rust World!'
Literal Slicing: 'Directly'

القيمة الثابتة للسلسلة (مثل "hello") هي في حد ذاتها من النوع &str — أي شريحة تشير إلى ملف ثنائي. ولا تتطلب البادئة & لأنها تمثل مرجعًا بالفعل.


(2) ▶ المثال:المزالق المتعلقة بحدود تقطيع السلاسل (الصعوبة ⭐⭐)

RUST
// ============================================
// UTF-8 Boundary: Slices must be at character boundaries.
// ============================================

fn main() {
    let s = "RustProgramming";  // Number of bytes: R(1) u(1) s(1) t(1) Bian(3) Cheng(3) = 10 bytes

    // Security Slices
    let rust = &s[..4];         // "Rust" (first 4 bytes are exactly ASCII)
    println!("Rust Part: {}", rust);

    // The following are examples of errors (uncomment to see panic!)
    // let bad = &s[0..5];       // ❌ 5 is between 4 and 6, landing in the middle of "Bian"'s first byte

    // A Safe Approach: use chars() and char_indices() to iterate
    for (i, c) in s.char_indices() {
        println!("Byte Index {}: Character '{}'", i, c);
    }

    // Use char_indices to find safe slice boundaries
    if let Some((pos, _)) = s.char_indices().nth(4) {
        let safe_slice = &s[..pos];
        println!("First 4 character slice: {}", safe_slice);
    }
}

الناتج:

TEXT 📖 للعرض فقط
Rust Part: Rust
Byte Index 0: Character 'R'
Byte Index 1: Character 'u'
Byte Index 2: Character 's'
Byte Index 3: Character 't'
Byte Index 4: Character 'Bian'
Byte Index 7: Character 'Cheng'
First 4 character slice: RustProgramming

يتم فهرسة الشرائح بالبايت، وليس بالأحرف. إذا وقع حد الشريحة في منتصف حرف متعدد البايتات، فسوف يدخل البرنامج في حالة ذعر ويتعطل. استخدم .char_indices() للحصول على فهرس آمن يحدد الحدود بالأحرف.


(3) ▶ المثال:تقطيع المصفوفات مقابل تقطيع المتجهات (مستوى الصعوبة ⭐⭐)

RUST
// ============================================
// Slicing Arrays and Vectors
// ============================================

fn main() {
    // Array Slicing
    let arr = [1, 2, 3, 4, 5];
    let slice_arr = &arr[1..4];      // [2, 3, 4]
    println!("Array Slicing: {:?}", slice_arr);

    // Vector Slicing
    let vec = vec![10, 20, 30, 40, 50];
    let slice_vec = &vec[..3];       // [10, 20, 30]
    println!("Vector Slicing(first 3): {:?}", slice_vec);

    // Edit Slice Content (requires &mut)
    let mut numbers = vec![1, 2, 3, 4, 5];
    let mut_slice = &mut numbers[1..4];  // [2, 3, 4]
    mut_slice[0] = 99;                   // Modifying a slice affects the original vector.
    println!("Modified original vector: {:?}", numbers);

    // Slice Type Size
    println!("&[i32] Occupancy: {} Byte", std::mem::size_of::<&[i32]>());
    println!("&[i32;5] Occupancy: {} Byte", std::mem::size_of::<&[i32; 5]>());
}

الناتج:

TEXT 📖 للعرض فقط
Array Slicing: [2, 3, 4]
Vector Slicing(first 3): [10, 20, 30]
Modified original vector: [1, 99, 3, 4, 5]
&[i32] Occupancy: 16 Byte
&[i32;5] Occupancy: 8 Byte

يشغل نوع الشريحة (&[i32]) 16 بايت (8 بايت للمؤشر + 8 بايت للطول)، في حين يشغل المرجع العادي (&[i32;5]) 8 بايت فقط (المؤشر فقط). وهذا ما يُعرف بـ«المؤشر الضخم» — فالشريحة هي مؤشر يتضمن معلومات عن الطول.


(4) ▶ المثال:الشرائح كمعلمات للدوال (مستوى الصعوبة ⭐⭐)

RUST
// ============================================
// &str As a function argument -- the most flexible approach
// ============================================

// ✅ Best Practices: Receive &str -- regardless of whether the input is &str or &String
fn first_word(s: &str) -> &str {
    for (i, &b) in s.as_bytes().iter().enumerate() {
        if b == b' ' {
            return &s[..i];
        }
    }
    &s[..]  // No spaces, return the entire string
}

fn main() {
    // Incoming &str Literal
    let result1 = first_word("hello world");
    println!("The first word in the literal string: '{}'", result1);

    // Incoming &String (automatically converts to &str)
    let s = String::from("Rust is awesome");
    let result2 = first_word(&s);  // &String Automatically convert to &str
    println!("String The First Word: '{}'", result2);

    // Passing an array slice
    let arr = [1, 2, 3, 4, 5];
    let sum: i32 = sum_slice(&arr[..]);  // Use [..] to convert array to slice
    println!("Array Slicing and: {}", sum);
}

fn sum_slice(slice: &[i32]) -> i32 {
    let mut total = 0;
    for x in slice {
        total += *x;
    }
    total
}

الناتج:

TEXT 📖 للعرض فقط
The first word in the literal string: 'hello'
String The First Word: 'Rust'
Array Slicing and: 15

أفضل الممارسات: عندما تقبل معلمة دالة سلسلة نصية، استخدم &str بدلاً من &String. يتم تحويل &String تلقائيًا إلى &str (عبر عملية تحويل Deref)، لذا فإن المعلمة &str أكثر مرونة — فهي تقبل كلاً من القيم الثابتة ومراجع السلاسل النصية.


(5) ▶ المثال:تمرين شامل — محلل السجلات (مستوى الصعوبة ⭐⭐⭐)

RUST
// ============================================
// Practical Guide to Slicing: Zero-Copy Log Parsing
// ============================================

fn parse_log_line(line: &str) -> (&str, &str, &str) {
    let ip_end = line.find(' ').unwrap_or(line.len());
    let ip = &line[..ip_end];

    let rest = &line[ip_end..].trim_start();
    let method_end = rest.find(' ').unwrap_or(rest.len());
    let method = &rest[..method_end];

    let path_part = &rest[method_end..].trim_start();
    let path_end = path_part.find(' ').unwrap_or(path_part.len());
    let path = &path_part[..path_end];

    (ip, method, path)
}

fn find_longest<'a>(strings: &[&'a str]) -> &'a str {
    strings.iter().max_by_key(|s| s.len()).unwrap_or(&"")
}

fn summarize(data: &[i32]) -> (f64, i32, i32) {
    if data.is_empty() {
        return (0.0, 0, 0);
    }
    let sum: i32 = data.iter().sum();
    let avg = sum as f64 / data.len() as f64;
    let min = *data.iter().min().unwrap();
    let max = *data.iter().max().unwrap();
    (avg, min, max)
}

fn main() {
    let logs = [
        "192.168.1.1 GET /index.html HTTP/1.1",
        "10.0.0.5 POST /api/login HTTP/1.1",
        "172.16.0.1 DELETE /api/user/42 HTTP/1.1",
    ];

    println!("=== Log Analysis (Zero-Copy) ===");
    for log in &logs {
        let (ip, method, path) = parse_log_line(log);
        println!("IP: {:<15} Methods: {:<6} Path: {}", ip, method, path);
    }

    println!("\n=== Longest Path ===");
    let paths: Vec<&str> = logs.iter().map(|l| {
        let (_, _, p) = parse_log_line(l);
        p
    }).collect();
    println!("Longest Path: '{}'", find_longest(&paths));

    println!("\n=== Statistics on Numerical Slices ===");
    let scores = [85, 92, 78, 95, 88, 70, 96];
    let (avg, min, max) = summarize(&scores);
    println!("Grade Slices: {:?}", scores);
    println!("Average: {:.1}, Lowest: {}, Highest: {}", avg, min, max);
}

الناتج:

TEXT 📖 للعرض فقط
=== Log Analysis (Zero-Copy) ===
IP: 192.168.1.1     Methods: GET    Path: /index.html
IP: 10.0.0.5        Methods: POST   Path: /api/login
IP: 172.16.0.1      Methods: DELETE Path: /api/user/42

=== Longest Path ===
Longest Path: '/api/user/42'

=== Statistics on Numerical Slices ===
Grade Slices: [85, 92, 78, 95, 88, 70, 96]
Average: 86.3, Lowest: 70, Highest: 96

أثناء تحليل السجلات، تشير القيم الثلاث &str التي تُرجعها parse_log_line جميعها إلى مناطق مختلفة من السلسلة الأصلية — دون نسخ، ودون تخصيص. تقبل find_longest شريحة من &[&str]، مما يجعلها متعددة الاستخدامات للغاية. كما أن الدوال الإحصائية الخاصة بالشرائح الرقمية لا تقوم سوى باستعارة البيانات.



❓ أسئلة شائعة

س ما الفرق بين الشريحة (slice) والإشارة (reference)؟
ج الشريحة هي مؤشر موسع (مؤشر + طول)، في حين أن الإشارة العادية هي مجرد مؤشر. &str يخزن معلومتين في الذاكرة: «المكان الذي يشير إليه» و«طوله»، بينما &String يخزن «المكان الذي يشير إليه» فقط. تتيح لك الشريحة معرفة حدود البيانات، مما يمنع الوصول خارج النطاق.
س ما هي العلاقة بين شريحة السلسلة &str وسلسلة String؟
ج يمكن اعتبار &str بمثابة عرض جزئي لسلسلة String. تمتلك سلسلة String البيانات، بينما تستعير &str جزءًا منها. يمكنك تصور سلسلة String ككتاب كامل، و&str كصفحة في ذلك الكتاب.
س لماذا يتسبب &s[0..5] في حدوث خطأ فادح عند استخدامه مع الأحرف الصينية؟
ج لأن الشرائح يتم فهرستها بالبايتات، ويشغل الحرف الصيني الواحد في ترميز UTF-8 3 بايتات. &s[0..4] في سلسلة ASCII، سيكون هذا بالضبط «hello»، ولكن في السلسلة «RustBianCheng»، يكون «Rust» (4 بايت). &s[0..5] يقع على البايت الأوسط للحرف «Bian»، ولا يستطيع المُترجم تحديد ما إذا كان هذا حرفًا صالحًا، لذا يحدث خطأ فادح.
س هل يجب عليّ استخدام &str أم &String كمعلمة؟
ج استخدم &str دائمًا. والسبب في ذلك هو أن &String يتم تحويله تلقائيًا إلى &str (عن طريق إزالة الإشارة)، لكن العكس ليس صحيحًا. فالمعلمة التي تستخدم &str يمكنها قبول كل من القيم الثابتة من نوع السلسلة والمراجع من نوع String — مما يجعلها أكثر مرونة.
س هل يؤدي تقطيع vec![1,2,3] باستخدام &vec[..] إلى إنشاء بيانات جديدة؟
ج لا، فهذا لا يتطلب أي جهد إضافي. فالتقطيع يكتفي بإنشاء مؤشر جديد إلى بيانات المتجه الأصلي مع معلومات النطاق. ولا يؤدي إلى تخصيص ذاكرة أو نسخ البيانات.

📖 ملخص


📝 تمارين

  1. الصعوبة ⭐: أنشئ String، واستخدم [..] لتقسيم السلسلة بأكملها، وتأكد من أن نوع الجزء المقسوم هو &str.
  2. الصعوبة ⭐⭐: اكتب دالة fn last_word(s: &str) -> &str تُرجع الكلمة الأخيرة (مفصولة بمسافات). استدعِها على كل من سلسلة نصية ثابتة ودالة String.
  3. الصعوبة ⭐⭐⭐: أنشئ سلسلة تحتوي على أحرف صينية ورموز تعبيرية "Rust🦀BianCheng"، واستخدم .char_indices() لتحديد حدود الشريحة الآمنة، واستخرج الجزء «Rust🦀» فقط (تلميح: الرمز 🦀 يشغل 4 بايت).
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%