ข้ามไปยังเนื้อหา

Generics & Trait Bounds

ใน TypeScript คุณเขียน function identity<T>(x: T): T เพื่อให้ function ทำงานได้กับทุก type ส่วน Rust ใช้ syntax วงเล็บมุมแบบเดียวกัน: fn identity<T>(x: T) -> T ความต่างหลักคือ generic ของ TypeScript ถูก erase บางส่วนตอน compile time แล้วไปพึ่ง structural typing ส่วน Rust ทำ monomorphisation — compiler สร้าง function แบบ concrete แยกให้ทุก type ที่ใช้จริง ไม่มี overhead ตอน runtime

TypeScript จำกัด generic ด้วย extends: <T extends Printable> ส่วน Rust จำกัดด้วย trait bound: <T: Display> (inline syntax) หรือ where clause (แยกบรรทัด ใช้เมื่อ bound ยาว)

TypeScript
// TypeScript — constraint with extends
interface Printable {
toString(): string;
}
function printTwice<T extends Printable>(value: T): void {
console.log(value.toString());
console.log(value.toString());
}
printTwice(42);
printTwice("hello");
Rust
use std::fmt::Display;
// Inline bound syntax
fn print_twice<T: Display>(value: T) {
println!("{value}");
println!("{value}");
}
// where clause — cleaner for multiple bounds
fn print_and_debug<T>(value: T)
where
T: Display + std::fmt::Debug,
{
println!("display: {value}");
println!("debug: {value:?}");
}
fn main() {
print_twice(42);
print_twice("hello");
print_and_debug(3.14_f64);
}

เหมือนกับที่ TypeScript classes เป็น generic ได้ (class Stack<T>), Rust structs ก็เป็น generic ได้เช่นกัน คุณเพิ่ม type parameter หลังชื่อ struct และทำซ้ำใน impl block

TypeScript
// TypeScript generic class
class Stack<T> {
private items: T[] = [];
push(item: T): void { this.items.push(item); }
pop(): T | undefined { return this.items.pop(); }
peek(): T | undefined { return this.items[this.items.length - 1]; }
}
const s = new Stack<number>();
s.push(1);
s.push(2);
console.log(s.pop()); // 2
Rust
struct Stack<T> {
items: Vec<T>,
}
impl<T> Stack<T> {
fn new() -> Self {
Stack { items: Vec::new() }
}
fn push(&mut self, item: T) {
self.items.push(item);
}
fn pop(&mut self) -> Option<T> {
self.items.pop()
}
fn peek(&self) -> Option<&T> {
self.items.last()
}
}
fn main() {
let mut s: Stack<i32> = Stack::new();
s.push(1);
s.push(2);
println!("{:?}", s.pop()); // Some(2)
}

คุณสามารถกำหนดให้ type ต้อง implement หลาย trait พร้อมกันโดยเชื่อมด้วย + เหมือนกับ T extends A & B ของ TypeScript

// TypeScript
function foo<T extends Serializable & Loggable>(x: T) { ... }
// Rust — inline
fn foo<T: Serialize + Debug>(x: T) { ... }
// Rust — where clause (แนะนำเมื่อ bounds ยาว)
fn foo<T>(x: T)
where
T: Serialize + Debug + Clone,
{ ... }
use std::fmt::Display;
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
fn describe<T: Display + std::fmt::Debug>(value: T) {
println!("Display: {value}");
println!("Debug: {value:?}");
}
fn main() {
let numbers = vec![34, 50, 25, 100, 65];
println!("largest number: {}", largest(&numbers));
let chars = vec!['y', 'm', 'a', 'q'];
println!("largest char: {}", largest(&chars));
describe(42_i32);
describe("hello");
}
Monomorphisation ของ Rust สำหรับ generic functions หมายความว่าอะไร?
ข้อใดเพิ่ม trait bounds สองอย่างให้กับ generic parameter ได้ถูกต้อง?
ควรใช้ `where` clause แทน inline bounds เมื่อใด?
คู่เทียบใน TypeScript ของ `<T: Serialize + Debug>` ของ Rust คืออะไร?