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 UnsafeCell
là invariant 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:
- Loại
bar
là Contravariant<&'x i32>
.
- 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
đó.
- Như vậy
bar
phải là một subtype của Contravariant<&'a i32>
Contravariant<T>
là contravariant trên T
, ví dụ:nếu U <: T
, sau đó Contravariant<T> <: Contravariant<U>
.
- Vì vậy, mối quan hệ phụ có thể được thỏa mãn khi
&'x i32
là supertype của &'a i32
.
- 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 -------+
Ô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ộ? –
@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
@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