در این دوره Rust را از اولین برنامه تا مالکیت، قرضگیری، trait و همروندی یاد میگیرید. هر برنامه را در مرورگر مینویسید و همان لحظه ساخته، اجرا و بررسی میشود.
fn main() {
let scores = [("Sara", 18.5), ("Reza", 12.0), ("Mina", 19.75), ("Ali", 9.5), ("Nima", 15.25)];
// زنجیرهٔ iterator: نه حلقهٔ دستی لازم است و نه متغیر شمارنده
let passed: Vec<_> = scores.iter().filter(|(_, s)| *s >= 10.0).collect();
let average = scores.iter().map(|(_, s)| s).sum::<f64>() / scores.len() as f64;
let best = scores.iter().max_by(|a, b| a.1.total_cmp(&b.1)).unwrap();
for (name, score) in &scores {
let bar = "#".repeat((*score / 2.0) as usize);
println!("{:<5} {:>6.2} {}", name, score, bar);
}
println!("passed: {} of {}", passed.len(), scores.len());
println!("average: {:.2}", average);
println!("best: {} ({})", best.0, best.1);
}
Sara 18.50 ######### Reza 12.00 ###### Mina 19.75 ######### Ali 9.50 #### Nima 15.25 ####### passed: 4 of 5 average: 15.00 best: Mina (19.75)
نمرههای یک کلاس با زنجیرهای از iterator خلاصه میشوند: قبولشدهها، میانگین و بهترین نمره. کنار هر نام نواری به اندازهٔ نمرهاش چاپ میشود. نمونههای دیگر
دوره به ترتیب «کتاب Rust» پیش میرود و روی بخشهایی که Rust را خاص میکنند بیشتر میماند.
با fn main و println! برنامه مینویسید، از ورودی میخوانید و با let، mut، shadowing، نوعهای عددی و سرریز کار میکنید.
برنامه را با if، loop، while و for هدایت میکنید و تابعهایی مینویسید که آخرین عبارتشان مقدار بازگشتی است.
قاعدههای مالکیت، move و clone را یاد میگیرید، با &T و &mut T قرض میدهید و پیامهای borrow checker را میخوانید.
داده را با struct و impl مدل میکنید و با enum، match جامع، if let و الگوها هر حالت را پوشش میدهید.
نبودن مقدار را با Option و خطا را با Result و عملگر ? مدیریت میکنید و نوع خطای سفارشی و Box<dyn Error> میسازید.
با Vec، HashMap، BTreeMap، HashSet، VecDeque و BinaryHeap کار میکنید و فرق String و &str و فرق بایت و کاراکتر را میفهمید.
trait تعریف و پیاده میکنید، تابع جنریک با کران مینویسید، از trait object استفاده میکنید و طولعمرها را میفهمید.
با closure و زنجیرهای از map، filter، zip و fold داده را بدون حلقهٔ دستی پردازش میکنید.
با Box، Rc و RefCell، ماژولها و macro_rules! کار میکنید و با thread، کانال mpsc و Arc<Mutex> کار را موازی میکنید.
کد را به سبک رایج Rust بازنویسی میکنید و ابزارهایی مثل رمز سزار، ارزیاب عبارت، پارسر JSON و یک ماشین مجازی پشتهای میسازید.
این پنج برنامه را برای همین صفحه نوشتهایم. هر کدام با همان کامپایلر درسها ساخته و اجرا شده و خروجی کنارش همان چیزی است که چاپ میکند.
نمرههای یک کلاس با زنجیرهای از iterator خلاصه میشوند: قبولشدهها، میانگین و بهترین نمره. کنار هر نام نواری به اندازهٔ نمرهاش چاپ میشود.
fn main() {
let scores = [("Sara", 18.5), ("Reza", 12.0), ("Mina", 19.75), ("Ali", 9.5), ("Nima", 15.25)];
// زنجیرهٔ iterator: نه حلقهٔ دستی لازم است و نه متغیر شمارنده
let passed: Vec<_> = scores.iter().filter(|(_, s)| *s >= 10.0).collect();
let average = scores.iter().map(|(_, s)| s).sum::<f64>() / scores.len() as f64;
let best = scores.iter().max_by(|a, b| a.1.total_cmp(&b.1)).unwrap();
for (name, score) in &scores {
let bar = "#".repeat((*score / 2.0) as usize);
println!("{:<5} {:>6.2} {}", name, score, bar);
}
println!("passed: {} of {}", passed.len(), scores.len());
println!("average: {:.2}", average);
println!("best: {} ({})", best.0, best.1);
}
Sara 18.50 ######### Reza 12.00 ###### Mina 19.75 ######### Ali 9.50 #### Nima 15.25 ####### passed: 4 of 5 average: 15.00 best: Mina (19.75)
یک تابع فهرست قیمتها را فقط میخواند، یکی آن را تغییر میدهد و یکی صاحبش میشود. خط کامنتشدهٔ آخر را کامپایلر نمیپذیرد، چون مقدار پیشتر به تابع دیگری منتقل شده است.
// هر مقدار یک صاحب دارد. با & فقط قرض میدهید و با &mut اجازهٔ تغییر هم میدهید
fn total(prices: &[u32]) -> u32 {
prices.iter().sum() // فقط میخواند؛ مالکیت نزد صدازننده میماند
}
fn add_tax(prices: &mut Vec<u32>) {
for p in prices.iter_mut() {
*p += *p / 10; // ده درصد مالیات، روی همان داده
}
}
fn archive(prices: Vec<u32>) -> String {
// این تابع صاحب prices میشود و در پایانش حافظهٔ آن آزاد میشود
format!("archived {} prices, max {}", prices.len(), prices.iter().max().unwrap())
}
fn main() {
let mut prices = vec![1000, 2500, 4000];
println!("total before tax: {}", total(&prices));
add_tax(&mut prices);
println!("after tax: {:?}", prices);
let backup = prices.clone(); // یک کپی مستقل
println!("{}", archive(prices)); // مالکیت prices به تابع منتقل شد
println!("backup: {:?}", backup);
// println!("{:?}", prices);
// کامپایلر خط بالا را نمیپذیرد: prices منتقل شده و دیگر در دسترس نیست
}
total before tax: 7500 after tax: [1100, 2750, 4400] archived 3 prices, max 4400 backup: [1100, 2750, 4400]
هر خط ورودی به یک فرمان تبدیل میشود یا خطایی خوانا میدهد. match همهٔ حالتها را پوشش میدهد و برداشت بیش از موجودی را رد میکند.
use std::io::{self, BufRead};
// هر فرمان یکی از این سه حالت است و هر حالت دادهٔ خودش را دارد
enum Command {
Deposit(u32),
Withdraw(u32),
Balance,
}
fn parse(line: &str) -> Result<Command, String> {
let parts: Vec<&str> = line.split_whitespace().collect();
match parts.as_slice() {
["deposit", n] => n.parse().map(Command::Deposit).map_err(|_| format!("bad amount: {}", n)),
["withdraw", n] => n.parse().map(Command::Withdraw).map_err(|_| format!("bad amount: {}", n)),
["balance"] => Ok(Command::Balance),
_ => Err(format!("unknown command: {}", line)),
}
}
fn main() {
let mut balance: u32 = 0;
for line in io::stdin().lock().lines() {
let line = line.unwrap();
// match باید همهٔ حالتها را پوشش دهد، وگرنه کامپایل نمیشود
match parse(&line) {
Ok(Command::Deposit(n)) => balance += n,
Ok(Command::Withdraw(n)) if n > balance => println!("refused: balance is {}", balance),
Ok(Command::Withdraw(n)) => balance -= n,
Ok(Command::Balance) => println!("balance: {}", balance),
Err(e) => println!("error: {}", e),
}
}
}
deposit 500 withdraw 120 balance withdraw 900 deposit 5o refund 30 balance
balance: 380 refused: balance is 380 error: bad amount: 5o error: unknown command: refund 30 balance: 380
دایره و مستطیل هر دو trait به نام Shape را پیاده میکنند. برنامه آنها را در یک فهرست نگه میدارد، بر اساس مساحت مرتب میکند و جمع مساحتها را چاپ میکند.
use std::fmt;
// trait یک قرارداد است: هر نوعی که آن را پیاده کند، مساحت دارد و چاپ میشود
trait Shape: fmt::Display {
fn area(&self) -> f64;
}
struct Circle { r: f64 }
struct Rect { w: f64, h: f64 }
impl Shape for Circle {
fn area(&self) -> f64 { std::f64::consts::PI * self.r * self.r }
}
impl Shape for Rect {
fn area(&self) -> f64 { self.w * self.h }
}
impl fmt::Display for Circle {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "circle r={}", self.r) }
}
impl fmt::Display for Rect {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "rect {}x{}", self.w, self.h) }
}
fn main() {
// نوعهای مختلف در یک فهرست، با Box<dyn Shape>
let mut shapes: Vec<Box<dyn Shape>> = vec![
Box::new(Rect { w: 4.0, h: 2.5 }),
Box::new(Circle { r: 1.5 }),
Box::new(Rect { w: 3.0, h: 3.0 }),
Box::new(Circle { r: 0.5 }),
];
shapes.sort_by(|a, b| b.area().total_cmp(&a.area()));
for s in &shapes {
println!("{:<12} {:>6.2}", s.to_string(), s.area());
}
let sum: f64 = shapes.iter().map(|s| s.area()).sum();
println!("{:<12} {:>6.2}", "total", sum);
}
rect 4x2.5 10.00 rect 3x3 9.00 circle r=1.5 7.07 circle r=0.5 0.79 total 26.85
چهار thread همزمان بازهٔ ۱ تا ۱ میلیون را بررسی میکنند تا عددی را پیدا کنند که طولانیترین دنبالهٔ کولاتز را دارد. نتیجهها از کانال میرسند و مرتبشده چاپ میشوند.
use std::sync::mpsc;
use std::thread;
// طول دنبالهٔ کولاتز: عدد زوج نصف میشود و عدد فرد سه برابر بهعلاوهٔ یک
fn steps(mut n: u64) -> u32 {
let mut count = 0;
while n != 1 {
n = if n % 2 == 0 { n / 2 } else { 3 * n + 1 };
count += 1;
}
count
}
fn main() {
let (tx, rx) = mpsc::channel();
for part in 0..4u64 {
let tx = tx.clone();
// هر thread یک بازه را بررسی میکند و نتیجه را از کانال میفرستد
thread::spawn(move || {
let range = part * 250_000 + 1..=(part + 1) * 250_000;
let best = range.map(|n| (steps(n), n)).max().unwrap();
tx.send((part, best)).unwrap();
});
}
drop(tx);
// نتیجهها به هر ترتیبی برسند، مرتبشده چاپ میشوند
let mut results: Vec<_> = rx.iter().collect();
results.sort();
for (part, (len, n)) in &results {
println!("part {}: longest chain starts at {:>6} ({} steps)", part + 1, n, len);
}
let (len, n) = results.iter().map(|r| r.1).max().unwrap();
println!("winner: {} with {} steps", n, len);
}
part 1: longest chain starts at 230631 (442 steps) part 2: longest chain starts at 410011 (448 steps) part 3: longest chain starts at 626331 (508 steps) part 4: longest chain starts at 837799 (524 steps) winner: 837799 with 524 steps
هر درس یک قاعده را با مثال توضیح میدهد و یک برنامهٔ کامل از شما میخواهد.
با دکمهٔ اجرا، کد شما با کامپایلر Rust ساخته میشود. اگر قاعدهای از مالکیت یا نوعها رعایت نشده باشد، پیام کامپایلر را همانجا میبینید.
دکمهٔ بررسی، برنامه را با چند ورودی اجرا و خروجی را با جواب درست مقایسه میکند. اگر گیر کردید، راهنمای همان تمرین کمکتان میکند.
| موضوع | Rust | C و C++ |
|---|---|---|
| مدیریت حافظه | مالکیت و borrow checker، در زمان کامپایل | دستی، یا با اشارهگرهای هوشمند |
| اشارهگر خالی | null ندارد؛ نبودن مقدار با Option بیان میشود. | استفاده از اشارهگر null معمولاً برنامه را در اجرا از کار میاندازد. |
| خطاها | Result و عملگر ? | کد بازگشتی یا استثنا |
| رقابت داده بین threadها | کامپایلر کد ناامن را نمیپذیرد. | باید خودتان با دقت جلوی آن را بگیرید. |
| سرعت اجرا | کامپایل به کد ماشین، بدون جمعآوری زباله | کامپایل به کد ماشین، بدون جمعآوری زباله |
| شروع یادگیری | قاعدههای مالکیت در ابتدا وقت میگیرد. | شروع سادهتر است، اما خطاهای حافظه دیرتر پیدا میشوند. |
این مقایسه دربارهٔ کد امن Rust است، یعنی همهٔ کدی که در این دوره مینویسید.
۲۴ سرفصل، ۱۵۶ درس، ۱۱۰ تمرین
درسهای اول رایگان است و برای شروع پرداختی لازم نیست. هر وقت خواستید ادامه دهید، همین دوره را بخرید یا با اشتراک پرو همهٔ دورهها را باز کنید.
۱۲ درس ابتدایی رایگان است. برای ادامه، اشتراک بگیرید یا دوره را بخرید.
بدون نیاز به پرداخت مبلغی برای شروع
اگر سرعت همین زبانها را با بررسی حافظه در زمان کامپایل میخواهید.
اگر میخواهید بدانید حافظه واقعاً چطور مدیریت میشود و برنامههای سریعتری بنویسید.
اگر به برنامهنویسی سیستمی، کامپایلر یا سیستمعامل علاقه دارید.
پیشنیاز: یک رایانه و یک مرورگر.
اولین برنامهٔ Rust خود را همین امروز در مرورگر بسازید. شروع رایگان است و در کمتر از دو دقیقه وارد محیط یادگیری میشوید.