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