2015-06-19 17 views
9

Tôi có một đặc điểm với hai chức năng liên quan đến:Tại sao ràng buộc `Sized` lại cần thiết trong đặc điểm này?

trait WithConstructor: Sized { 
    fn new_with_param(param: usize) -> Self; 

    fn new() -> Self { 
     Self::new_with_param(0) 
    } 
} 

Tại sao việc thực hiện mặc định của phương pháp thứ hai (new()) buộc tôi để đặt Sized bị ràng buộc vào loại? Tôi nghĩ đó là do thao tác con trỏ ngăn xếp, nhưng tôi không chắc chắn.

Nếu trình biên dịch cần biết kích thước phân bổ bộ nhớ trên ngăn xếp, tại sao ví dụ sau không yêu cầu Sized cho T?

struct SimpleStruct<T> { 
    field: T, 
} 

fn main() { 
    let s = SimpleStruct { field: 0u32 }; 
} 

Trả lời

21

Như bạn có thể đã biết, các loại trong Rust có thể có kích thước và chưa được đánh dấu. Các loại chưa được phân loại, như tên của chúng cho thấy, không có kích thước cần thiết để lưu trữ các giá trị thuộc loại này mà trình biên dịch biết đến. Ví dụ: [u32] là mảng chưa được đánh số là u32 s; vì số lượng phần tử không được chỉ định ở bất kỳ đâu, trình biên dịch không biết kích thước của nó. Một ví dụ khác là một loại đối tượng đặc điểm trần, ví dụ, Display, khi nó được sử dụng trực tiếp như một loại:

let x: Display = ...; 

Trong trường hợp này, trình biên dịch không biết đó là loại được thực sự sử dụng ở đây, nó được xoá hoàn toàn, do đó nó không biết kích thước của các giá trị này. Dòng trên không hợp lệ - bạn không thể tạo biến cục bộ mà không biết kích thước của nó (để phân bổ đủ byte trên ngăn xếp) và bạn không thể chuyển giá trị của một loại chưa được nhập vào hàm làm đối số hoặc trả lại từ.

loại không đúng cở thể được sử dụng thông qua một con trỏ, tuy nhiên, có thể thực hiện thêm thông tin - chiều dài của dữ liệu có sẵn để lát (&[u32]) hoặc một con trỏ vào một bảng ảo (Box<SomeTrait>). Bởi vì con trỏ luôn có kích thước cố định và đã biết, chúng có thể được lưu trữ trong các biến cục bộ và được chuyển vào hoặc trả về từ các hàm.

Với bất kỳ loại bê tông nào, bạn luôn có thể nói liệu loại bê tông có kích thước hoặc chưa được đánh dấu hay không. Tuy nhiên, với Generics, một câu hỏi nảy sinh - là một số tham số kiểu có kích thước hay không?

fn generic_fn<T>(x: T) -> T { ... } 

Nếu không xác định được thì định nghĩa chức năng không đúng, vì bạn không thể chuyển trực tiếp các giá trị chưa được sắp xếp. Nếu nó có kích thước, thì tất cả đều ổn.

Trong Rust, tất cả các tham số kiểu chung được định kích thước theo mặc định ở mọi nơi - theo chức năng, trong cấu trúc và trong các đặc điểm.Họ có một ràng buộc Sized tiềm ẩn; Sized là một đặc điểm để đánh dấu các loại kích thước:

fn generic_fn<T: Sized>(x: T) -> T { ... } 

Điều này là do trong số áp đảo lần bạn muốn thông số chung của bạn được kích thước. Đôi khi, tuy nhiên, bạn muốn để loại trừ ra khỏi sizedness, và điều này có thể được thực hiện với ?Sized ràng buộc:

fn generic_fn<T: ?Sized>(x: &T) -> u32 { ... } 

Bây giờ generic_fn có thể được gọi như generic_fn("abcde"), và T sẽ được khởi tạo với str đó là không đúng cở, nhưng đó là OK - chức năng này chấp nhận một tham chiếu đến T, vì vậy không có gì xấu xảy ra.

Tuy nhiên, có một nơi khác mà câu hỏi về kích thước là quan trọng. Các đặc điểm trong Rust luôn được triển khai đối với một số loại:

