Saya membaca bab seumur hidup dari buku Rust, dan saya menemukan contoh ini untuk seumur hidup bernama / eksplisit:
struct Foo<'a> {
x: &'a i32,
}
fn main() {
let x; // -+ x goes into scope
// |
{ // |
let y = &5; // ---+ y goes into scope
let f = Foo { x: y }; // ---+ f goes into scope
x = &f.x; // | | error here
} // ---+ f and y go out of scope
// |
println!("{}", x); // |
} // -+ x goes out of scope
Cukup jelas bagi saya bahwa kesalahan yang dicegah oleh kompiler adalah penggunaan-setelah-bebas dari referensi yang ditugaskan untuk x
: setelah lingkup bagian dalam dilakukan, f
dan karena itu &f.x
menjadi tidak valid, dan seharusnya tidak ditugaskan x
.
Masalah saya adalah bahwa masalahnya dapat dengan mudah dianalisis tanpa menggunakan masa hidup eksplisit 'a
, misalnya dengan menyimpulkan penugasan ilegal dari referensi ke ruang lingkup yang lebih luas ( x = &f.x;
).
Dalam kasus-kasus mana saja masa hidup eksplisit sebenarnya diperlukan untuk mencegah kesalahan penggunaan-setelah-bebas (atau kelas lainnya?)?
reference
rust
static-analysis
lifetime
corazza
sumber
sumber
Jawaban:
Jawaban yang lain semua memiliki poin yang menonjol ( contoh konkret fjh di mana masa pakai eksplisit diperlukan ), tetapi kehilangan satu hal utama: mengapa masa hidup eksplisit diperlukan ketika kompiler akan memberi tahu Anda bahwa Anda salah paham ?
Ini sebenarnya pertanyaan yang sama dengan "mengapa jenis eksplisit diperlukan ketika kompiler dapat menyimpulkannya". Contoh hipotetis:
Tentu saja, kompiler dapat melihat bahwa saya mengembalikan
&'static str
, jadi mengapa programmer harus mengetiknya?Alasan utamanya adalah bahwa sementara kompiler dapat melihat apa yang kode Anda lakukan, ia tidak tahu apa maksud Anda.
Fungsi adalah batas alami untuk firewall efek dari perubahan kode. Jika kita membiarkan masa hidup sepenuhnya diperiksa dari kode, maka perubahan yang tampak tidak bersalah dapat memengaruhi masa hidup, yang kemudian dapat menyebabkan kesalahan dalam fungsi yang jauh. Ini bukan contoh hipotetis. Seperti yang saya pahami, Haskell memiliki masalah ini ketika Anda mengandalkan inferensi tipe untuk fungsi tingkat atas. Rust menggigit masalah tertentu sejak awal.
Ada juga manfaat efisiensi bagi kompiler - hanya tanda tangan fungsi yang perlu diuraikan untuk memverifikasi jenis dan masa pakai. Lebih penting lagi, ini memiliki manfaat efisiensi bagi programmer. Jika kami tidak memiliki masa hidup yang eksplisit, apa fungsi fungsi ini:
Tidak mungkin untuk mengetahui tanpa memeriksa sumbernya, yang akan bertentangan dengan sejumlah besar praktik pengkodean terbaik.
Lingkup adalah kehidupan, pada dasarnya. Sedikit lebih jelas, seumur hidup
'a
adalah parameter seumur hidup generik yang dapat dikhususkan dengan ruang lingkup tertentu pada waktu kompilasi, berdasarkan situs panggilan.Tidak semuanya. Masa hidup diperlukan untuk mencegah kesalahan, tetapi masa hidup yang eksplisit diperlukan untuk melindungi apa yang dimiliki sedikit programmer kewarasan.
sumber
f x = x + 1
tanpa tanda tangan jenis yang Anda gunakan dalam modul lain. Jika nanti Anda mengubah definisi menjadif x = sqrt $ x + 1
, tipenya berubah dariNum a => a -> a
menjadiFloating a => a -> a
, yang akan menyebabkan kesalahan ketik di semua situs panggilan tempatf
dipanggil denganInt
argumen misalnya . Memiliki tipe tanda tangan memastikan bahwa kesalahan terjadi secara lokal.sqrt $
, hanya kesalahan lokal akan terjadi setelah perubahan, dan tidak banyak kesalahan di tempat lain (yang jauh lebih baik jika kita tidak ingin mengubah tipe yang sebenarnya)?Mari kita lihat contoh berikut.
Di sini, masa hidup eksplisit itu penting. Ini mengkompilasi karena hasil
foo
memiliki seumur hidup yang sama dengan argumen pertama ('a
), sehingga mungkin hidup lebih lama dari argumen kedua. Ini diungkapkan oleh nama seumur hidup dalam tanda tanganfoo
. Jika Anda mengalihkan argumen dalam panggilan kefoo
kompiler akan mengeluh bahway
tidak hidup cukup lama:sumber
Anotasi seumur hidup dalam struktur berikut:
menetapkan bahwa
Foo
instance tidak boleh hidup lebih lama dari referensi yang dikandungnya (x
bidang).Contoh yang Anda temukan dalam buku Karat tidak menggambarkan ini karena
f
dany
variabel keluar dari ruang lingkup pada saat yang sama.Contoh yang lebih baik adalah ini:
Sekarang,
f
benar-benar hidup lebih lama dari variabel yang ditunjukkan olehf.x
.sumber
Perhatikan bahwa tidak ada masa hidup eksplisit dalam potongan kode itu, kecuali definisi struktur. Compiler dapat dengan sempurna menyimpulkan masa hidup
main()
.Dalam definisi tipe, bagaimanapun, masa hidup eksplisit tidak dapat dihindari. Misalnya, ada ambiguitas di sini:
Haruskah ini berbeda seumur hidup atau haruskah mereka sama? Itu penting dari perspektif penggunaan,
struct RefPair<'a, 'b>(&'a u32, &'b u32)
sangat berbeda daristruct RefPair<'a>(&'a u32, &'a u32)
.Sekarang, untuk kasus-kasus sederhana, seperti yang Anda berikan, kompiler secara teoritis dapat menghilangkan masa hidup seperti di tempat lain, tetapi kasus-kasus seperti itu sangat terbatas dan tidak bernilai kompleksitas tambahan dalam kompiler, dan perolehan kejelasan ini akan berada di paling tidak dipertanyakan.
sumber
'static
,'static
dapat digunakan di mana-mana di mana masa hidup lokal dapat digunakan, karena itu dalam contoh Andap
akan memiliki parameter masa pakai yang disimpulkan sebagai masa hidup lokaly
.RefPair<'a>(&'a u32, &'a u32)
berarti bahwa'a
akan menjadi persimpangan dari kedua input seumur hidup, yaitu dalam hal ini seumur hidupy
.Kasing dari buku ini sangat sederhana dengan desain. Topik kehidupan dianggap kompleks.
Kompiler tidak dapat dengan mudah menyimpulkan masa pakai dalam suatu fungsi dengan banyak argumen.
Juga, peti opsional saya sendiri memiliki
OptionBool
tipe denganas_slice
metode yang tanda tangannya sebenarnya adalah:Sama sekali tidak ada cara kompiler bisa menemukan yang keluar.
sumber
Saya telah menemukan penjelasan hebat lainnya di sini: http://doc.rust-lang.org/0.12.0/guide-lifetimes.html#returning-references .
sumber
Jika suatu fungsi menerima dua referensi sebagai argumen dan mengembalikan referensi, maka implementasi fungsi kadang-kadang mengembalikan referensi pertama dan kadang-kadang yang kedua. Tidak mungkin memprediksi referensi mana yang akan dikembalikan untuk panggilan yang diberikan. Dalam hal ini, tidak mungkin untuk menyimpulkan seumur hidup untuk referensi yang dikembalikan, karena setiap referensi argumen dapat merujuk ke variabel yang berbeda yang mengikat dengan masa hidup yang berbeda. Kehidupan eksplisit membantu untuk menghindari atau mengklarifikasi situasi seperti itu.
Demikian juga, jika struktur memegang dua referensi (sebagai dua bidang anggota) maka fungsi anggota struktur kadang-kadang dapat mengembalikan referensi pertama dan kadang-kadang yang kedua. Lagi-lagi masa hidup yang eksplisit mencegah ambiguitas semacam itu.
Dalam beberapa situasi sederhana, ada elision seumur hidup di mana kompiler dapat menyimpulkan masa hidup.
sumber
Alasan mengapa contoh Anda tidak berhasil adalah karena Rust hanya memiliki inferensi seumur hidup dan jenis lokal. Apa yang Anda sarankan menuntut inferensi global. Setiap kali Anda memiliki referensi yang masa hidupnya tidak dapat dihilangkan, itu harus dijelaskan.
sumber
Sebagai pendatang baru di Rust, pemahaman saya adalah bahwa kehidupan eksplisit melayani dua tujuan.
Menempatkan anotasi seumur hidup eksplisit pada fungsi membatasi jenis kode yang mungkin muncul di dalam fungsi itu. Masa hidup yang eksplisit memungkinkan kompiler memastikan bahwa program Anda melakukan apa yang Anda inginkan.
Jika Anda (kompiler) ingin memeriksa apakah sepotong kode valid, Anda (kompiler) tidak akan harus melihat ke dalam setiap fungsi yang dipanggil secara iteratif. Cukuplah untuk melihat penjelasan fungsi yang secara langsung dipanggil oleh potongan kode tersebut. Ini membuat program Anda lebih mudah untuk dipikirkan untuk Anda (kompiler), dan membuat waktu kompilasi dapat dikelola.
Pada poin 1., Pertimbangkan program berikut yang ditulis dalam Python:
yang akan dicetak
Perilaku seperti ini selalu mengejutkan saya. Apa yang terjadi adalah
df
berbagi memoriar
, jadi ketika beberapa kontendf
berubahwork
, perubahan itu juga menginfeksiar
. Namun, dalam beberapa kasus ini mungkin persis seperti yang Anda inginkan, karena alasan efisiensi memori (tanpa salinan). Masalah sebenarnya dalam kode ini adalah fungsinyasecond_row
mengembalikan baris pertama, bukan yang kedua; semoga berhasil debugging itu.Pertimbangkan program serupa yang ditulis dalam Rust:
Kompilasi ini, Anda dapatkan
Bahkan Anda mendapatkan dua kesalahan, ada juga satu dengan peran
'a
dan'b
dipertukarkan. Melihat anotasisecond_row
, kami menemukan bahwa output harus&mut &'b mut [i32]
, yaitu, output seharusnya menjadi referensi ke referensi dengan masa pakai'b
(masa baris keduaArray
). Namun, karena kita mengembalikan baris pertama (yang memiliki masa pakai'a
), kompiler mengeluh tentang ketidakcocokan seumur hidup. Di tempat yang tepat. Di waktu yang tepat. Debugging sangat mudah.sumber
Saya menganggap anotasi seumur hidup sebagai kontrak tentang referensi yang diberikan hanya berlaku dalam lingkup penerima saja sementara itu masih berlaku dalam lingkup sumber. Mendeklarasikan lebih banyak referensi dalam jenis kehidupan yang sama menggabungkan lingkup, yang berarti bahwa semua sumber referensi harus memenuhi kontrak ini. Anotasi semacam itu memungkinkan penyusun untuk memeriksa pemenuhan kontrak.
sumber