برنامه نویسی

زنگ زدگی: نه فقط زوم سریع زوم

وقتی صحبت از Rust می شود، اولین چیزی که معمولاً به ذهن می رسد عملکرد چشمگیر آن است. و در حالی که Rust مطمئناً در این زمینه ارائه می‌کند، زبان بسیار بیشتر از سرعت خام است. از نحوی که به خوبی طراحی شده و انتزاعات قدرتمند آن گرفته تا مدیر بسته قوی و اکوسیستم پر جنب و جوش، Rust زبانی است که واقعاً همه آن را دارد. در این پست، نگاهی دقیق‌تر به برخی از ویژگی‌های کلیدی خواهیم داشت که Rust را به زبانی همه‌کاره و متقاعدکننده تبدیل می‌کنند.

اگرچه ویژگی‌های زبانی که در این پست مورد بحث قرار گرفته‌اند ممکن است منحصر به Rust نباشد، اما نحوه طراحی و ادغام دقیق آنهاست که Rust را متمایز می‌کند. Rust تنها زبانی است که در آن این ویژگی‌ها به‌طور یکپارچه برای ایجاد یک سیستم منسجم به هم نزدیک می‌شوند، به همین دلیل است که این زبان بسیار جذاب است.

تغییرناپذیری عملی

در Rust، متغیرها را می توان به عنوان تغییرناپذیر یا تغییرپذیر با استفاده از عبارت اعلام کرد mut کلمه کلیدی. Rust با تغییر ناپذیری متغیرها به طور پیش فرض، تغییر ناپذیری را به عنوان پیش فرض پذیرفته است. این بدان معنی است که اگر بعداً نیاز به تغییر مقدار متغیرها داشتیم، باید به صراحت متغیرها را تغییرپذیر اعلام کنیم. این رویکرد استدلال در مورد رفتار برنامه ها را آسان تر می کند و به جلوگیری از جهش های تصادفی کمک می کند.

let x = 5; // immutable
let mut y = 5; // mutable

y = 6; // ok
x = 6; // error: cannot assign twice to immutable variable `x`
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

علاوه بر متغیرهای قابل تغییر، Rust از انتقال ارجاعات قابل تغییر به توابع نیز پشتیبانی می کند. توابع باید اعلام کنند که آیا قصد جهش در آرگومان های خود را دارند یا خیر، که بیشتر به جلوگیری از جهش های تصادفی کمک می کند. این به ما این امکان را می دهد که از متغیرهای قابل تغییر به روشی کنترل شده و صریح استفاده کنیم.

fn increment(num: &mut i32) {
    *num += 1; // dereference the pointer to mutate the value
}

