ცვლადები და Mutability
როგორც “მნიშვნელობების შენახვა ცვლადებით” სექციაში აღვნიშნეთ, ნაგულისხმევად, ცვლადები არის immutable (შეუცვლელი). ეს არის Rust-ის მიერ მოცემული მრავალი მითითებიდან ერთ-ერთი, რათა დაწეროთ კოდი ისეთნაირად, რომ ისარგებლოთ იმ უსაფრთხოებითა და მარტივი კონკურენტულობით (concurrency), რომელსაც Rust-ი გთავაზობთ. თუმცა, თქვენ მაინც გაქვთ ოფცია, გახადოთ თქვენი ცვლადები mutable. განვიხილოთ, თუ როგორ და რატომ გირჩევთ Rust-ი უპირატესობა მიანიჭოთ immutability-ს და რატომ შეიძლება ზოგჯერ მოგინდეთ ამაზე უარის თქმა.
როდესაც ცვლადი არის immutable, მას შემდეგ, რაც მნიშვნელობა დაუკავშირდება სახელს, თქვენ ვერ შეცვლით ამ მნიშვნელობას. ამის ილუსტრირებისთვის, შექმენით ახალი პროექტი სახელად variables თქვენს projects დირექტორიაში cargo new variables-ის გამოყენებით.
შემდეგ, თქვენს ახალ variables დირექტორიაში გახსენით src/main.rs და შეცვალეთ მისი კოდი შემდეგი კოდით, რომელიც ჯერ ვერ დაკომპილირდება:
ფაილის სახელი: src/main.rs
fn main() {
let x = 5;
println!("The value of x is: {x}");
x = 6;
println!("The value of x is: {x}");
}
შეინახეთ და გაუშვით პროგრამა cargo run-ის გამოყენებით. თქვენ უნდა მიიღოთ შეცდომის შეტყობინება immutability შეცდომის შესახებ, როგორც ნაჩვენებია ამ გამონატანში:
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
error[E0384]: cannot assign twice to immutable variable `x`
--> src/main.rs:4:5
|
2 | let x = 5;
| - first assignment to `x`
3 | println!("The value of x is: {x}");
4 | x = 6;
| ^^^^^ cannot assign twice to immutable variable
|
help: consider making this binding mutable
|
2 | let mut x = 5;
| +++
For more information about this error, try `rustc --explain E0384`.
error: could not compile `variables` (bin "variables") due to 1 previous error
ეს მაგალითი აჩვენებს, თუ როგორ გეხმარებათ კომპილატორი პროგრამებში შეცდომების პოვნაში. კომპილატორის შეცდომები შეიძლება იყოს დამთრგუნველი, თუმცა სინამდვილეში ისინი მხოლოდ იმას ნიშნავს, რომ თქვენი პროგრამა ჯერ უსაფრთხოდ არ აკეთებს იმას, რაც გსურთ; ისინი არ ნიშნავს იმას, რომ კარგი პროგრამისტი არ ხართ! გამოცდილი Rustacean-ებიც კი იღებენ კომპილატორის შეცდომებს.
თქვენ მიიღეთ შეცდომის შეტყობინება cannot assign twice to immutable variable `x`, რადგან სცადეთ მეორე მნიშვნელობის მინიჭება immutable x ცვლადისთვის.
მნიშვნელოვანია, რომ მივიღოთ კომპილაციის დროის შეცდომები, როდესაც ვცდილობთ შევცვალოთ მნიშვნელობა, რომელიც მითითებულია როგორც immutable, რადგან ამ სიტუაციამ შეიძლება გამოიწვიოს ხარვეზები (bugs). თუ ჩვენი კოდის ერთი ნაწილი იმუშავებს იმ დაშვებით, რომ მნიშვნელობა არასოდეს შეიცვლება, ხოლო კოდის მეორე ნაწილი შეცვლის ამ მნიშვნელობას, შესაძლებელია, რომ კოდის პირველმა ნაწილმა არ გააკეთოს ის, რისთვისაც იყო განკუთვნილი. ამ ტიპის ხარვეზის მიზეზის მიკვლევა შეიძლება რთული იყოს ფაქტის შემდეგ, განსაკუთრებით მაშინ, როდესაც კოდის მეორე ნაწილი მნიშვნელობას ცვლის მხოლოდ ზოგჯერ. Rust-ის კომპილატორი იძლევა გარანტიას, რომ როდესაც აცხადებთ, რომ მნიშვნელობა არ შეიცვლება, ის ნამდვილად არ შეიცვლება, ასე რომ თქვენ არ მოგიწევთ მასზე თვალყურის დევნება თავად. ამრიგად, თქვენი კოდის გააზრება უფრო მარტივია.
თუმცა mutability შეიძლება იყოს ძალიან სასარგებლო და გახადოს კოდის დაწერა უფრო მოსახერხებელი. თუმცა ცვლადები ნაგულისხმევად immutable-ია, შეგიძლიათ გახადოთ ისინი mutable ცვლადის სახელის წინ mut-ის დამატებით, როგორც ეს გააკეთეთ მე-2 თავში. mut-ის დამატება ასევე გადასცემს განზრახვას კოდის მომავალ მკითხველებს იმის მითითებით, რომ კოდის სხვა ნაწილები შეცვლიან ამ ცვლადის მნიშვნელობას.
მაგალითად, შევცვალოთ src/main.rs შემდეგნაირად:
ფაილის სახელი: src/main.rs
fn main() {
let mut x = 5;
println!("The value of x is: {x}");
x = 6;
println!("The value of x is: {x}");
}
როდესაც ახლა ვაშვებთ პროგრამას, ვიღებთ ამას:
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s
Running `target/debug/variables`
The value of x is: 5
The value of x is: 6
ჩვენ უფლება გვავს შევცვალოთ x-თან დაკავშირებული მნიშვნელობა 5-იდან 6-მდე, როდესაც გამოიყენება mut. საბოლოო ჯამში, იმის გადაწყვეტა, გამოიყენოთ თუ არა mutability, თქვენზეა დამოკიდებული და იმაზე, თუ რა მიგაჩნიათ უფრო ნათლად იმ კონკრეტულ სიტუაციაში.
კონსტანტების გამოცხადება (Declaring Constants)
immutable ცვლადების მსგავსად, კონსტანტები (constants) არის მნიშვნელობები, რომლებიც დაკავშირებულია სახელთან და არ აქვთ შეცვლის უფლება, თუმცა არის რამდენიმე განსხვავება კონსტანტებსა და ცვლადებს შორის.
პირველი, თქვენ არ გაქვთ უფლება გამოიყენოთ mut კონსტანტებთან. კონსტანტები უბრალოდ ნაგულისხმევად immutable კი არ არის — ისინი ყოველთვის immutable-ია. კონსტანტებს აცხადებთ const საკვანძო სიტყვის გამოყენებით let საკვანძო სიტყვის ნაცვლად, ხოლო მნიშვნელობის ტიპი აუცილებლად უნდა იყოს მითითებული. ტიპებსა და ტიპების მითითებას დავფარავთ მომდევნო სექციაში, “მონაცემთა ტიპები”, ასე რომ ნუ იდარდებთ დეტალებზე ახლავე. უბრალოდ იცოდეთ, რომ ყოველთვის უნდა მიუთითოთ ტიპი.
კონსტანტები შეიძლება გამოცხადდეს ნებისმიერ Scope-ში, მათ შორის გლობალურ Scope-შიც, რაც მათ სასარგებლოს ხდის იმ მნიშვნელობებისთვის, რომელთა შესახებაც კოდის მრავალ ნაწილს სჭირდება ცოდნა.
ბოლო განსხვავება ის არის, რომ კონსტანტები შეიძლება დაყენდეს მხოლოდ კონსტანტურ გამოსახულებაზე (constant expression) და არა იმ მნიშვნელობის შედეგზე, რომლის გამოთვლა მხოლოდ შესრულების დროში (runtime) იქნება შესაძლებელი.
აი, კონსტანტის გამოცხადების მაგალითი:
#![allow(unused)]
fn main() {
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
}
კონსტანტის სახელია THREE_HOURS_IN_SECONDS, ხოლო მისი მნიშვნელობა დაყენებულია 60-ის (წამების რაოდენობა წუთში) 60-ზე (წუთების რაოდენობა საათში) 3-ზე (საათების რაოდენობა, რომლის დათვლაც გვსურს ამ პროგრამაში) გამრავლების შედეგზე. Rust-ის სახელდების სტანდარტი (naming convention) კონსტანტებისთვის არის ყველა დიდი ასოს გამოყენება სიტყვებს შორის ქვედა ტირეებით. კომპილატორს შეუძლია შეაფასოს ოპერაციების შეზღუდული ნაკრები კომპილაციის დროში, რაც საშუალებას გვძლევს ავირჩიოთ ამ მნიშვნელობის დაწერა ისეთი გზით, რომელიც უფრო მარტივი გასაგები და გადასამოწმებელია, ნაცვლად ამ კონსტანტის 10,800 მნიშვნელობაზე დაყენებისა. იხილეთ Rust Reference-ის სექცია კონსტანტების შეფასების შესახებ დამატებითი ინფორმაციისთვის იმ ოპერაციების შესახებ, რომლებიც შეიძლება გამოყენებულ იქნას კონსტანტების გამოცხადებისას.
კონსტანტები ვალიდურია პროგრამის შესრულების მთელი დროის განმავლობაში იმ Scope-ის შიგნით, სადაც ისინი გამოცხადდა. ეს თვისება კონსტანტებს სასარგებლოს ხდის თქვენი აპლიკაციის დომენში არსებული იმ მნიშვნელობებისთვის, რომელთა შესახებაც პროგრამის მრავალ ნაწილს შეიძლება დასჭირდეს ცოდნა, როგორიცაა თამაშის ნებისმიერი მოთამაშისთვის დასაშვები ქულების მაქსიმალური რაოდენობა ან სინათლის სიჩქარე.
თქვენს პროგრამაში გამოყენებული ჰარდკოდირებული (hardcoded) მნიშვნელობებისთვის კონსტანტების სახით სახელების დარქმევა სასარგებლოა კოდის მომავალი მომვლელებისთვის ამ მნიშვნელობის შინაარსის გადასაცემად. ის ასევე ეხმარება იმას, რომ კოდში გქონდეთ მხოლოდ ერთი ადგილი, რომლის შეცვლაც დაგჭირდებათ, თუ ჰარდკოდირებული მნიშვნელობა მომავალში განახლებას საჭიროებს.
Shadowing-ი (დაჩრდილვა)
როგორც ნახეთ რიცხვის გამოცნობის თამაშის სახელმძღვანელოში მე-2 თავში, შეგიძლიათ გამოაცხადოთ ახალი ცვლადი წინა ცვლადის იმავე სახელწოდებით. Rustacean-ები ამბობენ, რომ პირველი ცვლადი დაჩრდილულია (shadowed) მეორის მიერ, რაც ნიშნავს, რომ მეორე ცვლადი არის ის, რასაც კომპილატორი დაინახავს, როდესაც იყენებთ ცვლადის სახელს. ფაქტობრივად, მეორე ცვლადი ჩრდილავს პირველს, და იღებს ცვლადის სახელის ნებისმიერ გამოყენებას მანამ, სანამ ის თავად არ დაიჩრდილება ან Scope არ დასრულდება. ჩვენ შეგვიძლია დავჩრდილოთ ცვლადი იმავე ცვლადის სახელის გამოყენებით და let საკვანძო სიტყვის განმეორებით, შემდეგნაირად:
ფაილის სახელი: src/main.rs
fn main() {
let x = 5;
let x = x + 1;
{
let x = x * 2;
println!("The value of x in the inner scope is: {x}");
}
println!("The value of x is: {x}");
}
ეს პროგრამა ჯერ აკავშირებს x-ს 5 მნიშვნელობასთან. შემდეგ ის ქმნის ახალ ცვლადს x let x =-ის განმეორებით, იღებს თავდაპირველ მნიშვნელობას და უმატებს 1-ს, ასე რომ x-ის მნიშვნელობა ხდება 6. შემდეგ, ფიგურული ფრჩხილებით შექმნილ შიდა Scope-ში, მესამე let ინსტრუქციაც ჩრდილავს x-ს და ქმნის ახალ ცვლადს, ამრავლებს წინა მნიშვნელობას 2-ზე, რათა x-ს მისცეს 12 მნიშვნელობა. როდესაც ეს Scope სრულდება, შიდა shadowing-ი მთავრდება და x უბრუნდება 6-ად ყოფნას. როდესაც ამ პროგრამას გავაშვებთ, ის გამოიტანს შემდეგს:
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/variables`
The value of x in the inner scope is: 12
The value of x is: 6
Shadowing-ი განსხვავდება ცვლადის mut-ად აღნიშვნისგან, რადგან მივიღებთ კომპილაციის დროის შეცდომას, თუ შემთხვევით შევეცდებით ამ ცვლადისთვის ხელახლა მინიჭებას let საკვანძო სიტყვის გამოყენების გარეშე. let-ის გამოყენებით, შეგვიძლია შევასრულოთ რამდენიმე გარდაქმნა მნიშვნელობაზე, მაგრამ ცვლადი იყოს immutable ამ გარდაქმნების დასრულების შემდეგ.
სხვა განსხვავება mut-სა და shadowing-ს შორის არის ის, რომ რადგან ფაქტობრივად ვქმნით ახალ ცვლადს let საკვანძო სიტყვის ხელახლა გამოყენებისას, შეგვიძლია შევცვალოთ მნიშვნელობის ტიპი, თუმცა ხელახლა გამოვიყენოთ იგივე სახელი. მაგალითად, ვთქვათ, ჩვენი პროგრამა სთხოვს მომხმარებელს აჩვენოს, თუ რამდენი დაშორება სურს ტექსტებს შორის დაშორების პერსონაჟების შეყვანით, შემდეგ კი გვსურს ამ შეყვანილი მონაცემის რიცხვად შენახვა:
fn main() {
let spaces = " ";
let spaces = spaces.len();
}
პირველი spaces ცვლადი არის სტრიქონის ტიპი, ხოლო მეორე spaces ცვლადი არის რიცხვის ტიპი. Shadowing-ი ამრიგად გვზოგავს განსხვავებული სახელების მოფიქრებისგან, როგორიცაა spaces_str და spaces_num; ამის ნაცვლად, შეგვიძლია ხელახლა გამოვიყენოთ უფრო მარტივი spaces სახელი. თუმცა, თუ შევეცდებით ამისთვის mut-ის გამოყენებას, როგორც აქ არის ნაჩვენები, მივიღებთ კომპილაციის დროის შეცდომას:
fn main() {
let mut spaces = " ";
spaces = spaces.len();
}
შეცდომა ამბობს, რომ არ გვაქვს ცვლადის ტიპის შეცვლის (mutate) უფლება:
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
error[E0308]: mismatched types
--> src/main.rs:3:14
|
2 | let mut spaces = " ";
| ----- expected due to this value
3 | spaces = spaces.len();
| ^^^^^^^^^^^^ expected `&str`, found `usize`
For more information about this error, try `rustc --explain E0308`.
error: could not compile `variables` (bin "variables") due to 1 previous error
ახლა, როდესაც გამოვიკვლიეთ, თუ როგორ მუშაობს ცვლადები, განვიხილოთ მეტი მონაცემთა ტიპი, რომლებიც მათ შეიძლება ჰქონდეთ.