2015-06-05 13 views
8

Tôi đang gặp sự cố khi hiểu các quy tắc về các đặc điểm trong các loại dữ liệu đại số. Dưới đây là một ví dụ đơn giản:Đặc điểm trong các loại dữ liệu đại số

use std::rc::Rc; 
use std::cell::RefCell; 

trait Quack { 
    fn quack(&self); 
} 

struct Duck; 

impl Quack for Duck { 
    fn quack(&self) { println!("Quack!"); } 
} 

fn main() { 
    let mut pond: Vec<Box<Quack>> = Vec::new(); 
    let duck: Box<Duck> = Box::new(Duck); 
    pond.push(duck); // This is valid. 

    let mut lake: Vec<Rc<RefCell<Box<Quack>>>> = Vec::new(); 
    let mallard: Rc<RefCell<Box<Duck>>> = Rc::new(RefCell::new(Box::new(Duck))); 
    lake.push(mallard); // This is a type mismatch. 
} 

Trên đây thất bại trong việc biên dịch, năng suất thông báo lỗi sau:

expected `alloc::rc::Rc<core::cell::RefCell<Box<Quack>>>`, 
    found `alloc::rc::Rc<core::cell::RefCell<Box<Duck>>>` 
(expected trait Quack, 
    found struct `Duck`) [E0308] 
src/main.rs:19  lake.push(mallard); 

Tại sao nó rằng pond.push(duck) là hợp lệ, nhưng lake.push(mallard) không? Trong cả hai trường hợp, một số Duck đã được cung cấp trong đó Quack được mong đợi. Trước đây, trình biên dịch là hạnh phúc, nhưng ở phần sau, nó không phải.

Lý do cho sự khác biệt này có liên quan đến CoerceUnsized không?

+0

Lưu ý: 'RefCell' là không cần thiết ở đây, tôi có thể tạo lại vấn đề với' Rc > '. –

+0

Matthieu, bạn là chính xác. 'RefCell' là không cần thiết để làm cho lỗi xảy ra. Dựa trên câu trả lời của Vladimir dưới đây, tôi có thể thấy lý do tại sao cùng một lỗi xảy ra có hoặc không có 'RefCell'. – rlkw1024

Trả lời

6

Đây là hành vi đúng, ngay cả khi nó có phần không may.

Trong trường hợp đầu tiên chúng tôi có điều này:

Lưu ý rằng push(), khi kêu gọi Vec<Box<Quack>>, chấp nhận Box<Quack>, và bạn đang đi qua Box<Duck>. Đây là OK - rustc có thể hiểu rằng bạn muốn chuyển đổi một giá trị đóng hộp đến một đối tượng đặc điểm, như ở đây:

let duck: Box<Duck> = Box::new(Duck); 
let quack: Box<Quack> = duck; // automatic coercion to a trait object 

Trong trường hợp thứ hai, chúng tôi đã này:

let mut lake: Vec<Rc<RefCell<Box<Quack>>>> = Vec::new(); 
let mallard: Rc<RefCell<Box<Duck>>> = Rc::new(RefCell::new(Box::new(Duck))); 
lake.push(mallard); 

đây push() chấp nhận Rc<RefCell<Box<Quack>>> khi bạn cung cấp Rc<RefCell<Box<Duck>>>:

let mallard: Rc<RefCell<Box<Duck>>> = Rc::new(RefCell::new(Box::new(Duck))); 
let quack: Rc<RefCell<Box<Quack>>> = mallard; 

Và hiện tại có sự cố. Box<T> là loại tương thích DST, do đó, nó có thể được sử dụng làm vùng chứa cho đối tượng đặc điểm. Điều tương tự sẽ sớm đúng với Rc và các con trỏ thông minh khác khi this RFC được triển khai. Tuy nhiên, trong trường hợp này không có sự ép buộc từ một loại bê tông đến một đối tượng đặc điểm vì Box<Duck> nằm bên trong các lớp bổ sung của các loại (Rc<RefCell<..>>).

Hãy nhớ rằng, đối tượng đặc điểm là con trỏ chất béo, vì vậy, Box<Duck> khác với kích thước Box<Quack>. Do đó, về nguyên tắc, chúng không tương thích trực tiếp: bạn không thể chỉ lấy byte của Box<Duck> và ghi chúng vào nơi Box<Quack> được mong đợi. Rust thực hiện một chuyển đổi đặc biệt, tức là, nó lấy một con trỏ tới bảng ảo cho Duck, tạo một con trỏ chất béo và ghi nó vào biến số Box<Quack>.

Khi bạn có Rc<RefCell<Box<Duck>>>, tuy nhiên, rustc sẽ cần phải biết cách xây dựng và hủy cấu trúc cả hai RefCellRc để áp dụng cùng một chuyển đổi con trỏ chất béo cho nội bộ của nó. Đương nhiên, bởi vì đây là những loại thư viện, nó không thể biết làm thế nào để làm điều đó. Điều này cũng đúng với bất kỳ loại trình bao bọc nào khác, ví dụ: Arc hoặc Mutex hoặc thậm chí Vec. Bạn không mong đợi rằng có thể sử dụng Vec<Box<Duck>> làm Vec<Box<Quack>>, phải không?Cũng có một thực tế là trong ví dụ với Rc các Rcs được tạo ra từ Box<Duck>Box<Quack> sẽ không được kết nối - chúng sẽ có các bộ tham chiếu khác nhau.

