ช่วงชีวิตที่ไม่ใช่ศัพท์คืออะไร?


93

Rust มีRFCที่เกี่ยวข้องกับอายุขัยที่ไม่ใช่ศัพท์ซึ่งได้รับการอนุมัติให้ใช้งานในภาษาเป็นเวลานาน เมื่อเร็ว ๆ นี้การรองรับฟีเจอร์นี้ของ Rust ได้พัฒนาไปมากและถือว่าสมบูรณ์

คำถามของฉันคืออายุการใช้งานที่ไม่ใช่ศัพท์คืออะไร?

คำตอบ:


139

ง่ายที่สุดที่จะเข้าใจว่าช่วงชีวิตที่ไม่ใช่ศัพท์คืออะไรโดยการทำความเข้าใจว่าช่วงชีวิตของศัพท์คืออะไร ในเวอร์ชัน Rust ก่อนอายุขัยที่ไม่ใช่คำศัพท์รหัสนี้จะล้มเหลว:

fn main() {
    let mut scores = vec![1, 2, 3];
    let score = &scores[0];
    scores.push(4);
}

คอมไพเลอร์ Rust เห็นว่าscoresถูกยืมโดยscoreตัวแปรดังนั้นจึงไม่อนุญาตให้มีการกลายพันธุ์เพิ่มเติมของscores:

error[E0502]: cannot borrow `scores` as mutable because it is also borrowed as immutable
 --> src/main.rs:4:5
  |
3 |     let score = &scores[0];
  |                  ------ immutable borrow occurs here
4 |     scores.push(4);
  |     ^^^^^^ mutable borrow occurs here
5 | }
  | - immutable borrow ends here

แต่มนุษย์นิดจะเห็นว่าตัวอย่างนี้เป็นอนุรักษ์นิยมมากเกินไป: scoreจะไม่เคยใช้ ! ปัญหาคือการยืมscoresโดยscoreเป็นศัพท์ - จะคงอยู่จนกว่าจะสิ้นสุดบล็อกที่มีอยู่:

fn main() {
    let mut scores = vec![1, 2, 3]; //
    let score = &scores[0];         //
    scores.push(4);                 //
                                    // <-- score stops borrowing here
}

อายุการใช้งานที่ไม่ใช่ศัพท์แก้ไขปัญหานี้โดยการปรับปรุงคอมไพเลอร์ให้เข้าใจรายละเอียดระดับนี้ ขณะนี้คอมไพเลอร์สามารถบอกได้อย่างแม่นยำมากขึ้นว่าเมื่อใดจำเป็นต้องมีการยืมและรหัสนี้จะคอมไพล์

สิ่งที่ยอดเยี่ยมเกี่ยวกับอายุขัยที่ไม่ใช่คำศัพท์คือเมื่อเปิดใช้งานแล้วจะไม่มีใครคิดถึงสิ่งเหล่านี้ มันจะกลายเป็น "สิ่งที่ Rust ทำ" และสิ่งต่างๆ (หวังว่า) จะได้ผล

เหตุใดจึงอนุญาตให้ใช้ศัพท์ตลอดชีวิตได้?

Rust มีไว้เพื่ออนุญาตให้คอมไพล์โปรแกรมที่รู้จักปลอดภัยเท่านั้น อย่างไรก็ตามเป็นไปไม่ได้เลยที่จะอนุญาตเฉพาะโปรแกรมที่ปลอดภัยและปฏิเสธโปรแกรมที่ไม่ปลอดภัย ด้วยเหตุนี้ Rust จึงทำผิดในด้านของการอนุรักษ์นิยม: โปรแกรมปลอดภัยบางโปรแกรมถูกปฏิเสธ คำศัพท์ตลอดชีวิตเป็นตัวอย่างหนึ่งของสิ่งนี้

