Không thể vay được những gì không thể thay đổi bởi vì nó cũng được mượn như là một biến đổi có nghĩa là trong một chỉ số mảng lồng nhau?


16

Lỗi này có nghĩa gì trong trường hợp này:

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

Tôi thấy rằng lập chỉ mục được thực hiện thông qua IndexIndexMutcác đặc điểm và rằngv[1] là cú pháp đường cho *v.index(1). Được trang bị kiến ​​thức này, tôi đã thử chạy đoạn mã sau:

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;
}

Thật ngạc nhiên, điều này hoạt động hoàn hảo! Tại sao đoạn mã đầu tiên không hoạt động, nhưng đoạn thứ hai thì không? Theo cách tôi hiểu tài liệu, chúng nên tương đương, nhưng rõ ràng đây không phải là trường hợp.


2
Học Rust với Advent of Code? Chào mừng bạn đến với StackOverflow và cảm ơn câu hỏi tuyệt vời!
Sven Marnach

Đúng ; ) Đây là năm thứ 3 tôi làm việc đó (gấp 2 lần Haskell trước đó) ~> nghĩ sẽ tạo cho Rust một vòng xoáy kể từ khi tôi bắt đầu quan tâm nhiều hơn đến những thứ cấp thấp
Lucas Boucke

@LucasBoucke Thật buồn cười, tôi thường sử dụng Rust cho dự án của mình, nhưng tôi viết AoC này bằng Haskell. Cả hai đều là ngôn ngữ tuyệt vời trong miền của họ.
Boiethios

Câu trả lời:


16

Phiên bản desugared hơi khác so với những gì bạn có. Dòng

v[v[1]] = 999;

thực sự desugars để

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

Điều này dẫn đến cùng một thông báo lỗi, nhưng các chú thích đưa ra gợi ý về những gì đang xảy ra:

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

Sự khác biệt quan trọng đối với phiên bản bỏ hoang của bạn là thứ tự đánh giá. Các đối số của một lệnh gọi hàm được đánh giá từ trái sang phải theo thứ tự được liệt kê, trước khi thực sự thực hiện cuộc gọi hàm. Trong trường hợp này, điều này có nghĩa là đầu tiên &mut vđược đánh giá, vay tương đối v. Tiếp theo, Index::index(&v, 1)nên được đánh giá, nhưng điều này là không thể -v đã được vay mượn lẫn nhau. Cuối cùng, trình biên dịch cho thấy rằng tham chiếu có thể thay đổi vẫn cần thiết cho lệnh gọi hàm index_mut(), vì vậy tham chiếu có thể thay đổi vẫn còn tồn tại khi tham chiếu chia sẻ được thử.

Phiên bản thực sự biên dịch có thứ tự đánh giá hơi khác nhau.

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

Đầu tiên, các đối số hàm cho các lệnh gọi phương thức được ước tính từ trái sang phải, tức *v.index(1)là được ước tính trước. Điều này dẫn đến một usize, và khoản vay chia sẻ tạm thời vcó thể được phát hành lại. Sau đó, người nhậnindex_mut() được đánh giá, tức vlà được vay mượn. Điều này hoạt động tốt, vì khoản vay được chia sẻ đã được hoàn thành và toàn bộ biểu thức vượt qua trình kiểm tra khoản vay.

Lưu ý rằng phiên bản biên dịch chỉ làm như vậy kể từ khi giới thiệu "vòng đời phi từ vựng". Trong các phiên bản trước của Rust, khoản vay được chia sẻ sẽ tồn tại cho đến khi kết thúc biểu thức và dẫn đến một lỗi tương tự.

Theo tôi, giải pháp sạch nhất là sử dụng một biến tạm thời:

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

Ái chà! Có rất nhiều thứ đang diễn ra ở đây! Cảm ơn đã dành thời gian để giải thích! (thú vị là những "quirks" đó làm cho một ngôn ngữ thú vị hơn với tôi ...). Bạn cũng có thể đưa ra một gợi ý về lý do tại sao *v.index_mut(*v.index_mut(1)) = 999;thất bại với "không thể mượn v như có thể thay đổi nhiều lần" ~> không nên trình biên dịch, như có *v.index_mut(*v.index(1)) = 999;thể nhận ra rằng khoản vay bên trong không còn cần thiết nữa?
Lucas Boucke

@LucasBoucke Rust có một vài quirks đôi khi hơi bất tiện, nhưng trong hầu hết các trường hợp, giải pháp khá đơn giản, như trong trường hợp này. Mã này vẫn còn khá dễ đọc, chỉ cần một chút khác nhau hơn những gì bạn ban đầu đã có, vì vậy trong thực tế nó không phải là một vấn đề lớn.
Sven Marnach

@LucasBoucke Xin lỗi, tôi đã không thấy chỉnh sửa của bạn cho đến bây giờ. Kết quả *v.index(1)giá trị được lưu trữ tại chỉ mục đó và giá trị đó không bắt buộc phải giữ cho khoản vay vcòn sống. Mặt khác, kết quả *v.index_mut(1)là một biểu thức địa điểm có thể thay đổi về mặt lý thuyết có thể được gán cho, vì vậy nó giữ cho khoản vay còn sống. Nhìn bề ngoài, có thể dạy cho người kiểm tra mượn rằng một biểu thức địa điểm trong ngữ cảnh biểu thức giá trị có thể được coi là biểu thức giá trị, vì vậy có thể điều này sẽ biên dịch trong một số phiên bản tương lai của Rust.
Sven Marnach

Làm thế nào về một RFC để giải quyết điều này thành:{ let index = *Index::index(&v, 1); let value = 999; *IndexMut::index_mut(&mut v, index) = value; }
Boiethios

@FblerBoiethios Tôi không biết làm thế nào bạn chính thức hóa điều đó, và tôi chắc chắn rằng nó sẽ không bao giờ bị bay. Nếu bạn muốn giải quyết vấn đề này, cách duy nhất tôi thấy là bằng các cải tiến cho trình kiểm tra khoản vay, ví dụ: làm cho nó phát hiện ra rằng khoản vay có thể thay đổi có thể bắt đầu sau đó, vì nó không thực sự cần thiết sớm. (Ý tưởng đặc biệt này có lẽ cũng không hoạt động.)
Sven Marnach
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.