Referanslar ve Ödünç Alma
Liste 4-5'teki tanımlama grubu koduyla ilgili sorun, String
'i çağıran fonksiyona döndürmemizi gerektirmesidir,
böylece String
, calculate_length
çağrısından sonra hala String
'i kullanabiliriz, çünkü String
, içine taşınmıştır.
Bunun yerine, String
değerine bir referans sağlayabiliriz. Referans, bir işaretçi gibidir, çünkü o adreste depolanan verilere erişmek için takip edebileceğimiz bir adrestir; bu veriler başka bir değişkene aittir. Bir işaretçiden farklı olarak, bir referansın, o referansın ömrü boyunca belirli bir türün geçerli bir değerine işaret etmesi garanti edilir. Değerin sahipliğini almak yerine parametre olarak bir nesneye referansı olan bir
calculate_length
fonksiyonunu nasıl tanımlayacağınız ve kullanacağınız aşağıda açıklanmıştır:
Değerin sahipliğini almak yerine parametre olarak bir nesneye referansı olan bir calculate_length
fonksiyonunu nasıl tanımlayacağınız ve
kullanacağınız aşağıda açıklanmıştır:
Dosya adı: src/main.rs
fn main() { let s1 = String::from("hello"); let len = calculate_length(&s1); println!("The length of '{}' is {}.", s1, len); } fn calculate_length(s: &String) -> usize { s.len() }
İlk olarak, değişken bildirimindeki tüm tanımlama grubu kodunun ve fonksiyonun dönüş değerinin kaybolduğuna dikkat edin.
İkinci olarak, &s1
'i calculate_length
'e ilettiğimizi ve tanımında String
yerine &String
'i kullandığımızı unutmayın.
Bu ve bunun işaretleri, referansları temsil eder ve sahipliğini almadan bazı değerlere başvurmanıza izin verir.
Şekil 4-5 bu konsepti tasvir ediyor.
Not:
&
kullanılarak yapılan referansın tersi, referanstan çıkarma operatörü*
ile gerçekleştirilen referanstan çıkarma'dır. Bölüm 8'de referans kaldırma operatörünün bazı kullanımlarını göreceğiz ve Bölüm 15'te referans kaldırmanın ayrıntılarını tartışacağız.
Fonksiyon çağrısına daha yakından bakalım:
fn main() { let s1 = String::from("hello"); let len = calculate_length(&s1); println!("The length of '{}' is {}.", s1, len); } fn calculate_length(s: &String) -> usize { s.len() }
&s1
söz dizimi, s1
değerine refere eden ancak kendisine ait olmayan bir referans oluşturmamıza izin verir.
Kendisine ait olmadığı için, referansın kullanımı durduğunda işaret ettiği değer düşürülmeyecektir.
Benzer şekilde, fonksiyonun imzası, s
parametresinin türünün bir referans olduğunu belirtmek için &
kullanır.
Bazı açıklayıcı notlar ekleyelim:
fn main() { let s1 = String::from("hello"); let len = calculate_length(&s1); println!("The length of '{}' is {}.", s1, len); } fn calculate_length(s: &String) -> usize { // s is a reference to a String s.len() } // Here, s goes out of scope. But because it does not have ownership of what // it refers to, it is not dropped.
s
değişkeninin geçerli olduğu kapsam, herhangi bir fonksiyonla aynıdır.
Sahipliği olmadığı için s
kullanımı durduğunda parametrenin kapsamı bırakılır.
Ancak referansın gösterdiği değer bırakılmaz.
Sahipliği geri vermek için değerleri döndürmelisiniz, çünkü hiç sahibi olmadınız.
Peki ödünç aldığımız bir şeyi değiştirmeye çalışırsak ne olur? Liste 4-6'daki kodu deneyin. Uyarı: çalışmıyor!
Dosya adı: src/main.rs
fn main() {
let s = String::from("hello");
change(&s);
}
fn change(some_string: &String) {
some_string.push_str(", world");
}
İşte hata:
$ cargo run
Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference
--> src/main.rs:8:5
|
7 | fn change(some_string: &String) {
| ------- help: consider changing this to be a mutable reference: `&mut String`
8 | some_string.push_str(", world");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
For more information about this error, try `rustc --explain E0596`.
error: could not compile `ownership` due to previous error
Değişkenler varsayılan olarak değişmez olduğu gibi, referanslar da öyledir. Referansımız olan bir şeyi değiştirmemize izin verilmez.
Değişebilir Referanslar
Liste 4-6'daki kodu, ödünç alınan bir değeri, bunun yerine değişken bir referans kullanan sadece birkaç küçük ince ayar ile değiştirmemize izin verecek şekilde düzeltebiliriz.
Dosya adı: src/main.rs
fn main() { let mut s = String::from("hello"); change(&mut s); } fn change(some_string: &mut String) { some_string.push_str(", world"); }
İlk olarak, s
'i mut
olarak değiştiriyoruz.
Sonra &mut s
ile değiştirilebilen bir referans yaratır ve burada change
fonksiyonunu çağırırız ve
fonksiyon imzasını, some_string: &mut String
ile değiştirilebilir bir referansı kabul edecek şekilde güncelleriz.
Bu, change
fonksiyonunun ödünç aldığı değeri değiştireceğini açıkça ortaya koymaktadır.
Değişken referansların büyük bir kısıtlaması vardır: bir değere değiştirilebilir referansınız varsa,
o değere başka referansınız olamaz. s
için iki değişken referans oluşturmaya çalışan bu kod başarısız olur:
Dosya adı: src/main.rs
fn main() {
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
println!("{}, {}", r1, r2);
}
İşte hata:
$ cargo run
Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0499]: cannot borrow `s` as mutable more than once at a time
--> src/main.rs:5:14
|
4 | let r1 = &mut s;
| ------ first mutable borrow occurs here
5 | let r2 = &mut s;
| ^^^^^^ second mutable borrow occurs here
6 |
7 | println!("{}, {}", r1, r2);
| -- first borrow later used here
For more information about this error, try `rustc --explain E0499`.
error: could not compile `ownership` due to previous error
Bu hata, s
'i bir defada birden fazla değişken olarak ödünç alamayacağımız için bu kodun geçersiz olduğunu söylüyor.
İlk değişken ödünç alma r1
'dedir ve println!
'de kullanılana kadar sürmelidir, ancak bu değişken referansın oluşturulması ve
kullanımı arasında, r2
'de r1
ile aynı verileri ödünç alan başka bir değişken referans oluşturmaya çalıştık.
Aynı anda aynı verilere birden fazla değişken referansı engelleyen kısıtlama, değişkenliğe izin verir, ancak çok kontrollü bir şekilde. Bu, yeni Rustseverlerin uğraştığı bir şey çünkü çoğu dil, istediğiniz zaman değişkenliğe uğramanıza izin veriyor. Bu kısıtlamaya sahip olmanın yararı, Rust'ın derleme zamanında veri yarışlarını önleyebilmesidir. Bir veri yarışı, bir yarış durumuna benzer ve şu üç davranış gerçekleştiğinde gerçekleşir:
- İki veya daha fazla işaretçi aynı verilere aynı anda erişir.
- Verilere yazmak için işaretçilerden en az biri kullanılıyor.
- Verilere erişimi senkronize etmek için kullanılan hiçbir mekanizma yoktur.
Veri yarışları tanımsız davranışlara neden olur ve bunları çalışma zamanında izlemeye çalışırken teşhis edilmesi ve düzeltilmesi zor olabilir; Rust, veri yarışlarıyla dolu kodu derlemeyi reddederek bu sorunu önler!
Her zaman olduğu gibi, yeni bir kapsam oluşturmak için süslü parantezleri kullanabiliriz ve birden çok değişken referansa izin verebiliriz:
fn main() { let mut s = String::from("hello"); { let r1 = &mut s; } // r1 goes out of scope here, so we can make a new reference with no problems. let r2 = &mut s; }
Rust, değiştirilebilir ve değişmez referansları birleştirmek için benzer bir kural uygular. Bu kod hata verir:
fn main() {
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
let r3 = &mut s; // BIG PROBLEM
println!("{}, {}, and {}", r1, r2, r3);
}
İşte hata:
$ cargo run
Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> src/main.rs:6:14
|
4 | let r1 = &s; // no problem
| -- immutable borrow occurs here
5 | let r2 = &s; // no problem
6 | let r3 = &mut s; // BIG PROBLEM
| ^^^^^^ mutable borrow occurs here
7 |
8 | println!("{}, {}, and {}", r1, r2, r3);
| -- immutable borrow later used here
For more information about this error, try `rustc --explain E0502`.
error: could not compile `ownership` due to previous error
Vay be! Aynı değere değişmez bir referansımız varken değiştirilebilir bir referansımız da olamaz.
Değişmez bir referansın kullanıcıları, değerin aniden altlarında değişmesini beklemezler! Bununla birlikte, birden fazla değişmez referansa izin verilir, çünkü yalnızca verileri okuyan hiç kimse, başka birinin verileri okumasını etkileme yeteneğine sahip değildir.
Bir referansın kapsamının tanıtıldığı yerden başladığını ve o referansın son kullanıldığı zamana kadar devam ettiğini unutmayın.
Örneğin, değişmez referansların son kullanımı olan println!
, değiştirilebilir referans tanıtılmadan önce gerçekleştiği için bu kod derlenecektir:
fn main() { let mut s = String::from("hello"); let r1 = &s; // no problem let r2 = &s; // no problem println!("{} and {}", r1, r2); // variables r1 and r2 will not be used after this point let r3 = &mut s; // no problem println!("{}", r3); }
Değişmez r1
ve r2
referanslarının kapsamları println!
'den sonra biter. Bu kapsamlar çakışmaz, bu nedenle bu koda izin verilir.
Derleyicinin, kapsamın bitiminden önceki bir noktada bir referansın artık kullanılmadığını söyleyebilmesi, Sözcük Dışı Ömürlükler (kısaca NLL)
olarak adlandırılır ve bununla ilgili daha fazla bilgiyi The Edition Guide'dan okuyabilirsiniz.
Ödünç alma hataları bazen can sıkıcı olsa da, Rust derleyicisinin olası bir hatayı erkenden (çalışma zamanından ziyade derleme zamanında) ve sorunun tam olarak nerede olduğunu size gösterdiğini unutmayın. O zaman verilerinizin neden düşündüğünüz gibi olmadığını bulmak zorunda değilsiniz.
Sarkan Referanslar
İşaretçileri olan dillerde, belleğin bir kısmını boşaltırken, o belleğe bir işaretçiyi koruyarak, yanlışlıkla sarkan bir işaretçi (bellekte başka birine verilmiş olabilecek bir konuma başvuran bir işaretçi) oluşturmak kolaydır. Buna karşılık Rust'ta derleyici, referansların asla sarkan referanslar olmayacağını garanti eder: eğer bazı verilere referansınız varsa, derleyici, veriye yapılan referanstan önce verilerin kapsam dışına çıkmamasını sağlayacaktır.
Rust'ın derleme zamanı hatasıyla bunları nasıl önlediğini görmek için sarkan bir referans oluşturmaya çalışalım:
Dosya adı: src/main.rs
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s
}
İşte hata:
$ cargo run
Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0106]: missing lifetime specifier
--> src/main.rs:5:16
|
5 | fn dangle() -> &String {
| ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime
|
5 | fn dangle() -> &'static String {
| ~~~~~~~~
For more information about this error, try `rustc --explain E0106`.
error: could not compile `ownership` due to previous error
Bu hata mesajı, henüz ele almadığımız bir özelliğe atıfta bulunuyor: ömürler. Ömürleri Bölüm 10'da ayrıntılı olarak tartışacağız. Ancak, ömürlerle ilgili kısımları göz ardı ederseniz, mesaj bu kodun neden bir sorun olduğunun anahtarını içerir:
this function's return type contains a borrowed value, but there is no value
for it to be borrowed from
bu fonksiyonun dönüş türü ödünç alınan bir değer içeriyor, ancak ödünç alınacak bir değer yok
Sarkan kodumuzun her aşamasında tam olarak neler olduğuna daha yakından bakalım.
Dosya adı: src/main.rs
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String { // dangle returns a reference to a String
let s = String::from("hello"); // s is a new String
&s // we return a reference to the String, s
} // Here, s goes out of scope, and is dropped. Its memory goes away.
// Danger!
s
, dangle
içinde oluşturulduğundan, dangle
kodu bittiğinde, s
serbest bırakılır.
Ama ona bir referans döndürmeye çalıştık. Bu, bu referansın geçersiz bir String
'e işaret edeceği anlamına gelir.
Bu iyi bir fikir değildir! Rust bunu yapmamıza izin vermez.
Buradaki çözüm, String
'i doğrudan döndürmektir:
fn main() { let string = no_dangle(); } fn no_dangle() -> String { let s = String::from("hello"); s }
Bu herhangi bir sorun olmadan çalışır. Sahiplik taşınır ve hiçbir şey serbest bırakılmaz.
Referans Kuralları
Referanslar hakkında konuştuklarımızı gözden geçirelim:
-
Herhangi bir zamanda ya bir değişken referansa ya da istediğiniz sayıda değişmez referansa sahip olabilirsiniz.
-
Referanslar her zaman geçerli olmalıdır.
Bundan sonraki başlığımızda, farklı bir referans türü olan dilimlere bakacağız.