Tức là, chuyển đổi từ kiểu bê tông sang đối tượng đặc điểm chỉ có thể xảy ra nếu bạn có quyền truy cập trực tiếp vào con trỏ thông minh hỗ trợ DST, chứ không phải khi nó ẩn trong cấu trúc khác.

Điều đó nói rằng, tôi thấy cách có thể là có thể cho phép điều này cho một vài loại lựa chọn. Ví dụ, chúng tôi có thể giới thiệu một số loại đặc điểm Construct/Unwrap được biết đến với trình biên dịch và nó có thể sử dụng để "tiếp cận" bên trong một chồng trình bao bọc và thực hiện chuyển đổi đối tượng đặc điểm bên trong chúng. Tuy nhiên, không ai thiết kế điều này và cung cấp một RFC về nó được nêu ra - có lẽ bởi vì nó không phải là một tính năng cần thiết rộng rãi.

+0

Cảm ơn! Làm thế nào bạn sẽ thực hiện mô hình này? Các mẫu tôi có nghĩa là: Bạn có một loạt các đối tượng có thể thay đổi. Để có hiệu quả, chúng có thể được truy cập thông qua các cấu trúc tra cứu khác nhau, ví dụ: băm, khay nhị phân và vectơ. Một số cấu trúc tra cứu này cần các đối tượng thuộc các kiểu khác nhau mà tất cả đều thực hiện một đặc điểm. Tôi có thể bọc chúng trong Enums. Hoặc tôi có thể tạo các cấu trúc bao bọc có Hộp riêng. – rlkw1024

+0

Theo RFC này (https://github.com/rust-lang/rfcs/blob/master/text/0982-dst-coercion.md) loại trình bao bọc mục tiêu nên triển khai CoerceUnsized để làm cho tính năng tự động kết hợp có thể . Tuy nhiên RefCell tại thời điểm này bỏ lỡ thực hiện này, nhưng vẫn coerseable cho các thực thể dựa trên stack (https://stackoverflow.com/questions/30861295/how-to-i-pass-rcrefcellboxmystruct-to-a-function-accepting-rcrefcellbox). Box thực hiện CoerceUnsized và được coerseable trên riêng của mình, nhưng không phải trong RefCell, đó là hoàn toàn kỳ lạ. – snuk182

+0

Tôi đã cố gắng triển khai RefCell riêng để triển khai CoerceUnsized, không thành công (https://play.rust-lang.org/?gist=27e5b540bfceaa9db79abea5f6526d48&version=nightly&backtrace=2), và thậm chí còn không rõ tại sao nó lại như vậy, gây ra RefCell chỉ là một trình kiểm tra mượn thời gian chạy cho UnsafeCell, mà chỉ là một trình bao bọc như QuackWrap từ ví dụ bên dưới. – snuk182

1

Vladimir's answer giải thích trình biên dịch đang làm gì. Dựa trên thông tin đó, tôi đã phát triển một giải pháp: Tạo một wrapper struct xung quanh Box<Quack>.

Trình bao bọc được gọi là QuackWrap. Nó có kích thước cố định, và nó có thể được sử dụng giống như bất kỳ cấu trúc nào khác (tôi nghĩ) như . Các Box bên trong QuackWrap cho phép tôi để xây dựng một QuackWrap xung quanh bất kỳ đặc điểm nào thực hiện Quack. Vì vậy, tôi có thể có một Vec<Rc<RefCell<QuackWrap>>> nơi các giá trị bên trong là một hỗn hợp của Duck s, Goose s, vv

use std::rc::Rc; 
use std::cell::RefCell; 

trait Quack { 
    fn quack(&self); 
} 

struct Duck; 

impl Quack for Duck { 
    fn quack(&self) { println!("Quack!"); } 
} 

struct QuackWrap(Box<Quack>); 

impl QuackWrap { 
    pub fn new<T: Quack + 'static>(value: T) -> QuackWrap { 
     QuackWrap(Box::new(value)) 
    } 
} 

fn main() { 
    let mut pond: Vec<Box<Quack>> = Vec::new(); 
    let duck: Box<Duck> = Box::new(Duck); 
    pond.push(duck); // This is valid. 

    // This would be a type error: 
    //let mut lake: Vec<Rc<RefCell<Box<Quack>>>> = Vec::new(); 
    //let mallard: Rc<RefCell<Box<Duck>>> = Rc::new(RefCell::new(Box::new(Duck))); 
    //lake.push(mallard); // This is a type mismatch. 

    // Instead, we can do this: 
    let mut lake: Vec<Rc<RefCell<QuackWrap>>> = Vec::new(); 
    let mallard: Rc<RefCell<QuackWrap>> = Rc::new(RefCell::new(QuackWrap::new(Duck))); 
    lake.push(mallard); // This is valid. 
} 

Là một thuận tiện nhất, tôi có lẽ sẽ muốn thực hiện DerefDefrefMut trên QuackWrap. Nhưng điều đó không cần thiết cho ví dụ trên.

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