fn main() {
    let mut x = 10; // x is mutable
    increment(&mut x); // pass a mutable reference to x

    println!("x: {}", x); // prints "x: 11"
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

با پذیرفتن تغییر ناپذیری به عنوان پیش‌فرض و استفاده از متغیرهای قابل تغییر تنها در صورت لزوم، کد Rust قوی‌تر و قابل پیش‌بینی‌تر می‌شود.

انواع داده های جبری (ADTs) و تطبیق الگو

انواع داده های جبری (ADT) یک مفهوم اساسی در برنامه نویسی تابعی است که امکان ایجاد انواع داده های پیچیده را با ترکیب انواع ساده تر فراهم می کند. ADT ها می توانند دو نوع باشند: انواع مجموع و انواع محصول. انواع مجموع چندین نوع را در یک نوع واحد ترکیب می کنند که می تواند یکی از انواع تشکیل دهنده را در هر زمان معین نگه دارد. Rust پیاده سازی قدرتمندی از انواع Sum را در قالب enums یا انواع برشماری ارائه می دهد. از سوی دیگر، ساختارها برای نمایش انواع محصول استفاده می شوند. احتمالاً قبلاً از انواع محصولات در زبان‌های دیگر (رابط در TypeScript، کلاس‌ها در جاوا و غیره) استفاده کرده‌اید.

به عنوان مثال، برنامه ای را در نظر بگیرید که انواع اشکال را نشان می دهد. ما می توانیم از enum برای نمایش انواع مختلف اشکال استفاده کنیم:

enum Shape {
    Circle(f64),
    Rectangle(f64, f64),
    Triangle(f64, f64, f64),
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در اینجا یک enum تعریف کرده ایم Shape که سه نوع دارد: Circle که طول می کشد f64 آرگومان نشان دهنده شعاع، Rectangle که دوتا طول میکشه f64 آرگومان هایی که طول و عرض و Triangle که سه آرگومان f64 می گیرد که طول سه ضلع آن را نشان می دهد. این به ما امکان می دهد هر شکل ممکن را در یک نوع داده واحد نمایش دهیم.

اکنون می توانیم از تطبیق الگو برای تجزیه آسان و ایمن داده های یک شکل استفاده کنیم. اگر نمی دانید تطبیق الگو چیست، آن را به عنوان یک فکر کنید switch بیانیه در مورد استروئیدها این به ما امکان می دهد یک مقدار را با یک الگو مطابقت دهیم و کد را بر اساس الگوی مطابقت اجرا کنیم. من یک پست وبلاگ کامل در مورد الگوی تطبیق btw دارم.

fn area(shape: Shape) -> f64 {
    match shape {
        // pi * radius^2
        Shape::Circle(radius) => std::f64::consts::PI * radius * radius,

        // length * width
        Shape::Rectangle(length, width) => length * width,

        // Heron's formula: sqrt(s * (s - a) * (s - b) * (s - c)) where s = (a + b + c) / 2
        Shape::Triangle(side1, side2, side3) => {
            let s = (side1 + side2 + side3) / 2.0;
            (s * (s - side1) * (s - side2) * (s - side3)).sqrt()
        }
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

کامپایلر Rust همچنین تضمین می کند که تطبیق الگوی ما جامع است، به این معنی که ما باید همه موارد ممکن را مدیریت کنیم. این از فراموشی تصادفی رسیدگی به پرونده و ایجاد خطای زمان اجرا جلوگیری می کند.

تطبیق الگوی جامع

ADT ها همراه با تطبیق الگو، ایجاد و مدیریت انواع داده های پیچیده به روشی ایمن و مختصر را بی اهمیت می کند.

انتزاعات داخلی

Rust انتزاعات داخلی قدرتمندی را ارائه می دهد که به نوشتن کد صحیح و ایمن به راحتی کمک می کند. دو مورد از مهم ترین انتزاعات در Rust هستند Option و Result. این انواع برای کار با سیستم‌های کنترل خطا و ایمنی تهی Rust ضروری هستند که به جلوگیری از اشکالات و بهبود قابلیت اطمینان برنامه‌های Rust کمک می‌کنند. در این بخش به بررسی می پردازیم Option و Result به تفصیل، و ببینید که چگونه می توان از آنها برای نوشتن کد Rust قابل اعتمادتر و بدون خطا استفاده کرد.

بدون پوچ، بدون مشکل (گزینه)

Rust مفهوم null را با the جایگزین می کند Option نوع، ارائه یک جایگزین امن تر که خطرات مرتبط با مقادیر صفر را حذف می کند. Option یک عدد است که می تواند هر دو باشد Some با یک مقدار یا None برای نشان دادن عدم وجود یک مقدار این رویکرد ایمن نوع به ما این امکان را می دهد که عدم وجود یک مقدار را بدون توسل به null مدیریت کنیم. در اینجا نحوه Option نوع در Rust تعریف شده است:

enum Option<T> {
    Some(T),
    None,
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

با استفاده از Option، ما می توانیم اطمینان حاصل کنیم که کد ما عاری از اشکالات و خطاهای مرتبط با تهی است، و استدلال در مورد رفتار برنامه را آسان تر می کند. ممکن است با این الگو از زبان های دیگر مانند Haskell آشنا باشید Maybe monad یا OCaml’s option نوع

بیایید نگاهی بیندازیم به Option در عمل تابعی را در نظر بگیرید که بردار اعداد صحیح را می گیرد و بزرگترین عدد صحیح در بردار را برمی گرداند. اگر بردار خالی باشد، می خواهیم برگردیم None. در غیر این صورت می خواهیم برگردیم Some با بزرگترین عدد صحیح در اینجا نحوه اجرای این تابع در Rust آمده است:

fn largest(numbers: Vec<i32>) -> Option<i32> {
    if numbers.is_empty() {
        return None;
    }

    let mut largest = numbers[0];

    for num in numbers {
        if num > largest {
            largest = num;
        }
    }

    Some(largest)
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اکنون می توانیم از این تابع استفاده کنیم و از تطبیق الگو برای رسیدگی به هر دو مورد استفاده کنیم:

fn main() {
    let numbers = vec![1, 2, 3];

    match largest(numbers) {
        Some(num) => println!("Largest number: {}", num),
        None => println!("No largest number"),
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

و این کار می کند!

بزرگترین تابع کار می کند

اما ما می توانیم بهتر عمل کنیم. Rust یک کتابخانه استاندارد گسترده ارائه می دهد که شامل تعدادی عملکرد مفید است. در اینجا ما می توانیم یک تکرار کننده از خود ایجاد کنیم Option و استفاده کنید max تابع برای به دست آوردن بزرگترین عدد:

fn largest(numbers: Vec<i32>) -> Option<i32> {
    numbers.into_iter().max()
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این همچنین به طور خودکار مواردی را که بردار خالی است و برمی‌گردد کنترل می‌کند None برای ما. کتابخانه استاندارد Rust مملو از عملکردهای مفیدی مانند این است.

آیا می خواهید دو برابر بزرگترین اعداد زوج را برگردانید اما فقط اگر کمتر از 100 باشد؟ مشکلی نیست!

fn largest_even_less_than_100(numbers: Vec<i32>) -> Option<i32> {
    numbers
        .into_iter() // create an iterator from the vector
        .filter(|num| num % 2 == 0) // filter out only even numbers
        .max() // get the largest number - returns an Option<i32>
        .map(|num| num * 2) // double the Some value inside the Option, leaves None unchanged
        .filter(|num| num < &100) // only return Some if the value is less than 100
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

من فکر میکنم شما میخواهید درجه بگیرید.

وقتی همه چیز طبق برنامه ریزی پیش نمی رود (نتیجه)

زنگ Result type یک انتزاع داخلی دیگر است که اغلب برای رسیدگی به خطاها در برنامه های Rust استفاده می شود. نشان دهنده موفقیت یا شکست یک عملیات است. Result یک عدد با دو نوع ممکن است – Ok و Err. Ok نشان دهنده نتیجه موفقیت آمیز یک عملیات است، در حالی که Err نشان دهنده خطایی است که در طول عملیات رخ داده است. در اینجا نحوه Result نوع در Rust تعریف شده است:

enum Result<T, E> {
    Ok(T),
    Err(E),
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ممکن است با این الگو از زبان های دیگر مانند Haskell آشنا باشید Either monad یا OCaml’s result نوع

بیایید به یک مثال از چگونگی نگاه کنیم Result می تواند برای رسیدگی به خطاها استفاده شود. فرض کنید تابعی داریم که دو عدد صحیح را به عنوان آرگومان می گیرد و نتیجه تقسیم عدد صحیح اول بر عدد دوم را برمی گرداند. اما تقسیم بر صفر مجاز نیست و منجر به خطا می شود. ما می توانیم استفاده کنیم Result برای رسیدگی به مورد خطای احتمالی تایپ کنید:

fn divide(x: i32, y: i32) -> Result<i32, &'static str> {
    if y == 0 {
        return Err("Cannot divide by zero");
    }
    Ok(x / y)
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اکنون می توانیم از این تابع استفاده کنیم و موارد موفقیت و خطا را مدیریت کنیم:

fn main() {
    match divide(10, 2) {
        Ok(result) => println!("Result: {}", result),
        Err(error) => println!("Error: {}", error),
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

خیلی ساده، درست است؟ بیایید به برخی از عملکردهایی که Rust برای کار کردن با آنها ارائه می دهد نگاه کنیم Result یک نسیم.

فرض کنید می‌خواهیم 10 را به نتیجه یک عملیات تقسیم زنجیره‌ای اضافه کنیم. می توانیم از تودرتو استفاده کنیم match اظهارات اما کمی زشت و پرمخاطب است. زنگ ما را با آن پوشانده است and_then و map کارکرد:

fn main() {
    let result = divide(10, 2)
        .and_then(|x| divide(x, 2))
        .map(|x| x + 10);

    match result {
        Ok(result) => println!("Result: {}", result),
        Err(error) => println!("Error: {}", error),
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این بسیار مختصرتر از استفاده از تودرتو است match بیانیه. کتابخانه استاندارد Rust عملکردهای بسیار بیشتری را برای کار با آنها فراهم می کند Result و Option، پس حتما آنها را بررسی کنید.

جامعه و اکوسیستم پر جنب و جوش

زنگ فراتر از یک زبان است. دارای یک اکوسیستم و جامعه پر رونق است. این انجمن توسط یک اکوسیستم قوی از کتابخانه ها، ابزارها و منابع پشتیبانی می شود که ساخت، آزمایش و استقرار برنامه های Rust را آسان می کند.

در اینجا برخی از جنبه های کلیدی اکوسیستم و جامعه Rust آورده شده است:

  • Rustup – Rustup ابزار رسمی برای نصب و مدیریت Rust است. نصب و به روز رسانی Rust و ابزارهای مرتبط با آن را آسان می کند. همچنین نصب و مدیریت چندین نسخه Rust در یک سیستم را آسان می کند.

  • بار – باربری مدیر بسته های Rust است. برای ساخت، تست و اجرای برنامه های Rust استفاده می شود. همچنین مدیریت وابستگی ها و اجتناب از جهنم وابستگی را آسان می کند. همچنین به انتشار کتابخانه ها و فایل های باینری در crates.io، رجیستری بسته رسمی Rust اجازه می دهد. همچنین می تواند بنچمارک ها را انجام دهد و حتی چندین پروژه را در یک مخزن با استفاده از فضاهای کاری مدیریت کند.

  • کتابخانه ها – Rust مجموعه بزرگ و رو به رشدی از کتابخانه ها و چارچوب های متن باز دارد که به راحتی می توانند در پروژه های شما ادغام شوند. این شامل همه چیز از کتابخانه های سیستمی سطح پایین گرفته تا چارچوب های وب سطح بالا و موتورهای بازی می شود.

  • ابزار سازی – Rust تمرکز زیادی روی ابزارهای توسعه دهنده دارد. ابزارهایی مانند Rustfmt، Clippy و Rust Analyzer که به قالب‌بندی کد، لینتینگ و تحلیل کمک می‌کنند.

  • انجمن – جامعه Rust به دلیل استقبال و حمایت از خود، با منابع بسیاری در دسترس برای کمک به کاربران جدید برای شروع کار با زبان شناخته شده است. این شامل انجمن‌های آنلاین، اتاق‌های گفتگو، و ملاقات‌ها و همچنین مجموعه رو به رشدی از کتاب‌ها و آموزش‌های Rust می‌شود. من به طور خاص عاشق سرور اختلاف جامعه Rust هستم. این یک مکان عالی برای کمک گرفتن از Rust و ملاقات با دیگر Rustaceans است.

اینها برخی از ویژگی های مورد علاقه من از Rust هستند. اگر ادامه دهم، این مقاله خیلی طولانی خواهد شد، بنابراین من برخی از منابع را برای خنکی Rust در زیر پیوند می دهم. من به شدت توصیه می کنم آنها را بررسی کنید.

برخی از منابع جالب دیگر

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا