2017-04-04 16 views
10
fn works<'a>(foo: &Option<&'a mut String>, s: &'a mut String) {} 
fn error<'a>(foo: &RefCell<Option<&'a mut String>>, s: &'a mut String) {} 

let mut s = "hi".to_string(); 

let foo = None; 
works(&foo, &mut s); 

// with this, it errors 
// let bar = RefCell::new(None); 
// error(&bar, &mut s); 

s.len(); 

Nếu tôi đặt trong hai dòng với những nhận xét, các lỗi sau xảy ra:Lỗi này có phải do kiến ​​thức đặc biệt của trình biên dịch về RefCell không?

error[E0502]: cannot borrow `s` as immutable because it is also borrowed as mutable 
    --> <anon>:16:5 
    | 
14 |  error(&bar, &mut s); 
    |      - mutable borrow occurs here 
15 |  
16 |  s.len(); 
    | ^immutable borrow occurs here 
17 | } 
    | - mutable borrow ends here 

Chữ ký của works()errors() trông khá tương tự. Nhưng dường như trình biên dịch biết rằng bạn có thể lừa dối nó với một số RefCell, bởi vì người mượn tiền cư xử khác nhau.

Tôi thậm chí có thể "ẩn" RefCell trong một loại khác của riêng tôi, nhưng trình biên dịch vẫn luôn làm điều đúng (lỗi trong trường hợp có thể sử dụng RefCell). Trình biên dịch biết tất cả các công cụ đó như thế nào và nó hoạt động như thế nào? Trình biên dịch có đánh dấu các loại là "thùng chứa khả năng thay đổi nội thất" hay cái gì đó giống như vậy không?

Trả lời

8

RefCell<T> chứa một số UnsafeCell<T> là đặc biệt lang item. Đó là UnsafeCell gây ra lỗi. Bạn có thể kiểm tra với:

fn error<'a>(foo: &UnsafeCell<Option<&'a mut String>>, s: &'a mut String) {} 

... 

let bar = UnsafeCell::new(None); 
error(&bar, &mut s); 

Nhưng lỗi không phải do trình biên dịch nhận một UnsafeCell giới thiệu mutability nội thất, nhưng điều đó một UnsafeCellinvariant trong T. Trong thực tế, chúng ta có thể sao chép các lỗi sử dụng PhantomData:

struct Contravariant<T>(PhantomData<fn(T)>); 

fn error<'a>(foo: Contravariant<&'a i32>, s: &'a mut String) {} 

... 

let bar = Contravariant(PhantomData); 
error(bar, &mut s); 

hoặc thậm chí chỉ cần bất cứ điều gì đó là contravariant hoặc bất biến trong đời 'a:

fn error<'a>(foo: Option<fn(&'a i32)>, s: &'a mut String) {} 

let bar = None; 
error(bar, &mut s); 

Lý do bạn không thể ẩn RefCell là do phương sai được dẫn xuất thông qua các trường của cấu trúc. Khi bạn đã sử dụng RefCell<T> ở đâu đó, cho dù sâu bao nhiêu, trình biên dịch sẽ tìm ra T là bất biến.


Bây giờ hãy xem cách trình biên dịch xác định lỗi E0502. Trước tiên, điều quan trọng cần nhớ là trình biên dịch phải chọn hai thời gian sống cụ thể ở đây: tuổi thọ theo loại biểu thức &mut s ('a) và tuổi thọ theo loại bar (gọi là 'x). Cả hai đều bị hạn chế: tuổi thọ trước đây là 'a phải ngắn hơn phạm vi s, nếu không chúng tôi sẽ kết thúc bằng tham chiếu dài hơn chuỗi gốc. 'x phải lớn hơn phạm vi bar, nếu không chúng tôi có thể truy cập một con trỏ lơ lửng qua bar (nếu loại có tham số suốt đời thì trình biên dịch giả định loại có thể truy cập giá trị với thời gian đó).

Với những hạn chế cơ bản hai, trình biên dịch đi qua các bước sau:

  1. Loại barContravariant<&'x i32>.
  2. Chức năng error chấp nhận bất kỳ loại phụ nào là Contravariant<&'a i32>, trong đó 'a là thời gian tồn tại của biểu thức &mut s đó.
  3. Như vậy bar phải là một subtype của Contravariant<&'a i32>
  4. Contravariant<T> là contravariant trên T, ví dụ:nếu U <: T, sau đó Contravariant<T> <: Contravariant<U>.
  5. Vì vậy, mối quan hệ phụ có thể được thỏa mãn khi &'x i32supertype của &'a i32.
  6. Như vậy 'x nên ngắn hơn hơn 'a, ví dụ: 'a nên sống lâu hơn'x.

Tương tự như vậy, đối với một loại bất biến, mối quan hệ xuất phát là 'a == 'x, và cho convariant, 'x outlives 'a.

Bây giờ, vấn đề ở đây là thời gian tồn tại trong loại của bar cuộc sống cho đến hết phạm vi (theo hạn chế nêu trên):

let bar = Contravariant(PhantomData); // <--- 'x starts here -----+ 
    error(bar,        //       | 
      &mut s);       // <- 'a starts here ---+ | 
    s.len();        //      | | 
              // <--- 'x ends here¹ --+---+ 
              //      | 
              // <--- 'a ends here² --+ 
} 

// ¹ when `bar` goes out of scope 
// ² 'a has to outlive 'x 

Trong cả hai contravariant và các trường hợp bất biến, 'a outlives (hoặc bằng) 'x có nghĩa là tuyên bố s.len() phải được bao gồm trong phạm vi, gây ra lỗi mượn.

Chỉ trong trường hợp hiệp biến chúng ta có thể làm cho phạm vi của 'a ngắn hơn 'x, cho phép các đối tượng tạm thời &mut s được thả trước s.len() được gọi (nghĩa là: tại s.len(), s không được coi là mượn nữa):

let bar = Covariant(PhantomData);  // <--- 'x starts here -----+ 
              //       | 
    error(bar,        //       | 
      &mut s);       // <- 'a starts here --+ | 
              //      | | 
              // <- 'a ends here ----+ | 
    s.len();        //       | 
}           // <--- 'x ends here -------+ 
+0

Ôi trời! Thật là một câu trả lời tuyệt vời! Tôi đã sợ rằng câu trả lời sẽ đơn giản là "RefCell' là đặc biệt", nhưng đây là một cái nhìn sâu sắc tuyệt vời. Cảm ơn ♥ Một câu hỏi, mặc dù: trong trường hợp 'foo: Option ', chúng ta có thể thực sự phá vỡ an toàn bộ nhớ không? Hoặc về cơ bản nó là một dương tính giả do cách trình biên dịch suy nghĩ nội bộ? –

+0

@LukasKalbertodt Nếu đó là giải thích bổ sung, không phải câu hỏi, cảm thấy tự do. (Nếu đó là câu hỏi có liên quan bổ sung mà hộp nhận xét quá nhỏ, hãy chỉnh sửa câu hỏi thay thế). – kennytm

+0

@LukasKalbertodt Tôi tin rằng trường hợp 'Tùy chọn ' là một dương tính giả có thể được giải quyết bằng cuộc sống không từ vựng. Nhưng tôi đã không kiểm tra chi tiết, có lẽ nó sẽ có một số tương tác xấu với chủ đề. – kennytm

Các vấn đề liên quan