อะไร "ไม่สามารถยืมเป็นไม่เปลี่ยนรูปเพราะมันก็ยืมเป็นไม่แน่นอน" หมายถึงในดัชนีอาร์เรย์ที่ซ้อนกัน?


16

ข้อผิดพลาดหมายถึงอะไรในกรณีนี้:

fn main() {
    let mut v: Vec<usize> = vec![1, 2, 3, 4, 5];
    v[v[1]] = 999;
}
error[E0502]: cannot borrow `v` as immutable because it is also borrowed as mutable
 --> src/main.rs:3:7
  |
3 |     v[v[1]] = 999;
  |     --^----
  |     | |
  |     | immutable borrow occurs here
  |     mutable borrow occurs here
  |     mutable borrow later used here

ผมพบว่าการจัดทำดัชนีที่จะดำเนินการผ่านทางIndexและIndexMutลักษณะและที่เป็นน้ำตาลประโยคสำหรับv[1] *v.index(1)ด้วยความรู้นี้ฉันพยายามรันโค้ดต่อไปนี้:

use std::ops::{Index, IndexMut};

fn main() {
    let mut v: Vec<usize> = vec![1, 2, 3, 4, 5];
    *v.index_mut(*v.index(1)) = 999;
}

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


2
เรียนรู้สนิมด้วยการถือกำเนิดของรหัส? ยินดีต้อนรับสู่ StackOverflow และขอบคุณสำหรับคำถามที่ยอดเยี่ยม!
Sven Marnach

แม่นยำ ) นี่เป็นปีที่ 3 ของฉันในการทำ (2x Haskell ก่อนหน้านั้น) ~> คิดว่าจะทำให้วนเป็นวงกลมตั้งแต่ฉันเริ่มสนใจสิ่งต่าง ๆ ในระดับต่ำมากขึ้น
Lucas Boucke

@ LucouBoucke มันตลกดีฉันมักจะใช้ Rust สำหรับโครงการของฉัน แต่ฉันเขียน AoC นี้ใน Haskell พวกเขาทั้งสองเป็นภาษาที่ยอดเยี่ยมในโดเมนของพวกเขา
Boiethios

คำตอบ:


16

รุ่นที่ได้รับการออกแบบแตกต่างจากที่คุณมีเล็กน้อย เส้น

v[v[1]] = 999;

อันที่จริงควรทำ

*IndexMut::index_mut(&mut v, *Index::index(&v, 1)) = 999;

ผลลัพธ์นี้มีข้อความแสดงข้อผิดพลาดเหมือนกัน แต่คำอธิบายประกอบให้คำแนะนำว่าเกิดอะไรขึ้น:

error[E0502]: cannot borrow `v` as immutable because it is also borrowed as mutable
 --> src/main.rs:7:48
  |
7 |     *IndexMut::index_mut(&mut v, *Index::index(&v, 1)) = 999;
  |      ------------------- ------                ^^ immutable borrow occurs here
  |      |                   |
  |      |                   mutable borrow occurs here
  |      mutable borrow later used by call

ความแตกต่างที่สำคัญสำหรับเวอร์ชันที่คุณต้องการคือลำดับการประเมินผล อาร์กิวเมนต์ของการเรียกใช้ฟังก์ชันจะถูกประเมินจากซ้ายไปขวาตามลำดับที่แสดงก่อนทำการเรียกใช้ฟังก์ชัน ในกรณีนี้หมายความว่าการ&mut vประเมินครั้งแรกเป็นการยืมvอย่างไม่แน่นอน ถัดไปIndex::index(&v, 1)ควรได้รับการประเมิน แต่เป็นไปไม่ได้ - vยืมไปแล้วอย่างไม่แน่นอน ในที่สุดคอมไพเลอร์แสดงว่าการอ้างอิงที่ไม่แน่นอนยังคงจำเป็นสำหรับการเรียกใช้ฟังก์ชันindex_mut()ดังนั้นการอ้างอิงที่ไม่แน่นอนจะยังคงมีชีวิตอยู่เมื่อพยายามอ้างอิงที่ใช้ร่วมกัน

รุ่นที่คอมไพล์จริงมีลำดับการประเมินแตกต่างกันเล็กน้อย

