برای شروع یک پروژه جدید در Rust، میتوانید از دستور زیر استفاده کنید:
cargo new projectName
این دستور یک دایرکتوری جدید به نام projectName
ایجاد میکند و در آن یک پروژه جدید با ساختار اولیه و فایلهای لازم (از جمله Cargo.toml
و یک فایل main.rs
) ایجاد میکند.
برای ساخت پروژه، از دستور زیر استفاده میشود:
cargo build
این دستور کد شما را کامپایل میکند و فایلهای اجرایی را در دایرکتوری target/debug
ذخیره میکند. این دایرکتوری حاوی باینریهایی است که برای توسعه و تست استفاده میشوند.
برای اجرای برنامهی Rust، میتوانید از یکی از دو روش زیر استفاده کنید:
-
اجرای مستقیم باینری:
./target/debug/projectName
این روش به شما امکان میدهد تا باینری ساخته شده را به صورت مستقیم اجرا کنید.
-
استفاده از Cargo:
cargo run
این دستور به طور خودکار کد شما را کامپایل کرده و سپس آن را اجرا میکند. این روش برای توسعهدهندگان بسیار راحتتر است، زیرا نیازی به یادآوری مسیر باینری نیست.
اگر میخواهید کد خود را بررسی کنید و مطمئن شوید که بدون خطا کامپایل میشود، میتوانید از دستور زیر استفاده کنید:
cargo check
این دستور کد شما را سریعاً بررسی میکند، اما باینری تولید نمیکند. این ویژگی برای شناسایی سریع خطاها و مشکلات در کد بسیار مفید است.
زمانی که پروژه شما آمادهی انتشار است و میخواهید آن را با بهینهسازیهای لازم کامپایل کنید، میتوانید از دستور زیر استفاده کنید:
cargo build --release
این دستور کد شما را با بهینهسازیهای خاصی کامپایل میکند و باینری تولید شده را در دایرکتوری target/release
ذخیره میکند. باینریهای تولید شده در این دایرکتوری معمولاً سریعتر و بهینهتر هستند و برای استفاده در محیطهای تولید توصیه میشوند.
اگر قصد دارید زمان اجرای کد خود را بنچمارک کنید، مهم است که از باینری موجود در target/release
استفاده کنید. این باینری بهینهسازی شده است و نتایج دقیقتری از عملکرد کد شما ارائه میدهد.
use rand::Rng; // برای تولید اعداد تصادفی
use std::cmp::Ordering; // برای مقایسه مقادیر
use std::io; // برای ورودی و خروجی
fn main() {
println!("guess the number:");
// تولید یک عدد تصادفی بین 1 تا 100
let secret_number = rand::thread_rng().gen_range(1..=100);
// چاپ عدد مخفی (برای تست)
println!("the secret number is: {secret_number}");
loop {
println!("please input your guess");
let mut guess = String::new(); // ایجاد یک رشته خالی برای ورودی کاربر
io::stdin()
.read_line(&mut guess) // خواندن ورودی کاربر
.expect("failed to read line"); // مدیریت خطا
println!("you guessed: {guess}"); // چاپ حدس کاربر
// تبدیل ورودی کاربر به عدد
let guess: u32 = match guess.trim().parse() {
Ok(num) => num, // اگر تبدیل موفق بود، عدد را ذخیره کن
Err(_) => continue, // اگر خطا بود، حلقه را ادامه بده
};
// مقایسه حدس کاربر با عدد مخفی
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"), // اگر حدس کمتر بود
Ordering::Greater => println!("Too big!"), // اگر حدس بیشتر بود
Ordering::Equal => {
println!("You win!"); // اگر حدس برابر بود
return; // پایان بازی
}
}
}
}
use rand::Rng; // برای تولید اعداد تصادفی
use std::cmp::Ordering; // برای مقایسه مقادیر
use std::io; // برای ورودی و خروجی
use rand::Rng;
: این خط ماژولRng
از کتابخانهrand
را وارد میکند که برای تولید اعداد تصادفی استفاده میشود. این ماژول توابعی را برای تولید اعداد تصادفی فراهم میکند.use std::cmp::Ordering;
: ماژولOrdering
شامل سه حالتLess
,Greater
, وEqual
است که برای مقایسه مقادیر استفاده میشود. این مقادیر به ما اجازه میدهند تا بفهمیم یک عدد کمتر، بیشتر یا برابر با عدد دیگر است.use std::io;
: ماژولio
برای کار با ورودی و خروجی در Rust استفاده میشود. ما از آن برای خواندن ورودی کاربر استفاده خواهیم کرد.
fn main() {
println!("guess the number:");
fn main()
: این تابع نقطه شروع هر برنامه Rust است. هر برنامه Rust باید یک تابعmain
داشته باشد.println!
: این ماکرو برای چاپ متن به کنسول استفاده میشود. در اینجا، به کاربر اعلام میشود که باید عددی را حدس بزند.
let secret_number = rand::thread_rng().gen_range(1..=100);
rand::thread_rng()
: این تابع یک ژنراتور تصادفی برای ترد فعلی ایجاد میکند. این ژنراتور برای تولید اعداد تصادفی استفاده میشود.gen_range(1..=100)
: این تابع یک عدد تصادفی در بازه 1 تا 100 (شامل هر دو) تولید میکند. علامت..=
نشاندهنده این است که انتهای بازه نیز شامل میشود.
println!("the secret number is: {secret_number}");
- این خط عدد مخفی را چاپ میکند. این کار برای تست مفید است، اما در یک بازی واقعی باید این خط را حذف کرد تا کاربر نتواند عدد مخفی را ببیند.
loop {
println!("please input your guess");
loop { ... }
: این یک حلقه بینهایت است که به کاربر اجازه میدهد تا حدسهای خود را وارد کند. حلقه تا زمانی که کاربر برنده نشود یا برنامه به صورت دستی متوقف نشود، ادامه خواهد داشت.
let mut guess = String::new(); // ایجاد یک رشته خالی برای ورودی کاربر
io::stdin()
.read_line(&mut guess) // خواندن ورودی کاربر
.expect("failed to read line"); // مدیریت خطا
let mut guess = String::new();
: یک رشته خالی به نامguess
ایجاد میشود.mut
به این معنی است که این متغیر قابل تغییر است.io::stdin().read_line(&mut guess)
: این خط ورودی کاربر را از کنسول میخواند و آن را در متغیرguess
ذخیره میکند.&mut guess
به این معناست که ما به تابع اجازه میدهیم تا محتوای این متغیر را تغییر دهد..expect("failed to read line")
: این متد در صورت بروز خطا، یک پیام خطا چاپ میکند و برنامه را متوقف میکند. این کار برای اطمینان از این است که ورودی به درستی خوانده شده است.
println!("you guessed: {guess}"); // چاپ حدس کاربر
- این خط حدس کاربر را چاپ میکند. این کار برای تأیید به کاربر است که ورودی او به درستی خوانده شده است.
let guess: u32 = match guess.trim().parse() {
Ok(num) => num, // اگر تبدیل موفق بود، عدد را ذخیره کن
Err(_) => continue, // اگر خطا بود، حلقه را ادامه بده
};
guess.trim()
: این متد فضای خالی را از ابتدا و انتهای رشته حذف میکند. این کار برای جلوگیری از خطا در تبدیل رشته به عدد ضروری است..parse()
: این متد سعی میکند رشته را به نوع عددی تبدیل کند. در اینجا ما ازu32
(عدد صحیح غیر منفی) استفاده میکنیم.match
: این ساختار برای بررسی نتیجهیparse()
استفاده میشود:Ok(num)
: اگر تبدیل موفق باشد، عدد در متغیرnum
ذخیره میشود و به متغیرguess
نسبت داده میشود.Err(_)
: اگر تبدیل با خطا مواجه شود (به عنوان مثال، اگر کاربر چیزی غیر از عدد وارد نکند)، حلقه دوباره شروع میشود و از کاربر خواسته میشود که دوباره حدس بزند.
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"), // اگر حدس کمتر بود
Ordering::Greater => println!("Too big!"), // اگر حدس بیشتر بود
Ordering::Equal => {
println!("You win!"); // اگر حدس برابر بود
return; // پایان بازی
}
}
guess.cmp(&secret_number)
: این متد مقایسهای بینguess
وsecret_number
انجام میدهد و نتیجه را به یکی از مقادیرOrdering
(کمتر، بیشتر، برابر) برمیگرداند.match
: با توجه به نتیجه مقایسه، یکی از سه حالت زیر اجرا میشود:Ordering::Less
: اگر حدس کاربر کمتر از عدد مخفی باشد، پیام "Too small!" چاپ میشود.Ordering::Greater
: اگر حدس کاربر بیشتر از عدد مخفی باشد، پیام "Too big!" چاپ میشود.Ordering::Equal
: اگر حدس برابر با عدد مخفی باشد، پیام "You win!" چاپ میشود و با استفاده ازreturn
از تابعmain
خارج میشود، که به معنای پایان بازی است.
- اگر کاربر حدس صحیح را بزند، برنامه خاتمه مییابد. در غیر این صورت، حلقه ادامه مییابد و از کاربر خواسته میشود که دوباره حدس بزند.
- مدیریت خطا: در این برنامه، از
expect
برای مدیریت خطاها استفاده شده است. این روش ساده است، اما در برنامههای بزرگتر، ممکن است بخواهید از مدیریت خطای پیچیدهتری استفاده کنید تا به کاربر اطلاعات بیشتری بدهید. - توسعه و بهبود: این بازی میتواند با افزودن ویژگیهای جدید مانند شمارش تعداد حدسها، ارائه گزینههای دوباره بازی، یا ذخیرهسازی رکوردها بهبود یابد.
- استفاده از ماژولها: اگر برنامه بزرگتر شود، میتوانید از ماژولها برای سازماندهی کد خود استفاده کنید. Rust به شما اجازه میدهد تا کد خود را به ماژولهای مختلف تقسیم کنید که به مدیریت بهتر پروژه کمک میکند.
let mut x: i32 = 5; // متغیر قابل تغییر
println!("x value is : {x}");
x = 6;
println!("x value is : {x}");
-
متغیرها: در Rust، متغیرها به طور پیشفرض غیرقابل تغییر (immutable) هستند. این به این معنی است که پس از تعریف، نمیتوانید مقدار آن را تغییر دهید. برای تعریف یک متغیر قابل تغییر، از کلمه کلیدی
mut
استفاده میکنیم. -
تعریف نوع: در اینجا،
x
به عنوان یک عدد صحیح 32 بیتی (i32
) تعریف شده است. Rust از نوع دادههای استاتیک استفاده میکند، به این معنی که نوع هر متغیر در زمان کامپایل مشخص میشود. این ویژگی به بهبود کارایی و ایمنی برنامه کمک میکند. -
چاپ مقادیر: از ماکرو
println!
برای چاپ مقادیر استفاده میشود. با استفاده از{x}
، مقدار متغیرx
در خروجی چاپ میشود. -
تغییر مقدار: مقدار
x
از 5 به 6 تغییر میکند و این تغییر در خروجی نشان داده میشود. این قابلیت تغییر مقدار متغیرهای قابل تغییر، یکی از ویژگیهای مهم Rust است.
const ONE_DAY_IN_SEC: u64 = 24 * 60 * 60;
println!("one day in second: {ONE_DAY_IN_SEC}");
-
تعریف ثابت: با استفاده از کلمه کلیدی
const
، میتوان یک مقدار ثابت تعریف کرد که نمیتواند تغییر کند. ثابتها باید در زمان کامپایل مشخص شوند و در کل برنامه قابل دسترسی هستند. -
نوع داده: در اینجا،
ONE_DAY_IN_SEC
به عنوان یک عدد صحیح بدون علامت 64 بیتی (u64
) تعریف شده است. این ثابت تعداد ثانیههای یک روز را محاسبه میکند (24 ساعت × 60 دقیقه × 60 ثانیه). -
چاپ مقدار ثابت: مقدار ثابت با استفاده از
println!
چاپ میشود. این به شما امکان میدهد تا مقادیر ثابت را به راحتی در برنامههای خود استفاده کنید.
let y = 5;
let y = y + 1;
{
let y = y * 2;
println!("y value is: {y}");
}
println!("y value is : {y}");
-
سایهزنی: در Rust، میتوانید یک متغیر با همان نام دوباره تعریف کنید. این به شما اجازه میدهد تا در یک بلوک خاص، متغیر جدیدی با نام مشابه تعریف کنید که بر روی متغیر قبلی سایه میزند.
-
مقداردهی اولیه:
y
ابتدا به 5 مقداردهی میشود. سپس، در خط بعدی،y
به 6 تغییر میکند. -
بلوک داخلی: در این بلوک، یک
y
جدید تعریف میشود که بر رویy
قبلی سایه میزند. اینy
جدید مقدار 12 را دارد (6 × 2). فقط در این بلوک، اینy
معتبر است و در خط بعدی،y
اصلی (6) چاپ میشود. این ویژگی به شما این امکان را میدهد که متغیرها را در محدودههای مختلف مدیریت کنید.
let age = "18";
let age: u8 = age.parse().expect("type is not casting to string");
println!("age is : {age}");
-
تعریف رشته:
age
به عنوان یک رشته (string) تعریف شده است که مقدار"18"
را دارد. در Rust، رشتهها به صورت پیشفرض از نوع&str
هستند که یک رشته قابل تغییر نیست. -
تبدیل نوع: با استفاده از متد
parse
، رشته به نوعu8
(عدد صحیح 8 بیتی) تبدیل میشود. این متد به صورت عمومی برای تبدیل رشتهها به انواع مختلف دادهها استفاده میشود. اگر تبدیل موفقیتآمیز نباشد، ازexpect
برای مدیریت خطا استفاده میشود. این به شما این امکان را میدهد که در صورت بروز خطا، پیام خطای مناسبی دریافت کنید. -
چاپ مقدار: مقدار نهایی
age
که اکنون از نوعu8
است، چاپ میشود. این روش به شما کمک میکند تا ورودیهای کاربر را به نوع مناسب تبدیل کنید.
let _tup: (&str, u8, bool) = (&"mohammadreza", 2, false);
let names = ["mohammadreza", "reza"];
let ages: [u8; 2] = [29, 10];
let role = ["test"; 10];
-
تاپل:
tup
یک تاپل است که شامل سه نوع مختلف داده است: یک رشته (&str
)، یک عدد صحیح 8 بیتی (u8
)، و یک مقدار بولی (bool
).- تاپلها میتوانند مقادیر مختلفی از انواع مختلف را در خود نگه دارند. تاپلها به شما این امکان را میدهند که چندین مقدار را به عنوان یک واحد مدیریت کنید.
-
آرایهها:
names
یک آرایه از رشتهها است که شامل دو نام است. آرایهها در Rust باید دارای اندازه ثابت باشند.ages
یک آرایه از نوعu8
است که شامل دو سن است.role
یک آرایه است که 10 بار مقدار"test"
را تکرار میکند. این نوع تعریف آرایه به شما این امکان را میدهد که یک آرایه با اندازه ثابت و مقادیر تکراری ایجاد کنید.
let a = [1, 2, 3, 4, 5];
println!("please enter array index : ");
let mut index = String::new();
io::stdin()
.read_line(&mut index)
.expect("failed to read line");
let index: usize = index.trim().parse().expect("index entered is not a number");
let element = a[index];
println!("the index value is {element}");
-
تعریف آرایه:
a
یک آرایه از اعداد صحیح است که شامل 5 عدد (1 تا 5) است. آرایهها در Rust دارای اندازه ثابت هستند و میتوانند مقادیر مشابه یا متفاوتی را نگه دارند. -
ورودی از کاربر:
- با استفاده از
println!
از کاربر خواسته میشود که ایندکس آرایه را وارد کند. - یک رشته خالی به نام
index
تعریف میشود و با استفاده ازio::stdin().read_line()
ورودی کاربر خوانده میشود. این ورودی بهindex
اضافه میشود. expect
برای مدیریت خطا در صورت عدم موفقیت در خواندن ورودی استفاده میشود. این به شما کمک میکند تا در صورت بروز خطا، پیام خطای مناسبی دریافت کنید.
- با استفاده از
-
تبدیل ورودی: ورودی کاربر که به صورت رشته است، با استفاده از
trim()
وparse()
به نوعusize
تبدیل میشود. این نوع برای ایندکسها مناسب است و به شما این امکان را میدهد که به عناصر آرایه دسترسی پیدا کنید. -
دسترسی به عنصر آرایه: عنصر مربوط به ایندکس وارد شده از آرایه
a
خوانده میشود. این بخش از کد میتواند منجر به خطا شود اگر کاربر ایندکس نامعتبری وارد کند (مثلاً ایندکسهای خارج از محدوده آرایه). -
چاپ مقدار: مقدار عنصر آرایه با استفاده از ایندکس چاپ میشود. این به شما امکان میدهد تا ببینید که کدام عنصر از آرایه با ایندکس وارد شده مطابقت دارد.
-
مدیریت خطا: استفاده از
expect
به شما کمک میکند تا در صورت بروز خطا، پیام خطای مناسبی دریافت کنید. این روش به شما امکان میدهد تا در زمان توسعه و دیباگ کردن، مشکلات را سریعتر شناسایی کنید. -
نوع دادههای استاتیک: Rust به خاطر نوع دادههای استاتیک و ایمنی حافظه مشهور است. این ویژگیها باعث میشوند که بسیاری از خطاهای رایج در زمان کامپایل شناسایی شوند، که این امر به کاهش خطاها در زمان اجرا کمک میکند.
-
قابلیتهای اضافی Rust: Rust دارای ویژگیهای دیگری نیز هست که میتواند به شما کمک کند، مانند:
- مدیریت حافظه بدون جمعآوری زباله (Garbage Collection): Rust از یک سیستم مالکیت (Ownership) استفاده میکند که به شما این امکان را میدهد که حافظه را به طور ایمن مدیریت کنید بدون اینکه نیاز به جمعآوری زباله داشته باشید.
- مدیریت همزمانی: Rust به شما این امکان را میدهد که برنامههای همزمان را به راحتی بنویسید و از خطاهای رایج در برنامههای همزمان جلوگیری کنید.
- ماکروها: Rust از ماکروها برای تولید کد به صورت خودکار استفاده میکند که میتواند به کاهش تکرار و سادهسازی کد کمک کند.
use std::io;
- وارد کردن ماژول: این خط ماژول
io
را از کتابخانه استاندارد Rust وارد میکند. ماژولio
شامل توابعی برای کار با ورودی و خروجی (I/O) است، مانند خواندن ورودی از کاربر.
fn sum(a: i32, b: i32) -> bool {
let sum = a + b;
println!("sum: {sum}");
return true;
}
-
تعریف تابع: تابع
sum
با دو پارامترa
وb
از نوعi32
(عدد صحیح 32 بیتی) تعریف شده است. -
محاسبه مجموع: درون تابع، مقدار مجموع
a
وb
محاسبه شده و در متغیرsum
ذخیره میشود. -
چاپ مجموع: با استفاده از ماکرو
println!
، مقدار مجموع چاپ میشود.{sum}
به مقدار متغیرsum
اشاره دارد. -
نوع بازگشتی: تابع
sum
از نوعbool
(بولی) است، اما در اینجا همیشه مقدارtrue
را باز میگرداند. در واقع، در این کد، مقدار بازگشتی چندان کاربردی ندارد.
fn main() {
println!("Hello, world!");
let mut a = String::new();
let mut b = String::new();
io::stdin().read_line(&mut a).expect("value is not number");
io::stdin().read_line(&mut b).expect("value is not number");
let a: i32 = a.trim().parse().expect("error");
let b: i32 = b.trim().parse().expect("error");
sum(a, b);
}
-
تعریف تابع
main
: این تابع نقطه شروع اجرای برنامه است. -
چاپ پیام خوشامدگویی: با استفاده از
println!
، پیام"Hello, world!"
چاپ میشود. -
تعریف متغیرها: دو متغیر
a
وb
به عنوان رشتههای خالی (String::new()
) تعریف شدهاند. این متغیرها برای ذخیره ورودی کاربر استفاده میشوند. -
خواندن ورودی از کاربر:
- با استفاده از
io::stdin().read_line(&mut a)
، برنامه از کاربر میخواهد که یک خط ورودی وارد کند و آن را در متغیرa
ذخیره میکند. expect("value is not number")
به شما این امکان را میدهد که در صورت بروز خطا در خواندن ورودی، یک پیام خطا چاپ کنید.
- با استفاده از
-
تبدیل ورودی به عدد صحیح:
- ورودی کاربر که به صورت رشته است، با استفاده از
trim()
(برای حذف فضاهای اضافی) وparse()
به نوعi32
تبدیل میشود. - اگر تبدیل موفقیتآمیز نباشد،
expect("error")
پیام خطا چاپ میکند.
- ورودی کاربر که به صورت رشته است، با استفاده از
-
فراخوانی تابع
sum
: در نهایت، تابعsum
با مقادیرa
وb
به عنوان آرگومان فراخوانی میشود.
-
مدیریت خطا: استفاده از
expect
در این کد به شما کمک میکند تا در صورت بروز خطا، پیام خطای مناسبی دریافت کنید. این روش به شما امکان میدهد تا در زمان توسعه و دیباگ کردن، مشکلات را سریعتر شناسایی کنید. -
نوع بازگشتی تابع: در این کد، تابع
sum
از نوعbool
است، اما هیچ دلیلی برای این نوع بازگشتی وجود ندارد، زیرا مقدار بازگشتی هیچ کاربردی ندارد. میتوانید نوع بازگشتی را به()
(واحد) تغییر دهید که نشاندهنده عدم وجود مقدار بازگشتی است. -
بهبود کد: اگر فقط میخواهید مجموع دو عدد را محاسبه کنید، میتوانید از نوع بازگشتی
()
استفاده کنید و مقدارtrue
را از تابع حذف کنید. این کار باعث سادهتر شدن کد میشود.
fn main() {
let grade = 1;
if grade >= 12 && grade <= 20 {
println!("pass")
} else if grade >= 7 {
println!("failed")
} else {
println!("ridi");
}
-
تعریف متغیر:
let grade = 1;
یک متغیر به نامgrade
تعریف میکند و به آن مقدار 1 اختصاص میدهد. این متغیر نشاندهنده نمره یک دانشآموز است.
-
شرطها:
-
شرط اول:
if grade >= 12 && grade <= 20
:- این شرط بررسی میکند که آیا نمره در بازه 12 تا 20 (شامل) قرار دارد. اگر این شرط برقرار باشد، پیام
"pass"
چاپ میشود. - عملگر
&&
به معنای "و" است و به این معنی است که هر دو شرط باید درست باشند. در اینجا، نمره باید هم بزرگتر یا مساوی 12 و هم کمتر یا مساوی 20 باشد.
- این شرط بررسی میکند که آیا نمره در بازه 12 تا 20 (شامل) قرار دارد. اگر این شرط برقرار باشد، پیام
-
شرط دوم:
else if grade >= 7
:- اگر نمره در بازه 7 تا 11 باشد (و نه در بازه قبلی)، پیام
"failed"
چاپ میشود. این به این معناست که نمره در حد قبولی نیست اما به اندازهای خوب است که به طور کامل مردود نشود.
- اگر نمره در بازه 7 تا 11 باشد (و نه در بازه قبلی)، پیام
-
شرط سوم:
else
:- اگر هیچیک از شرایط قبلی برقرار نباشد، یعنی نمره کمتر از 7 باشد، پیام
"ridi"
چاپ میشود. این پیام به نظر میرسد یک اصطلاح غیررسمی یا طنزآمیز باشد و ممکن است به معنای "خندهدار" یا "مسخره" باشد.
- اگر هیچیک از شرایط قبلی برقرار نباشد، یعنی نمره کمتر از 7 باشد، پیام
-
//loop
let mut count = 0;
'counter: loop {
if count == 100 {
println!("number: {count}");
break 'counter;
}
if count % 2 == 0 {
println!("number: {count}")
}
count += 1;
}
-
تعریف متغیر شمارنده:
let mut count = 0;
یک متغیر به نامcount
تعریف میکند و آن را به 0 مقداردهی میکند. این متغیر به عنوان شمارنده در حلقه استفاده میشود.
-
حلقه بیپایان:
'counter: loop { ... }
یک حلقه بیپایان ایجاد میکند. نام'counter
به این حلقه اختصاص داده شده است تا بتوانیم از آن در دستورbreak
استفاده کنیم. این نامگذاری به ما این امکان را میدهد که از چندین حلقه در یک بلوک کد استفاده کنیم و به راحتی از یک حلقه خاص خارج شویم.
-
شرط خروج از حلقه:
if count == 100
:- اگر مقدار
count
برابر با 100 شود، پیام"number: {count}"
چاپ میشود و با استفاده ازbreak 'counter
از حلقه خارج میشویم. در اینجا،{count}
مقدار شمارنده را در خروجی نمایش میدهد.
- اگر مقدار
-
چاپ اعداد زوج:
if count % 2 == 0
:- اگر
count
عددی زوج باشد (یعنی باقیمانده تقسیم بر 2 برابر با 0 باشد)، مقدار آن چاپ میشود. این به ما امکان میدهد تا تمام اعداد زوج بین 0 و 100 را مشاهده کنیم.
- اگر
-
افزایش شمارنده:
count += 1;
:- در نهایت، مقدار
count
به اندازه 1 افزایش مییابد. این خط اطمینان میدهد که حلقه به سمت پایان پیش میرود.
- در نهایت، مقدار
در کد شما به حلقه while
اشاره شده، اما در واقع کدی برای while
وجود ندارد. اما میتوانیم به صورت کلی بگوییم که در Rust میتوانیم از حلقه while
به شکل زیر استفاده کنیم:
let mut count = 0;
while count < 100 {
// انجام کارها
count += 1;
}
حلقه while
به شما این امکان را میدهد که تا زمانی که یک شرط خاص برقرار باشد، کد را تکرار کنید. در اینجا، حلقه تا زمانی که count
کمتر از 100 باشد، ادامه مییابد.
for element in (1..4).rev() {
println!("element: {element}");
}
-
حلقه
for
:- این حلقه بر روی یک رنج (range) از اعداد تکرار میکند.
(1..4)
یک رنج از 1 تا 3 (شامل 1 و غیرشامل 4) را تولید میکند. توجه داشته باشید که در Rust، رنجها شامل عدد ابتدایی و غیرشامل عدد انتهایی هستند.
- این حلقه بر روی یک رنج (range) از اعداد تکرار میکند.
-
معکوس کردن رنج:
rev()
متدی است که رنج را معکوس میکند، بنابراین حلقه از 3 به 1 تکرار میشود. در این حالت،element
به ترتیب 3، 2 و 1 خواهد بود.
-
چاپ مقادیر:
- در هر تکرار، مقدار
element
چاپ میشود. این به شما امکان میدهد تا ببینید که چه مقادیری در هر تکرار از حلقه تولید میشوند.
- در هر تکرار، مقدار
کد شما شامل چهار بخش اصلی است:
- بررسی نمره: با استفاده از شرطها، نمره یک دانشآموز را بررسی میکند و پیام مناسب را چاپ میکند.
- حلقه
loop
: یک حلقه بیپایان که شمارنده را از 0 تا 100 افزایش میدهد و اعداد زوج را چاپ میکند. - حلقه
while
: اگرچه در این کد وجود ندارد، اما میتوان از آن برای تکرار بر اساس یک شرط خاص استفاده کرد. - حلقه
for
: یک حلقه که بر روی یک رنج معکوس از 3 تا 1 تکرار میکند و مقادیر را چاپ میکند.
-
مدیریت خطا: در این کد، هیچ مدیریت خطایی وجود ندارد. در شرایط واقعی، بهتر است برای ورودیها و تبدیلها از مدیریت خطا استفاده کنید تا از بروز مشکلات جلوگیری شود.
-
عملگرهای منطقی: استفاده از عملگر
&&
برای ترکیب شرایط در شرطها به شما این امکان را میدهد که چندین شرط را به طور همزمان بررسی کنید. -
حلقههای تودرتو: اگر بخواهید، میتوانید از حلقههای تودرتو (nested loops) نیز استفاده کنید. این کار به شما این امکان را میدهد که ساختارهای پیچیدهتری از تکرار را پیادهسازی کنید.
-
بهینهسازی کد: میتوانید برخی از بخشهای کد را بهینه کنید، مانند استفاده از توابع برای جدا کردن منطق و بهبود خوانایی کد.
Rust به طور خاص به مدیریت ارجحیت بین استفاده از استک و هپ توجه دارد و این کار را از طریق سیستم مالکیت (ownership) و اعتبارسنجی زمان کامپایل (compile-time checks) انجام میدهد. در ادامه، به چگونگی مدیریت این ارجحیتها در Rust میپردازیم:
- سیستم مالکیت (Ownership) Rust دارای یک سیستم مالکیت قوی است که به شما کمک میکند تا به طور مؤثری از حافظه استفاده کنید. این سیستم شامل سه اصل اصلی است:
هر متغیر دارای یک مالک است: هر داده در Rust یک مالک (owner) دارد که مسئول مدیریت آن داده است. وقتی مالک داده از بین میرود (مثلاً وقتی از محدودهای خارج میشود)، داده به طور خودکار آزاد میشود.
تنها یک مالک میتواند داده را در یک زمان داشته باشد: این اصل به جلوگیری از شرایط رقابتی (race conditions) و مشکلات مربوط به دسترسی همزمان به دادهها کمک میکند.
انتقال مالکیت (Ownership Transfer): شما میتوانید مالکیت دادهها را به متغیرهای دیگر منتقل کنید. این کار به شما اجازه میدهد تا از هپ به طور مؤثر استفاده کنید بدون اینکه نیاز به مدیریت دستی حافظه داشته باشید.
- ارجحیت در استفاده از استک و هپ Rust به شما این امکان را میدهد که به طور خودکار تصمیم بگیرید که دادهها باید در کجا ذخیره شوند:
استفاده از استک: اگر دادهها اندازه ثابتی دارند و نیازی به زندگی طولانیمدت ندارند، Rust به طور خودکار آنها را در استک قرار میدهد. این کار به دلیل سرعت و کارایی بالای استک انجام میشود.
استفاده از هپ: اگر دادهها اندازه نامشخصی دارند یا نیاز به زندگی طولانیمدت دارند (مثلاً آرایههای دینامیک، ساختارهای پیچیده)، Rust به شما این امکان را میدهد که آنها را در هپ قرار دهید. برای این کار، معمولاً از نوعهای اشارهگر مانند Box, Rc, یا Arc استفاده میشود که به شما اجازه میدهند تا به دادههای موجود در هپ دسترسی پیدا کنید و مالکیت را مدیریت کنید.
- اعتبارسنجی زمان کامپایل Rust با استفاده از اعتبارسنجی زمان کامپایل، از بروز مشکلات مربوط به مدیریت حافظه جلوگیری میکند. این اعتبارسنجی به شما این امکان را میدهد که قبل از اجرای برنامه، خطاهای مربوط به مالکیت و دسترسی به حافظه را شناسایی کنید. این کار باعث میشود که برنامههای Rust معمولاً ایمنتر و پایدارتر از برنامههای نوشته شده در زبانهای دیگر باشند که مدیریت حافظه را به عهده برنامهنویس میگذارند.
-
ساختار و مدیریت:
- استک به صورت یک ساختار دادهای LIFO (آخرین وارد، اولین خارج) عمل میکند. این به این معناست که آخرین مقداری که به استک اضافه میشود، اولین مقداری است که حذف میشود.
- مدیریت حافظه در استک به صورت خودکار انجام میشود. وقتی یک تابع فراخوانی میشود، فضای لازم برای متغیرهای محلی و پارامترها به طور خودکار در بالای استک قرار میگیرد و وقتی تابع به پایان میرسد، این فضا به طور خودکار آزاد میشود.
-
سرعت:
- افزودن و حذف دادهها از استک بسیار سریع است، زیرا مکان دادهها همیشه در بالای استک قرار دارد و نیازی به جستجو ندارد.
- به همین دلیل، استفاده از استک برای دادههایی که اندازه آنها در زمان کامپایل مشخص است (مانند اعداد صحیح، آرایههای با اندازه ثابت و ...) بسیار مناسب است.
-
محدودیتها:
- تمامی دادههایی که در استک قرار میگیرند باید اندازه ثابتی داشته باشند. این بدان معناست که نمیتوانید دادههایی با اندازه نامشخص یا متغیر را در استک ذخیره کنید.
- استک دارای محدودیت اندازه است و اگر بیش از حد از آن استفاده کنید، ممکن است با خطای "Stack Overflow" مواجه شوید.
-
ساختار و مدیریت:
- هپ به صورت یک فضای بزرگ از حافظه عمل میکند که میتوانید با درخواستهای خاص، فضاهای مورد نیاز را از آن بگیرید.
- مدیریت حافظه در هپ نیاز به تخصیص و آزادسازی دستی دارد. شما باید خودتان مشخص کنید که چه زمانی فضا را اختصاص داده و چه زمانی آن را آزاد کنید. این کار معمولاً با استفاده از اشارهگرها انجام میشود.
-
سرعت:
- تخصیص فضا در هپ معمولاً کندتر از استک است، زیرا سیستم باید یک مکان خالی مناسب پیدا کند و مدیریت کند.
- دسترسی به دادهها در هپ نیز معمولاً کندتر است، زیرا باید به اشارهگر مراجعه کنید.
-
مزایا:
- هپ به شما این امکان را میدهد که دادههایی با اندازه نامشخص یا متغیر را ذخیره کنید، مانند آرایههای دینامیک یا ساختارهای پیچیده.
- همچنین میتوانید از هپ برای ذخیرهسازی دادههایی که باید در طول عمر بیشتری از برنامه باقی بمانند استفاده کنید.
- استفاده از استک: به طور کلی، اگر دادهای دارید که اندازه آن در زمان کامپایل مشخص است و نیازی به زندگی طولانیمدت ندارد، بهتر است از استک استفاده کنید. این کار به دلیل سرعت و مدیریت خودکار حافظه، بهینهتر است.
- استفاده از هپ: اگر دادهای دارید که اندازه آن نامشخص است یا نیاز به زندگی طولانیمدت دارد (مانند دادههایی که باید پس از خروج از یک تابع همچنان در دسترس باشند)، باید از هپ استفاده کنید.
مالکیت یکی از اصول کلیدی زبان Rust است که به مدیریت حافظه کمک میکند. این مفهوم به شما اجازه میدهد تا بدون نیاز به جمعآوری زباله (Garbage Collection) یا مدیریت دستی حافظه، به طور ایمن و کارآمد از حافظه استفاده کنید. در ادامه، به تفصیل به قوانین مالکیت، محدوده متغیرها، نوع دادهها، و تعاملات آنها میپردازیم.
مالکیت در Rust شامل سه قانون اصلی است:
-
هر مقدار در Rust یک مالک دارد: هر دادهای که در Rust ایجاد میشود، یک مالک (owner) دارد که مسئول مدیریت آن داده است. این مالک معمولاً یک متغیر است.
-
در هر زمان فقط یک مالک وجود دارد: این بدان معناست که نمیتوانید دو متغیر به طور همزمان مالک یک داده باشند. این ویژگی به جلوگیری از شرایط رقابتی (race conditions) و مشکلات مربوط به دسترسی همزمان به دادهها کمک میکند.
-
وقتی مالک از محدوده خارج میشود، مقدار رها میشود: هنگامی که متغیر مالک از محدوده (scope) خارج میشود، حافظه مربوط به آن به طور خودکار آزاد میشود. این کار توسط تابع خاصی به نام
drop
انجام میشود.
محدوده به محدودهای در برنامه اشاره دارد که یک متغیر معتبر است. متغیرها از زمانی که تعریف میشوند تا زمانی که از محدوده خارج شوند، معتبر هستند. به عنوان مثال:
{
let s = "hello"; // s از این نقطه معتبر است
// do something with s
} // s دیگر معتبر نیست
برای درک بهتر قوانین مالکیت، نوع دادهای به نام String معرفی میشود. این نوع داده به شما این امکان را میدهد که متونی با اندازه متغیر را مدیریت کنید، زیرا دادهها در هپ ذخیره میشوند.
شما میتوانید یک String از یک رشته ثابت (string literal) با استفاده از تابع String::from
ایجاد کنید:
let s = String::from("hello");
String به شما این امکان را میدهد که محتوای آن را تغییر دهید:
let mut s = String::from("hello");
s.push_str(", world!"); // محتوای s به "hello, world!" تغییر میکند
println!("{s}"); // خروجی: hello, world!
-
رشتههای ثابت (string literals): مقادیر آنها در زمان کامپایل مشخص است و به طور مستقیم در باینری نهایی قرار میگیرد. این رشتهها غیرقابل تغییر (immutable) هستند و در استک ذخیره میشوند.
-
نوع داده String: برای ذخیرهسازی متون متغیر، حافظهای در هپ تخصیص داده میشود. این حافظه در زمان اجرا درخواست میشود و وقتی متغیر مالک از محدوده خارج میشود، حافظه به طور خودکار آزاد میشود.
در Rust، وقتی یک متغیر به متغیر دیگری اختصاص داده میشود، مالکیت داده منتقل میشود. به عنوان مثال:
let s1 = String::from("hello");
let s2 = s1; // مالکیت s1 به s2 منتقل میشود
در اینجا، s1
دیگر معتبر نیست و اگر بخواهید از آن استفاده کنید، خطای کامپایل دریافت خواهید کرد. این به دلیل جلوگیری از مشکلات مربوط به آزادسازی دو بار حافظه (double free) است.
اگر بخواهید یک کپی عمیق (deep copy) از دادهها داشته باشید، میتوانید از متد clone
استفاده کنید:
let s1 = String::from("hello");
let s2 = s1.clone(); // یک کپی عمیق از s1 ایجاد میکند
برخی از نوعها، مانند اعداد صحیح، به دلیل اینکه اندازه آنها در زمان کامپایل مشخص است، به طور کامل در استک ذخیره میشوند و میتوانند به راحتی کپی شوند. این نوعها Copy trait را پیادهسازی میکنند و بنابراین میتوانند بدون انتقال مالکیت کپی شوند.
let x = 5;
let y = x; // x به y کپی میشود و x همچنان معتبر است
println!("x = {x}, y = {y}"); // خروجی: x = 5, y = 5
وقتی یک متغیر به یک تابع منتقل میشود، مالکیت آن متغیر منتقل میشود. این به این معناست که اگر بخواهید از متغیری که به تابع منتقل شده استفاده کنید، دیگر نمیتوانید.
fn main() {
let s = String::from("hello"); // s به محدوده میآید
takes_ownership(s); // s به تابع منتقل میشود و دیگر معتبر نیست
// println!("{s}"); // این خط خطا میدهد
let x = 5; // x به محدوده میآید
makes_copy(x); // x به تابع منتقل میشود، اما چون i32 نوعی Copy است، x هنوز معتبر است
println!("{x}"); // خروجی: 5
}
fn takes_ownership(some_string: String) {
println!("{some_string}");
} // some_string از محدوده خارج میشود و حافظه آزاد میشود
fn makes_copy(some_integer: i32) {
println!("{some_integer}");
} // some_integer از محدوده خارج میشود، اما هیچ کاری انجام نمیدهد
وقتی یک تابع مقداری را برمیگرداند، مالکیت آن مقدار نیز منتقل میشود. این روند ممکن است کمی خستهکننده باشد، زیرا باید اطمینان حاصل کنید که دادهها به درستی مدیریت میشوند.
fn main() {
let s1 = gives_ownership(); // gives_ownership مقدار خود را به s1 منتقل میکند
let s2 = String::from("hello"); // s2 به محدوده میآید
let s3 = takes_and_gives_back(s2); // s2 به تابع منتقل میشود و مقدار بازگشتی به s3 منتقل میشود
}
fn gives_ownership() -> String {
let some_string = String::from("yours");
some_string // some_string به تابع فراخوانی منتقل میشود
}
fn takes_and_gives_back(a_string: String) -> String {
a_string // a_string به تابع فراخوانی منتقل میشود
}
Rust به شما این امکان را میدهد که بدون انتقال مالکیت از یک مقدار استفاده کنید. این کار با استفاده از مراجع (references) انجام میشود. مراجع به شما این امکان را میدهند که به دادهها دسترسی پیدا کنید بدون اینکه مالکیت آنها را تغییر دهید.
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1); // با استفاده از مرجع به s1 دسترسی پیدا میکنیم
println!("The length of '{}' is {}.", s1, len); // s1 هنوز معتبر است
}
fn calculate_length(s: &String) -> usize {
s.len() // طول رشته را برمیگرداند
}
اگر بخواهید به دادهای که در یک مرجع ذخیره شده است، تغییراتی اعمال کنید، باید از مراجع قابل تغییر (mutable references) استفاده کنید. در Rust، تنها میتوانید یک مرجع قابل تغییر در هر زمان داشته باشید تا از بروز شرایط رقابتی جلوگیری شود.
fn main() {
let mut s = String::from("hello");
change(&mut s); // مرجع قابل تغییر به تابع ارسال میشود
println!("{}", s); // خروجی: hi
}
fn change(s: &mut String) {
s.push_str(", world!"); // محتوای s تغییر میکند
}
Rust برای مراجع قوانین خاصی دارد:
- هر مرجع باید به یک مقدار معتبر اشاره کند: اگر مرجع به مقداری اشاره کند که دیگر معتبر نیست، خطای کامپایل ایجاد میشود.
- تنها میتوانید یک مرجع قابل تغییر در هر زمان داشته باشید: این کار به جلوگیری از شرایط رقابتی کمک میکند.
- میتوانید چندین مرجع غیرقابل تغییر در یک زمان داشته باشید: این به شما این امکان را میدهد که به دادهها دسترسی داشته باشید بدون اینکه مالکیت آنها را تغییر دهید.
- ایمنی حافظه: با استفاده از مالکیت، Rust از بروز مشکلاتی مانند آزادسازی دو بار حافظه (double free) و استفاده از حافظه آزاد شده (dangling pointers) جلوگیری میکند.
- کارایی: سیستم مالکیت به Rust این امکان را میدهد که بدون نیاز به جمعآوری زباله، حافظه را به طور مؤثری مدیریت کند. این باعث میشود که برنامههای Rust سریعتر و کارآمدتر باشند.
- شفافیت: قوانین مالکیت و مراجع به وضوح مشخص میکنند که چه کسی مالک یک داده است و چه زمانی آن داده آزاد میشود، که به کد شما شفافیت بیشتری
use std::string;
fn main() {
//ownership in rust
//variable scope
let s = "hello";
{
println!("s: {s}");
let s = "h";
println!("s: {s}");
} //drop new s => "h"
println!("s: {s}");
//muteable string
let mut str = String::from("mohammadreza "); //idont know this
str.push_str("hooshmand");
println!("str name: {str}");
//variables and data intracting with move
let x = 5;
let y = x; //copy x value to y
let s1 = String::from("test"); //{ptr:HEAP,len:4Byte,capacity:4memory}
let s2 = s1; //move s1 value to s2,1 is not shollow copy is move
// for copy value we can use s1.clone()
take_ownership(s2);
//we can use return for takes ownership
}
fn take_ownership(s2: String) -> String {
return s2;
}
use std::string;
- این خط ماژول
string
را از کتابخانه استاندارد Rust وارد میکند. با این حال، در این کد خاص، نیازی به وارد کردنstd::string
نیست، زیرا از نوعString
به طور مستقیم استفاده نمیشود.
fn main() {
- این خط شروع تابع اصلی برنامه است. در Rust، اجرای برنامه از تابع
main
آغاز میشود.
let s = "hello";
{
println!("s: {s}");
let s = "h";
println!("s: {s}");
} //drop new s => "h"
println!("s: {s}");
-
تعریف متغیر
s
: متغیرs
به رشته ثابت"hello"
اشاره میکند. این رشته در استک ذخیره میشود. در Rust، رشتههای ثابت (string literals) به طور خودکار در حافظهای که به استک تعلق دارد، ذخیره میشوند و به دلیل اینکه اندازه آنها در زمان کامپایل مشخص است، سریع و کارآمد هستند. -
محدوده داخلی: درون
{}
یک محدوده جدید تعریف شده است. این محدوده به Rust میگوید که متغیرها و دادههای جدیدی که در اینجا تعریف میشوند، فقط در این بخش معتبر هستند.-
println!("s: {s}");
: این خط مقدارs
را در این محدوده چاپ میکند، که"hello"
است. -
let s = "h";
: در اینجا، یک متغیر جدید به نامs
تعریف میشود که در این محدوده معتبر است و مقدار آن"h"
است. این متغیر جدید، متغیر قبلی را در این محدوده پنهان میکند. به عبارت دیگر، این یک پنهانسازی (shadowing) است. -
println!("s: {s}");
: این خط مقدار جدیدs
را چاپ میکند، که"h"
است.
-
-
خروج از محدوده: وقتی از محدوده خارج میشویم، متغیر جدید
s
رها میشود و متغیر قبلی ("hello"
) دوباره معتبر میشود. در اینجا، Rust به طور خودکار حافظه مربوط به متغیر جدید را آزاد میکند. -
println!("s: {s}");
: این خط مقدار اصلیs
را چاپ میکند، که"hello"
است.
let mut str = String::from("mohammadreza "); //idont know this
str.push_str("hooshmand");
println!("str name: {str}");
- تعریف رشته قابل تغییر: متغیر
str
به یک رشته قابل تغییر (String
) با مقدار اولیه"mohammadreza "
اختصاص داده میشود. ازString::from
برای ایجاد این نوع داده استفاده میشود. نوعString
در هپ ذخیره میشود و به شما این امکان را میدهد که محتوای آن را تغییر دهید. - اضافه کردن به رشته: با استفاده از متد
push_str
، رشته"hooshmand"
به انتهایstr
اضافه میشود. حالا مقدارstr
به"mohammadreza hooshmand"
تغییر میکند. این کار به دلیل این است که نوعString
میتواند مقادیر متغیر را مدیریت کند. - چاپ مقدار رشته: این خط مقدار جدید
str
را چاپ میکند. خروجی این بخش"str name: mohammadreza hooshmand"
خواهد بود.
let x = 5;
let y = x; //copy x value to y
let s1 = String::from("test"); //{ptr:HEAP,len:4Byte,capacity:4memory}
let s2 = s1; //move s1 value to s2,1 is not shollow copy is move
// for copy value we can use s1.clone()
take_ownership(s2);
- تعریف عدد صحیح:
let x = 5;
یک عدد صحیح به نامx
تعریف میکند. از آنجا کهi32
(نوع عدد صحیح) یک نوعCopy
است، میتوانیم مقدار آن را به متغیر دیگری بدون از بین رفتن اعتبارx
کپی کنیم. - کپی عدد صحیح:
let y = x;
در اینجا، مقدارx
بهy
کپی میشود. از آنجا کهi32
یک نوعCopy
است،x
وy
هر دو معتبر هستند و میتوانند به طور مستقل استفاده شوند. - تعریف یک رشته:
let s1 = String::from("test");
یک رشته قابل تغییر با مقدار"test"
ایجاد میکند. این رشته در هپ ذخیره میشود و شامل یک اشارهگر به دادهها، طول و ظرفیت است. این نوع داده به شما این امکان را میدهد که محتوای آن را تغییر دهید و اندازه آن را در زمان اجرا تغییر دهید. - انتقال مالکیت:
let s2 = s1;
در اینجا، مالکیتs1
بهs2
منتقل میشود. از آنجا کهString
یک نوعCopy
نیست،s1
دیگر معتبر نیست و نمیتوانید از آن استفاده کنید. این عمل به عنوان move شناخته میشود. به عبارت دیگر،s1
دیگر به دادههای هپ اشاره نمیکند وs2
اکنون مالک آن دادهها است. - فراخوانی تابع:
take_ownership(s2);
تابعtake_ownership
باs2
به عنوان آرگومان فراخوانی میشود. در اینجا،s2
مالکیت داده را به تابع منتقل میکند.
fn take_ownership(s2: String) -> String {
return s2;
}
- تعریف تابع: تابع
take_ownership
یک آرگومان از نوعString
میگیرد و آن را برمیگرداند. این تابع در واقع مالکیتs2
را میگیرد و سپس آن را برمیگرداند. - بازگشت مقدار: مقدار
s2
به تابعی که آن را فراخوانی کرده است، بازگردانده میشود. در این کد، این مقدار به هیچ متغیر دیگری اختصاص داده نمیشود و در واقع هیچ استفادهای از آن نمیشود. با این حال، این نشان میدهد که چگونه میتوان مالکیت را به تابعی دیگر منتقل کرد و سپس آن را برگرداند.
-
مالکیت و انتقال: در Rust، انتقال مالکیت یک مفهوم کلیدی است. وقتی یک متغیر به متغیر دیگری اختصاص داده میشود، مالکیت داده به متغیر جدید منتقل میشود. این به Rust کمک میکند تا از بروز مشکلاتی مانند آزادسازی دو بار حافظه جلوگیری کند.
-
محدوده متغیرها: محدوده متغیرها در Rust بسیار مهم است. متغیرها فقط در محدودهای که تعریف شدهاند معتبر هستند و وقتی از آن محدوده خارج میشوند، حافظه مربوط به آنها به طور خودکار آزاد میشود.
-
رشتههای ثابت و قابل تغییر: رشتههای ثابت (string literals) در استک ذخیره میشوند و غیرقابل تغییر هستند، در حالی که رشتههای قابل تغییر (String) در هپ ذخیره میشوند و میتوانند تغییر کنند.
-
کپی و Move: انواعی که اندازه آنها در زمان کامپایل مشخص است (مانند اعداد صحیح) به طور خودکار کپی میشوند، در حالی که انواعی مانند
String
که اندازه آنها در زمان کامپایل مشخص نیست، با انتقال مالکیت (move) مدیریت میشوند. -
استفاده از clone: اگر بخواهید یک کپی عمیق از دادهها داشته باشید، میتوانید از متد
clone
استفاده کنید. این کار دادهها را در هپ کپی میکند و به شما این امکان را میدهد که از هر دو متغیر به طور مستقل استفاده کنید.