อายุการใช้งานของคำศัพท์นั้นง่ายกว่ามากในการนำไปใช้ในคอมไพเลอร์เนื่องจากความรู้เกี่ยวกับบล็อกนั้น "ไม่สำคัญ" ในขณะที่ความรู้เกี่ยวกับการไหลของข้อมูลมีน้อยกว่า คอมไพเลอร์จะต้องเขียนใหม่ที่จะแนะนำและทำให้การใช้งานของ "ระดับกลางการแสดงกลาง" (MIR) จากนั้นตัวตรวจสอบการยืม (หรือที่เรียกว่า "ยืม") จะต้องเขียนใหม่เพื่อใช้ MIR แทนโครงสร้างไวยากรณ์นามธรรม (AST) จากนั้นกฎของผู้ตรวจการยืมจะต้องได้รับการขัดเกลาให้มีความละเอียดรอบคอบ

อายุการใช้งานของคำศัพท์ไม่ได้เป็นอุปสรรคต่อโปรแกรมเมอร์เสมอไปและมีหลายวิธีในการทำงานกับช่วงชีวิตของศัพท์แม้ว่าพวกเขาจะน่ารำคาญก็ตาม ในหลาย ๆ กรณีสิ่งนี้เกี่ยวข้องกับการเพิ่มวงเล็บปีกกาพิเศษหรือค่าบูลีน สิ่งนี้ทำให้ Rust 1.0 สามารถจัดส่งได้และมีประโยชน์เป็นเวลาหลายปีก่อนที่จะนำอายุการใช้งานที่ไม่ใช่ศัพท์มาใช้

ที่น่าสนใจคือรูปแบบที่ดีบางอย่างได้รับการพัฒนาเนื่องจากอายุการใช้งานของศัพท์ ตัวอย่างที่สำคัญให้ฉันเป็นรูปแบบ รหัสนี้ล้มเหลวก่อนอายุขัยที่ไม่ใช่ศัพท์และรวบรวมด้วย:entry

fn example(mut map: HashMap<i32, i32>, key: i32) {
    match map.get_mut(&key) {
        Some(value) => *value += 1,
        None => {
            map.insert(key, 1);
        }
    }
}

อย่างไรก็ตามรหัสนี้ไม่มีประสิทธิภาพเนื่องจากคำนวณแฮชของคีย์สองครั้ง โซลูชันที่สร้างขึ้นเนื่องจากอายุการใช้งานของศัพท์สั้นลงและมีประสิทธิภาพมากขึ้น:

fn example(mut map: HashMap<i32, i32>, key: i32) {
    *map.entry(key).or_insert(0) += 1;
}

ชื่อ "non-lexical lifetimes" ไม่เหมาะกับฉัน

อายุการใช้งานของค่าคือช่วงเวลาที่ค่ายังคงอยู่ในที่อยู่หน่วยความจำเฉพาะ (ดูเหตุใดฉันจึงไม่สามารถจัดเก็บค่าและการอ้างอิงถึงค่านั้นในโครงสร้างเดียวกันสำหรับคำอธิบายที่ยาวขึ้น) คุณลักษณะที่เรียกว่าอายุขัยที่ไม่ใช่คำศัพท์ไม่ได้เปลี่ยนอายุการใช้งานของค่าใด ๆ ดังนั้นจึงไม่สามารถทำให้อายุการใช้งานไม่เป็นศัพท์ได้ เพียงทำให้การติดตามและตรวจสอบการยืมของค่าเหล่านั้นแม่นยำยิ่งขึ้น

ชื่อที่ถูกต้องกว่าสำหรับคุณลักษณะนี้อาจเป็น "คำยืมที่ไม่ใช่คำศัพท์" ผู้พัฒนาคอมไพเลอร์บางรายอ้างถึง "MIR-basedorrowck"

อายุการใช้งานที่ไม่เป็นคำศัพท์ที่ไม่เคยมีเจตนาที่จะเป็น "ผู้ใช้หันหน้าไปทางคุณลักษณะ" ต่อ se พวกเขาเติบโตขึ้นอย่างมากในความคิดของเราเพราะกระดาษตัดกระดาษเล็ก ๆ น้อย ๆ ที่เราได้รับจากการที่พวกเขาไม่อยู่ ชื่อของพวกเขาส่วนใหญ่มีวัตถุประสงค์เพื่อการพัฒนาภายในและการเปลี่ยนชื่อเพื่อวัตถุประสงค์ทางการตลาดไม่เคยมีความสำคัญ