*v.index_mut(*v.index(1)) = 999;

ข้อแรกฟังก์ชันอาร์กิวเมนต์ของการเรียกใช้เมธอดจะถูกประเมินจากซ้ายไปขวาเช่น*v.index(1)ถูกประเมินก่อน สิ่งนี้ส่งผลให้ a usizeและเงินกู้ชั่วคราวที่ใช้ร่วมกันของvสามารถออกใหม่ได้อีกครั้ง จากนั้นindex_mut()จะทำการประเมินผู้รับซึ่งvก็คือการยืมอย่างไม่แน่นอน วิธีนี้ใช้งานได้ดีเนื่องจากการกู้ที่ใช้ร่วมกันได้รับการสรุปแล้วและการแสดงออกทั้งหมดผ่านการตรวจสอบการยืม

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

ทางออกที่สะอาดที่สุดในความคิดของฉันคือการใช้ตัวแปรชั่วคราว:

let i = v[1];
v[i] = 999;

ว้าว! มีหลายอย่างเกิดขึ้นที่นี่! ขอบคุณที่สละเวลาอธิบาย! (น่าสนใจว่า "นิสัยใจคอ" แบบนั้นทำให้ฉันสนใจภาษามากขึ้น ... ) คุณอาจจะให้คำใบ้เกี่ยวกับสาเหตุที่*v.index_mut(*v.index_mut(1)) = 999;ล้มเหลวด้วย "ไม่สามารถยืม v เป็นความแปรปรวนมากกว่าหนึ่งครั้ง" ~> ไม่ควรคอมไพเลอร์เช่นเดียวกับที่*v.index_mut(*v.index(1)) = 999;สามารถคิดได้ว่ายืมภายในไม่จำเป็นอีกต่อไป?
Lucas Boucke

@ LucouBoucke Rust มีนิสัยใจคอเล็กน้อยที่บางครั้งก็ไม่สะดวก แต่ในกรณีส่วนใหญ่การแก้ปัญหาค่อนข้างง่ายเหมือนในกรณีนี้ รหัสยังอ่านได้ค่อนข้างแตกต่างจากที่คุณมีอยู่เล็กน้อยดังนั้นในทางปฏิบัติมันไม่ใช่เรื่องใหญ่
Sven Marnach

@LucasBoucke ขออภัยฉันไม่เห็นการแก้ไขของคุณจนกระทั่งตอนนี้ ผลลัพธ์ของ*v.index(1)คือค่าที่เก็บไว้ที่ดัชนีนั้นและค่านั้นไม่จำเป็นต้องเก็บรักษาการยืมของvชีวิต ในทางกลับกันผลลัพธ์ของการแสดงออก*v.index_mut(1)เป็นสถานที่ที่ไม่แน่นอนที่อาจได้รับมอบหมายในทางทฤษฎีเพื่อให้มันยืมชีวิตยังคงอยู่ บนพื้นผิวมันควรจะเป็นไปได้ที่จะสอนผู้ยืมยืมว่าการแสดงออกของสถานที่ในบริบทการแสดงออกของค่าสามารถถือเป็นนิพจน์ค่าดังนั้นจึงเป็นไปได้ที่จะรวบรวมในรุ่นอนาคตของสนิม
Sven Marnach

วิธีการเกี่ยวกับ RFC เพื่อ desugar นี้เพื่อ:{ let index = *Index::index(&v, 1); let value = 999; *IndexMut::index_mut(&mut v, index) = value; }
Boiethios

@ FrenchBoiethios ฉันไม่รู้ว่าคุณทำพิธีแบบนั้นได้อย่างไรและฉันมั่นใจว่ามันจะไม่บินไปไหน หากคุณต้องการที่จะแก้ไขปัญหานี้วิธีเดียวที่ฉันเห็นคือการปรับปรุงตัวตรวจสอบการยืมเช่นการตรวจสอบว่าการยืมที่ไม่แน่นอนสามารถเริ่มได้ในเวลาต่อมาเนื่องจากไม่จำเป็นจริงๆในช่วงต้น (ความคิดเฉพาะนี้อาจไม่ได้ผลเช่นกัน)
Sven Marnach
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.