trait A { 
    fn do_something(&self); 
} 

struct X; 
impl A for X { 
    fn do_something(&self) {} 
} 

Tuy nhiên, điều này chỉ cần thiết cho mục đích tiện lợi và thiết thực. Có thể xác định đặc điểm luôn luôn lấy một tham số kiểu và không xác định loại các đặc điểm được thực hiện cho:

// this is not actual Rust but some Rust-like language 

trait A<T> { 
    fn do_something(t: &T); 
} 

struct X; 
impl A<X> { 
    fn do_something(t: &X) {} 
} 

Đó là cách các lớp học kiểu Haskell làm việc, và, trên thực tế, đó là cách đặc điểm đang thực sự thực hiện trong Gỉ ở mức thấp hơn.

Mỗi đặc điểm trong Rust có tham số kiểu ngầm, được gọi là Self, trong đó chỉ định loại đặc điểm này được triển khai. Nội dung luôn có sẵn trong phần nội dung của đặc điểm:

trait A { 
    fn do_something(t: &Self); 
} 

Đây là câu hỏi về kích thước có trong hình. Tham số Self có kích thước không?

Hóa ra là không, Self không được định kích thước theo mặc định trong Rust. Mỗi đặc điểm có một liên kết ?Sized tiềm ẩn bị ràng buộc trên Self. Một trong những lý do này là cần thiết vì có rất nhiều đặc điểm có thể được triển khai cho các loại chưa được xử lý và vẫn hoạt động. Ví dụ, bất kỳ đặc điểm nào chỉ chứa các phương thức chỉ lấy và trả về Self bằng tham chiếu có thể được triển khai cho các loại chưa được loại. Bạn có thể đọc thêm về động lực trong RFC 546.

Mức độ hiệu chỉnh không phải là vấn đề khi bạn chỉ xác định chữ ký của đặc điểm và phương pháp của nó. Bởi vì không có mã thực sự trong các định nghĩa này, trình biên dịch không thể giả định bất cứ điều gì. Tuy nhiên, khi bạn bắt đầu viết mã chung sử dụng đặc điểm này, bao gồm các phương thức mặc định vì chúng có tham số Self tiềm ẩn, bạn nên tính đến kích thước. Vì Self không được định kích thước theo mặc định, các phương thức đặc điểm mặc định không thể trả về giá trị Self hoặc lấy nó làm tham số theo giá trị. Do đó, bạn có cần phải xác định rằng Self phải được kích thước theo mặc định:

trait A: Sized { ... } 

hoặc bạn có thể xác định rằng một phương pháp duy nhất có thể được gọi nếu Self có kích thước:

trait WithConstructor { 
    fn new_with_param(param: usize) -> Self; 

    fn new() -> Self 
    where 
     Self: Sized, 
    { 
     Self::new_with_param(0) 
    } 
} 
+0

Cảm ơn bạn đã trả lời đầy đủ. Tôi không biết tất cả các "theo mặc định là Sized nhưng tự không phải là" một phần. Đó là lý do chính khiến tôi bối rối. – eulerdisk

4

Hãy xem điều gì sẽ xảy ra nếu bạn làm điều này với một loại chưa được đánh dấu.

new()di chuyển kết quả của phương thức new_with_param(_) của bạn cho người gọi. Nhưng trừ khi loại có kích thước, bao nhiêu byte nên được di chuyển? Chúng tôi chỉ đơn giản là không thể biết. Đó là lý do tại sao di chuyển ngữ nghĩa yêu cầu Sized loại.

Lưu ý: Các Box es khác nhau đã được thiết kế để cung cấp các dịch vụ thời gian chạy cho chính xác vấn đề này.

+2

Tại sao nó không phàn nàn về 'new_with_param' mặc dù? Nó cũng đòi hỏi phải đặt đúng số lượng không gian trên stack của người gọi của nó. –

+0

Vì vậy, ý tưởng của tôi là chính xác, nhưng sau đó tại sao 'Kích thước' là không cần thiết trong cấu trúc chung ?? Tôi đã cập nhật câu hỏi. – eulerdisk

+0

@Matthieu M.'New_with_param' chỉ là một định nghĩa phương thức đặc điểm, không phải là một cách thực hiện. – llogiq

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