سیستم مالکیت Rust توضیح داده شد – انجمن DEV

Summarize this content to 400 words in Persian Lang
برای درک مفهوم مالکیت، لازم است تصوری از اینکه پشته و پشته چیست و چگونه کار می کنند، داشته باشیم.
پشته و هیپ
متغیرها برچسب هایی هستند که حاوی آدرس داده های موجود در حافظه هستند. هر قطعه داده یا روی پشته یا پشته ذخیره می شود. پشته و پشته بخشی از حافظه در دسترس برنامه شما در طول زمان اجرا هستند.
اندازه پشته در زمان کامپایل مشخص است و ثابت است. بنابراین فقط می تواند داده هایی با اندازه ثابت و شناخته شده در زمان کامپایل مانند اعداد صحیح، شناور، بولی و غیره را ذخیره کند.
// STACK VARIABLES
let x: i32 = 2;
let y: f64 = 3.14;
let z: bool = false;
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
هیپ داده هایی را ذخیره می کند که اندازه آنها ناشناخته است یا می تواند اندازه آنها افزایش یابد.
عناصر جدید را می توان در برخی از زمان های برنامه به بردار فشار داد. بنابراین روی پشته ذخیره می شود.
let a: Veci32> = vec![2, 4, 6, 8];
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
متغیر b دادههایی را نگه میدارد که ویژگی نمایش را اجرا میکند و این میتواند در هر اندازه باشد، بنابراین با استفاده از اشارهگر هوشمند Box آنها را در پشته ذخیره میکنیم.
use std::fmt::Display;
let b: Boxdyn Display> = Box::new(12);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
متغیر c یک ساختار داده رشته ای است و می توانیم در طول برنامه کاراکترهای بیشتری به آن اضافه کنیم، بنابراین در پشته نیز ذخیره می شود.
let c: String = String::from(“hello”);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
پشته مقادیر را به ترتیبی که آنها را دریافت می کند ذخیره می کند و مقادیر را به ترتیب مخالف حذف می کند. در شروع این محدوده جدید در برنامه، 2 به بالای پشته اضافه می شود. وقتی به قسمت داخلی می رسیم، 3.0 و 4 به بالای پشته فشار داده می شوند و x و y به مقادیر مربوطه خود اشاره کنید.
fn main() {
//OUTER SCOPE
// w is pushed to top the stack
let w = 2;
{
//INNER SCOPE
// x and y are pushed to the top of the stack
let x = 3.0;
let y = 4;
}
// y and x are popped off the stack and
// can not be used here
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
وقتی به انتهای حوزه درونی رسیدیم y و x از پشته بیرون می آیند. این روشی است که همه زبان های برنامه نویسی با اضافه کردن و حذف داده ها در پشته کار می کنند.
برای افزودن دادهها روی پشته، تخصیصدهنده حافظه فضایی به اندازه کافی بزرگ برای نگهداری دادهها پیدا میکند، آنها را ذخیره میکند و یک اشارهگر که آدرس آن حافظه است را برمیگرداند.
زبان های برنامه نویسی مختلف روش های مختلفی برای حذف داده ها روی پشته دارند. زبانهای سطح بالا، مانند جاوا اسکریپت و گلانگ، یک زبالهگیر دارند که بهطور دورهای حافظهای را پیدا میکند که دیگر استفاده نمیشود و آنها را پاک میکند.
زبانهای سطح پایین به کاربر این امکان را میدهند که صریحاً حافظه را بر روی پشته اختصاص داده و آزاد کند. در زبانهایی مانند C و C++، برنامهنویس به صورت دستی حافظه را تخصیص میدهد که یک اشارهگر را برمیگرداند و با استفاده از آن اشارهگر، حافظه را توزیع میکند.
اگر این کار به درستی انجام نشود، زمانی که برنامه نویس فراموش می کند حافظه را آزاد کند، می تواند منجر به نشت حافظه شود و به مرور زمان باعث از کار افتادن برنامه شود. یا سعی کنید از یک حافظه آزاد شده بخوانید یا همان حافظه را دو بار آزاد کنید.
اگر نسخه ویدیویی این مقاله را ترجیح می دهید، ویدیوی یوتیوب من را در آن بررسی کنید.
مدل مالکیت زنگ
Rust با مدل مالکیت خود کارها را متفاوت انجام می دهد.
وقتی به پشته اضافه می کنیم، اشاره گر که آدرس حافظه است به پشته اضافه می شود زیرا اندازه آن مشخص است. برای سیستم 64 بیتی 8 بایت و برای سیستم 32 بیتی 4 بایت است.
سپس متغیر به آن اشاره گر در پشته منتهی می شود و صاحب داده نامیده می شود.
هنگامی که یک متغیر heap از محدوده خارج می شود، اشاره گر از پشته خارج می شود و هر زمان که نشانگر از پشته خارج شود، داده های روی پشته پاک می شود.
مدل مالکیت Rust تضمین میکند که تنها یک اشارهگر به حافظه روی پشته وجود دارد تا از اشاره به حافظه آزاد شده جلوگیری کند. اگر دو اشاره گر به یک داده روی پشته وجود داشته باشد، نشانگرها در پشته ذخیره می شوند و اگر یکی از متغیرها از محدوده خارج شود، نشانگر خارج می شود و داده های پشته پاک می شود.
تلاش برای خواندن داده ها از نشانگر دیگر باعث بروز خطا می شود که منجر به رفتار غیرمنتظره یا خراب شدن برنامه می شود.
هنگامی که یک متغیر پشته را به یک متغیر جدید اختصاص می دهیم، داده ها کپی شده و به بالای پشته منتقل می شوند. این فرآیند ارزان است زیرا اندازه و مکان آنها قبلاً شناخته شده است. این فرآیند کپی نامیده می شود.
وقتی مقادیر را چاپ می کنیم x و y، برای هر دو 3 می گیریم.
let x = 3;
let y = x;
println!(“x: {x}”);
println!(“y: {y}”);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
x: 3
y: 3
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
اگر یک متغیر heap را به متغیر دیگری اختصاص دهیم، کپی کردن داده ها در پشته پرهزینه خواهد بود، زیرا تخصیص دهنده حافظه باید ابتدا فضایی را روی پشته پیدا کند، به اندازه ای بزرگ که داده ها را در خود جای دهد، قبل از کپی کردن مقادیر در آن آدرس. .
let x = String::from(“hello”);
let y = x;
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
در پشته، نمیتوانیم نشانگر را کپی کنیم، زیرا دو نشانگر به یک مکان روی پشته خواهیم داشت. در عوض، اشاره گر به بالای پشته منتقل می شود، سپس متغیر جدید به آن اشاره گر منتهی می شود و متغیر قدیمی نامعتبر می شود.
متغیر y اکنون نشانگر رشته را نگه می دارد و زمانی که سعی می کنیم متغیر را بخوانیم x، یک خطا دریافت می کنیم.
println!(“{x}”);
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
error[E0382]: borrow of moved value: `x`
–> main.rs:16:13
|
12 | let x = String::from(“hello”);
| – move occurs because `x` has type `String`, wh
ich does not implement the `Copy` trait
13 |
14 | let y = x;
| – value moved here
15 |
16 | println!(“{x}”);
| ^^^ value borrowed here after move
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
به همین ترتیب، زمانی که مقادیری را به آرگومان های یک تابع ارسال می کنیم، متغیرهای موجود در پشته که نشانگر نیستند، در پارامترها کپی می شوند و در حالی که متغیرهایی که نشانگر هستند منتقل می شوند، به پشته فشار داده می شوند، و پارامتر تابع مالک جدید است. متغیر قدیمی اکنون نامعتبر است و زمانی که میخواهیم از آن استفاده کنیم، در زمان کامپایل با خطا مواجه میشویم.
fn main() {
let s = vec![2, 4, 6, 8];
let t = 1;
// s is moved into the first parameter of the function
// while t is copied
get_elem(s, t);
//s is invalid here, but t is valid
}
fn get_elem(v: Veci32>, u: usize) -> i32 {
v[u]
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
هنگامی که دامنه به پایان می رسد، نشانگر بیرون زده و حذف می شود.
ما می توانیم با برگرداندن مقادیر و تخصیص آنها به متغیرها، مالکیت مجدد را منتقل کنیم.
fn main() {
let s = vec![2, 4, 6, 8];
let t = 1;
let (result, s) = get_elem(s, t);
}
fn get_elem(v: Veci32>, u: usize) -> (i32, Veci32>) {
(v[u], v)
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
این رویکرد کمی خسته کننده است. اگر بخواهیم مالکیت را در حین استفاده از مقادیر در یک تابع حفظ کنیم، از مراجع استفاده می کنیم.
مراجع و قرض گرفتن در رست
مراجع اشاره گرهای خاصی هستند که به داده ها و سایر اشاره گرهای روی پشته اشاره می کنند.
ما تابع را تغییر می دهیم تا مرجع یک بردار را با افزودن یک علامت بپذیرد.
fn main() {
let y = vec![2, 4, 6, 8];
let x = 1;
let result = get_elem(&y, x);
}
fn get_elem(v: &Veci32>, u: usize) -> i32 {
v[u]
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
هنگامی که یک مرجع از یک متغیر ایجاد می کنیم، یک اشاره گر به آن متغیر داده می شود و به پشته هل داده می شود. کامپایلر مطمئن می شود که این مرجع همیشه به داده های معتبر روی پشته با استفاده از مجموعه ای از قوانین به نام قوانین قرض گرفتن اشاره می کند. عمل ایجاد ارجاع به یک متغیر نامیده می شود قرض گرفتن.
اکنون وقتی محدوده تابع به پایان می رسد، مرجع از پشته خارج می شود و نه مقدار واقعی.
اکنون می توانیم از متغیر استفاده کنیم y بعد از تابع
fn main() {
let y = vec![2, 4, 6, 8];
let x = 1;
let result = get_elem(&y, x);
println!(“{y}”);
}
fn get_elem(v: &Veci32>, u: usize) -> i32 {
v[u]
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
اگر متغیری جابجا شود، همه مراجع آن باطل می شوند. در اینجا، متغیر x شامل داده های رشته ای و y مراجع x.
let x = “hello”.to_string();
let y = &x;
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
اگر حرکت کنیم x، وارد یک محدوده درونی شده و سعی کنید از آن استفاده کنید y بعد از scope، یک خطا دریافت می کنیم. این به این دلیل است که وقتی x منتقل می شود، مراجع آن باطل می شود.
let x = “hello”.to_string();
let y = &x;
{
x;
}
// Here, all references of x are invalid
// Since x is invalid
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
برای کسب اطلاعات بیشتر در مورد مراجع و قوانین وام گرفتن، این مقاله را بررسی کنید
با تشکر برای خواندن.
مقاله ای از وب سایت من https://cudi.dev/articles/ownership_in_rust_explained
برای درک مفهوم مالکیت، لازم است تصوری از اینکه پشته و پشته چیست و چگونه کار می کنند، داشته باشیم.
پشته و هیپ
متغیرها برچسب هایی هستند که حاوی آدرس داده های موجود در حافظه هستند. هر قطعه داده یا روی پشته یا پشته ذخیره می شود. پشته و پشته بخشی از حافظه در دسترس برنامه شما در طول زمان اجرا هستند.
اندازه پشته در زمان کامپایل مشخص است و ثابت است. بنابراین فقط می تواند داده هایی با اندازه ثابت و شناخته شده در زمان کامپایل مانند اعداد صحیح، شناور، بولی و غیره را ذخیره کند.
// STACK VARIABLES
let x: i32 = 2;
let y: f64 = 3.14;
let z: bool = false;
هیپ داده هایی را ذخیره می کند که اندازه آنها ناشناخته است یا می تواند اندازه آنها افزایش یابد.
عناصر جدید را می توان در برخی از زمان های برنامه به بردار فشار داد. بنابراین روی پشته ذخیره می شود.
let a: Veci32> = vec![2, 4, 6, 8];
متغیر b
دادههایی را نگه میدارد که ویژگی نمایش را اجرا میکند و این میتواند در هر اندازه باشد، بنابراین با استفاده از اشارهگر هوشمند Box آنها را در پشته ذخیره میکنیم.
use std::fmt::Display;
let b: Boxdyn Display> = Box::new(12);
متغیر c
یک ساختار داده رشته ای است و می توانیم در طول برنامه کاراکترهای بیشتری به آن اضافه کنیم، بنابراین در پشته نیز ذخیره می شود.
let c: String = String::from("hello");
پشته مقادیر را به ترتیبی که آنها را دریافت می کند ذخیره می کند و مقادیر را به ترتیب مخالف حذف می کند. در شروع این محدوده جدید در برنامه، 2 به بالای پشته اضافه می شود. وقتی به قسمت داخلی می رسیم، 3.0 و 4 به بالای پشته فشار داده می شوند و x
و y
به مقادیر مربوطه خود اشاره کنید.
fn main() {
//OUTER SCOPE
// w is pushed to top the stack
let w = 2;
{
//INNER SCOPE
// x and y are pushed to the top of the stack
let x = 3.0;
let y = 4;
}
// y and x are popped off the stack and
// can not be used here
}
وقتی به انتهای حوزه درونی رسیدیم y
و x
از پشته بیرون می آیند. این روشی است که همه زبان های برنامه نویسی با اضافه کردن و حذف داده ها در پشته کار می کنند.
برای افزودن دادهها روی پشته، تخصیصدهنده حافظه فضایی به اندازه کافی بزرگ برای نگهداری دادهها پیدا میکند، آنها را ذخیره میکند و یک اشارهگر که آدرس آن حافظه است را برمیگرداند.
زبان های برنامه نویسی مختلف روش های مختلفی برای حذف داده ها روی پشته دارند. زبانهای سطح بالا، مانند جاوا اسکریپت و گلانگ، یک زبالهگیر دارند که بهطور دورهای حافظهای را پیدا میکند که دیگر استفاده نمیشود و آنها را پاک میکند.
زبانهای سطح پایین به کاربر این امکان را میدهند که صریحاً حافظه را بر روی پشته اختصاص داده و آزاد کند. در زبانهایی مانند C و C++، برنامهنویس به صورت دستی حافظه را تخصیص میدهد که یک اشارهگر را برمیگرداند و با استفاده از آن اشارهگر، حافظه را توزیع میکند.
اگر این کار به درستی انجام نشود، زمانی که برنامه نویس فراموش می کند حافظه را آزاد کند، می تواند منجر به نشت حافظه شود و به مرور زمان باعث از کار افتادن برنامه شود. یا سعی کنید از یک حافظه آزاد شده بخوانید یا همان حافظه را دو بار آزاد کنید.
اگر نسخه ویدیویی این مقاله را ترجیح می دهید، ویدیوی یوتیوب من را در آن بررسی کنید.
https://www.youtube.com/watch?v=UiF8wMA-afA
مدل مالکیت زنگ
Rust با مدل مالکیت خود کارها را متفاوت انجام می دهد.
وقتی به پشته اضافه می کنیم، اشاره گر که آدرس حافظه است به پشته اضافه می شود زیرا اندازه آن مشخص است. برای سیستم 64 بیتی 8 بایت و برای سیستم 32 بیتی 4 بایت است.
سپس متغیر به آن اشاره گر در پشته منتهی می شود و صاحب داده نامیده می شود.
هنگامی که یک متغیر heap از محدوده خارج می شود، اشاره گر از پشته خارج می شود و هر زمان که نشانگر از پشته خارج شود، داده های روی پشته پاک می شود.
مدل مالکیت Rust تضمین میکند که تنها یک اشارهگر به حافظه روی پشته وجود دارد تا از اشاره به حافظه آزاد شده جلوگیری کند. اگر دو اشاره گر به یک داده روی پشته وجود داشته باشد، نشانگرها در پشته ذخیره می شوند و اگر یکی از متغیرها از محدوده خارج شود، نشانگر خارج می شود و داده های پشته پاک می شود.
تلاش برای خواندن داده ها از نشانگر دیگر باعث بروز خطا می شود که منجر به رفتار غیرمنتظره یا خراب شدن برنامه می شود.
هنگامی که یک متغیر پشته را به یک متغیر جدید اختصاص می دهیم، داده ها کپی شده و به بالای پشته منتقل می شوند. این فرآیند ارزان است زیرا اندازه و مکان آنها قبلاً شناخته شده است. این فرآیند کپی نامیده می شود.
وقتی مقادیر را چاپ می کنیم x
و y
، برای هر دو 3 می گیریم.
let x = 3;
let y = x;
println!("x: {x}");
println!("y: {y}");
x: 3
y: 3
اگر یک متغیر heap را به متغیر دیگری اختصاص دهیم، کپی کردن داده ها در پشته پرهزینه خواهد بود، زیرا تخصیص دهنده حافظه باید ابتدا فضایی را روی پشته پیدا کند، به اندازه ای بزرگ که داده ها را در خود جای دهد، قبل از کپی کردن مقادیر در آن آدرس. .
let x = String::from("hello");
let y = x;
در پشته، نمیتوانیم نشانگر را کپی کنیم، زیرا دو نشانگر به یک مکان روی پشته خواهیم داشت. در عوض، اشاره گر به بالای پشته منتقل می شود، سپس متغیر جدید به آن اشاره گر منتهی می شود و متغیر قدیمی نامعتبر می شود.
متغیر y
اکنون نشانگر رشته را نگه می دارد و زمانی که سعی می کنیم متغیر را بخوانیم x
، یک خطا دریافت می کنیم.
println!("{x}");
error[E0382]: borrow of moved value: `x`
--> main.rs:16:13
|
12 | let x = String::from("hello");
| - move occurs because `x` has type `String`, wh
ich does not implement the `Copy` trait
13 |
14 | let y = x;
| - value moved here
15 |
16 | println!("{x}");
| ^^^ value borrowed here after move
به همین ترتیب، زمانی که مقادیری را به آرگومان های یک تابع ارسال می کنیم، متغیرهای موجود در پشته که نشانگر نیستند، در پارامترها کپی می شوند و در حالی که متغیرهایی که نشانگر هستند منتقل می شوند، به پشته فشار داده می شوند، و پارامتر تابع مالک جدید است. متغیر قدیمی اکنون نامعتبر است و زمانی که میخواهیم از آن استفاده کنیم، در زمان کامپایل با خطا مواجه میشویم.
fn main() {
let s = vec![2, 4, 6, 8];
let t = 1;
// s is moved into the first parameter of the function
// while t is copied
get_elem(s, t);
//s is invalid here, but t is valid
}
fn get_elem(v: Veci32>, u: usize) -> i32 {
v[u]
}
هنگامی که دامنه به پایان می رسد، نشانگر بیرون زده و حذف می شود.
ما می توانیم با برگرداندن مقادیر و تخصیص آنها به متغیرها، مالکیت مجدد را منتقل کنیم.
fn main() {
let s = vec![2, 4, 6, 8];
let t = 1;
let (result, s) = get_elem(s, t);
}
fn get_elem(v: Veci32>, u: usize) -> (i32, Veci32>) {
(v[u], v)
}
این رویکرد کمی خسته کننده است. اگر بخواهیم مالکیت را در حین استفاده از مقادیر در یک تابع حفظ کنیم، از مراجع استفاده می کنیم.
مراجع و قرض گرفتن در رست
مراجع اشاره گرهای خاصی هستند که به داده ها و سایر اشاره گرهای روی پشته اشاره می کنند.
ما تابع را تغییر می دهیم تا مرجع یک بردار را با افزودن یک علامت بپذیرد.
fn main() {
let y = vec![2, 4, 6, 8];
let x = 1;
let result = get_elem(&y, x);
}
fn get_elem(v: &Veci32>, u: usize) -> i32 {
v[u]
}
هنگامی که یک مرجع از یک متغیر ایجاد می کنیم، یک اشاره گر به آن متغیر داده می شود و به پشته هل داده می شود. کامپایلر مطمئن می شود که این مرجع همیشه به داده های معتبر روی پشته با استفاده از مجموعه ای از قوانین به نام قوانین قرض گرفتن اشاره می کند. عمل ایجاد ارجاع به یک متغیر نامیده می شود قرض گرفتن.
اکنون وقتی محدوده تابع به پایان می رسد، مرجع از پشته خارج می شود و نه مقدار واقعی.
اکنون می توانیم از متغیر استفاده کنیم y
بعد از تابع
fn main() {
let y = vec![2, 4, 6, 8];
let x = 1;
let result = get_elem(&y, x);
println!("{y}");
}
fn get_elem(v: &Veci32>, u: usize) -> i32 {
v[u]
}
اگر متغیری جابجا شود، همه مراجع آن باطل می شوند. در اینجا، متغیر x
شامل داده های رشته ای و y
مراجع x
.
let x = "hello".to_string();
let y = &x;
اگر حرکت کنیم x
، وارد یک محدوده درونی شده و سعی کنید از آن استفاده کنید y
بعد از scope، یک خطا دریافت می کنیم. این به این دلیل است که وقتی x
منتقل می شود، مراجع آن باطل می شود.
let x = "hello".to_string();
let y = &x;
{
x;
}
// Here, all references of x are invalid
// Since x is invalid
برای کسب اطلاعات بیشتر در مورد مراجع و قوانین وام گرفتن، این مقاله را بررسی کنید
با تشکر برای خواندن.
مقاله ای از وب سایت من https://cudi.dev/articles/ownership_in_rust_explained