Khai báo một shape
Một shape là một kiểu bản ghi với các trường có tên và có kiểu:
shape Point {
x: i32,
y: i32,
}
Các thành phần:
shapemở đầu khai báo.Pointlà tên của kiểu.- Cặp ngoặc nhọn bao một danh sách trường, mỗi trường có dạng
name: Type.
Khai báo đó thêm một kiểu mới tên Point vào phạm vi hiện tại. Ở bất cứ đâu bạn dùng được một kiểu có sẵn như i32, giờ bạn cũng dùng được Point.
Tạo một giá trị shape
Tạo một instance bằng biểu thức literal struct:
let point = Point { x: 40, y: 2 }
Literal ghi tên shape và gán từng trường. Mọi trường đều phải có mặt: Zero không âm thầm gán 0 hoặc null cho trường bị thiếu. Nếu bạn quên một trường, trình biên dịch sẽ báo:
{
"code": "FLD002",
"message": "missing field: y",
"line": 4
}
(Mã lỗi chính xác có thể khác; nguyên tắc "không có giá trị mặc định ngầm" thì luôn giữ nguyên.)
Bạn có thể ghi rõ kiểu nếu muốn nó hiện ra trong code:
let point: Point = Point { x: 40, y: 2 }
Đọc trường
Truy cập trường dùng cú pháp dấu chấm:
let point = Point { x: 40, y: 2 }
let xVal = point.x
let yVal = point.y
point.x đọc trường x. Không có phương thức get_x() nào: trường chỉ là dữ liệu thuần.
Một ví dụ hoàn chỉnh
Đây là ví dụ point.0 chuẩn trong repository của ngôn ngữ. Nhấn Chạy để thử:
Đi qua từ trên xuống:
- Khai báo shape
Pointvới hai trườngi32. - Định nghĩa hàm
sumnhận mộtPointvà trả về tổng các trường của nó. - Trong
main, tạo mộtPoint, gọisumvà so sánh kết quả.
Ba bước, ba ý tưởng liên quan đến shape (khai báo, tạo, truy cập) và một hiệu ứng: lệnh check world.out.write(...) ở cuối. Để ý rằng sum không đụng tới World. Nó là một hàm thuần trên dữ liệu, và chữ ký hàm thể hiện rõ điều đó.
Shape với trường lồng nhau
Một shape có thể chứa giá trị thuộc bất kỳ kiểu nào, kể cả shape khác:
shape Range {
start: i32,
end: i32,
}
shape Segment {
label: String,
range: Range,
}
let seg = Segment {
label: "warmup",
range: Range { start: 0, end: 10 },
}
Truy cập trường nối tiếp nhau đúng như bạn nghĩ:
let len = seg.range.end - seg.range.start
Shape generic
Khi các trường của một shape cần đa hình theo kiểu, hãy khai báo tham số kiểu trong dấu ngoặc nhọn:
shape Pair<T, U> {
left: T,
right: U,
}
Các instance chốt tham số thành kiểu cụ thể:
let intBytePair: Pair<i32, u8> = Pair { left: 40, right: 2_u8 }
let words: Pair<String, String> = Pair { left: "hello", right: "world" }
Một bí danh kiểu (type alias) có thể rút gọn một cách tham số hóa hay dùng:
type BytePair = Pair<u8, u8>
let bytes: BytePair = Pair { left: 1_u8, right: 2_u8 }
Bài Generics trình bày sâu hơn về tham số kiểu, kể cả trên hàm chứ không chỉ trên shape.
Shape không phải là gì
Một vài thứ bạn có thể mong đợi ở "struct" trong ngôn ngữ khác nhưng shape cố ý không có:
- Không có phương thức. Khai báo shape chỉ là dữ liệu. Hành vi nằm trong các hàm tự do nhận shape làm tham số. Điều này phản ánh cùng sự tách biệt giữa dữ liệu và hiệu ứng mà bạn thấy ở hàm.
- Không có kế thừa. Shape không mở rộng shape khác. Nếu muốn dùng chung cấu trúc, hãy tách nó thành một trường chung hoặc xây dựng một kiểu tổng bằng choice.
- Không có hàm khởi tạo hay hàm hủy ngầm. Việc tạo giá trị chính là biểu thức literal struct. Việc dọn dẹp là tường minh: khi thư viện chuẩn cung cấp tài nguyên cần giải phóng, việc đó được làm qua các API theo kiểu capability thay vì RAII ẩn.
- Không có trường private. Mọi trường của một shape đều truy cập được từ code nhìn thấy kiểu của shape đó. Phạm vi truy cập nằm ở mức kiểu, không ở mức trường.
Nguyên tắc chung: shape là kiểu bản ghi đơn giản, dễ đoán, và bạn xây mọi thứ khác từ chúng.
Khi nào dùng shape, khi nào dùng choice
Hướng dẫn nhanh:
- Dùng shape khi một giá trị có tất cả các trường đó cùng lúc. Một
Pointluôn có cảxlẫny. - Dùng choice khi một giá trị là một trong nhiều lựa chọn. Một
Resulthoặc làokhoặc làerr. - Dùng enum khi các lựa chọn không mang thêm dữ liệu nào, chúng chỉ là nhãn. Các ngày trong tuần, các trạng thái đơn giản.
Ba khối xây dựng này, shape (và), choice (hoặc), enum (hoặc, không kèm dữ liệu), đáp ứng gần như mọi nhu cầu mô hình hóa dữ liệu.
Tiếp theo: generics
Bạn đã thoáng thấy Pair<T, U>. Tài liệu tiếp theo, generics, giải thích cách tham số kiểu hoạt động trên cả shape lẫn hàm, bao gồm các mẫu xuất hiện khắp thư viện chuẩn của Zero.
Câu hỏi thường gặp
Shape trong Zero là gì?
shape là kiểu tích giống struct của Zero: một bản ghi có tên với các trường có kiểu. Bạn khai báo bằng shape Name { field1: T1, field2: T2 }, tạo giá trị bằng Name { field1: v1, field2: v2 } và đọc trường bằng cú pháp dấu chấm (value.field1). Shape là khối xây dựng để mô hình hóa dữ liệu có cấu trúc.
Tạo một giá trị shape như thế nào?
Dùng một biểu thức literal struct ghi tên shape và gán từng trường: let point = Point { x: 40, y: 2 }. Mọi trường đều phải được điền: Zero không âm thầm gán giá trị mặc định cho trường bị thiếu. Thứ tự các trường trong literal không cần khớp với khai báo.
Shape khác class ở điểm nào?
Shape là dữ liệu thuần: có trường, nhưng không có phương thức, không có kế thừa, không có hàm khởi tạo ngầm. Các hàm làm việc với shape nhận nó làm tham số một cách tường minh. Sự tách biệt này giữ cho ngôn ngữ nhỏ gọn và giúp chi phí tạo hay sao chép một shape luôn dễ đoán, không có vtable hay hàm hủy ẩn.
Shape trong Zero có thể generic không?
Có. Khai báo tham số kiểu trong dấu ngoặc nhọn: shape Pair<T, U> { left: T, right: U }. Các instance chốt lại những tham số đó: Pair<i32, u8>. Shape generic xuất hiện khắp thư viện chuẩn: Maybe<T>, Span<T>, v.v. đều là shape generic hoặc kiểu tổng (sum type) xây dựng trên cùng ý tưởng.
Shape được sao chép hay tham chiếu khi truyền vào hàm?
Truyền theo giá trị là mô hình tư duy mặc định cho shape: hàm được gọi nhận bản sao logic của riêng nó, không phải một tham chiếu vào binding của nơi gọi. Mô hình bộ nhớ chính xác ở Zero trước bản 1.0 vẫn đang được hoàn thiện (bạn sẽ thấy ref và mutref trong các ví dụ của thư viện chuẩn cho kiểu tham chiếu tường minh). Với phần lớn code ứng dụng, hãy coi tham số shape là đầu vào kiểu giá trị.