ใช่ แต่ฉันจะใช้มันอย่างไร?

ใน Rust 1.31 (เผยแพร่เมื่อ 2018-12-06) คุณต้องเลือกใช้รุ่น Rust 2018 ใน Cargo.toml ของคุณ:

[package]
name = "foo"
version = "0.0.1"
authors = ["An Devloper <an.devloper@example.com>"]
edition = "2018"

สำหรับ Rust 1.36 รุ่น Rust 2015 ยังเปิดใช้งานอายุการใช้งานที่ไม่ใช่ศัพท์

การใช้อายุการใช้งานที่ไม่ใช่ศัพท์ในปัจจุบันอยู่ใน "โหมดการย้ายข้อมูล" หากตัวตรวจสอบการยืม NLL ผ่านการคอมไพล์จะดำเนินต่อไป หากไม่เป็นเช่นนั้นระบบจะเรียกใช้ตัวตรวจสอบการยืมก่อนหน้า หากตัวตรวจสอบการยืมเก่าอนุญาตรหัสจะมีการพิมพ์คำเตือนเพื่อแจ้งให้คุณทราบว่ารหัสของคุณมีแนวโน้มที่จะพังในรุ่น Rust ในอนาคตและควรได้รับการอัปเดต

ใน Rust เวอร์ชันกลางคืนคุณสามารถเลือกใช้การแตกที่บังคับใช้ผ่านแฟล็กคุณลักษณะ:

#![feature(nll)]

-Z poloniusคุณยังสามารถเลือกในการให้เป็นเวอร์ชันทดลองของสาวน้อยโดยใช้ธงคอมไพเลอร์

ตัวอย่างปัญหาที่แท้จริงที่แก้ไขได้โดยช่วงชีวิตที่ไม่ใช่ศัพท์


12
ฉันคิดว่ามันจะคุ้มค่าที่จะเน้นย้ำว่าบางทีชีวิตที่ไม่ใช่คำศัพท์อาจไม่เกี่ยวกับอายุการใช้งานของตัวแปร แต่เกี่ยวกับ Lifetime of Borrows หรือกล่าวอีกอย่างว่า Non-Lexical Lifetimes เป็นเรื่องเกี่ยวกับการตกแต่งที่เกี่ยวข้องกับอายุการใช้งานของตัวแปรจากการยืม ... เว้นแต่ฉันจะผิด? (แต่ฉันไม่คิดว่า NLL จะเปลี่ยนไปเมื่อมีการดำเนินการทำลายล้าง)
Matthieu M.

2
"ที่น่าสนใจคือรูปแบบที่ดีบางอย่างได้รับการพัฒนาเนื่องจากอายุการใช้งานของศัพท์ " - ฉันคิดว่ามีความเสี่ยงที่การมีอยู่ของ NLL อาจทำให้รูปแบบที่ดีในอนาคตระบุได้ยากขึ้น?
eggyal

1
@eggyal เป็นไปได้อย่างแน่นอน การออกแบบภายในชุดข้อ จำกัด (แม้ว่าโดยพลการ!) สามารถนำไปสู่การออกแบบใหม่ที่น่าสนใจ หากไม่มีข้อ จำกัด เหล่านั้นเราอาจถอยกลับไปใช้ความรู้และรูปแบบที่มีอยู่และไม่เคยเรียนรู้หรือสำรวจเพื่อค้นหาสิ่งใหม่ ๆ ตามที่กล่าวไว้น่าจะมีคนคิดว่า "โอ้กำลังคำนวณแฮชสองครั้งฉันแก้ไขได้" และ API จะถูกสร้างขึ้น แต่อาจจะยากกว่าสำหรับผู้ใช้ในการค้นหา API ในตอนแรก ฉันหวังว่าเครื่องมือเช่นclippy จะช่วยคนเหล่านั้นได้
Shepmaster
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.