Rust: Turbofish ::<> 🐠

Rust Turbofish

Trong trường hợp bạn cần chỉ định kiểu dữ liệu cho một generic function, method, struct, hoặc enum, Rust có một cú pháp đặc biệt để làm điều này gọi là turbofish. Quy tắc là khi nào bạn thấy

$ident<T>

trong bất kỳ định nghĩa nào, thì bạn có thể sử dụng nó dưới dạng

$ident::<T>

để chỉ định kiểu dữ liệu cho generic parameter. Sau đây là một số ví dụ để làm rõ hơn.

Generic Function

Ví dụ function std::mem::size_of() có definition như sau:

pub fn size_of<T>() -> usize

Khi gọi size_of với turbofish:

std::mem::size_of::<u32>()
// 4

sẽ cho ta biết size của u32 theo số bytes.

Generic Method

Phương thức parse() của str bạn cũng sẽ hay gặp cách sử dụng với cú pháp turbofish:

fn parse<F>(&self) -> Result<F, F::Err> where F: FromStr

Chúng ta có thể sử dụng turbofish để mô tả kiểu dữ liệu sẽ được parsed từ str

"1234".parse::<u32>()

Một ví dụ phổ biến nữa là collect() của Iterator

fn collect<B>(self) -> B where B: FromIterator<Self::Item>

Bởi vì compiler đã biết kiểu dữ liệu của Self::Item mà ta đang collect rồi, chúng ta thường không cần ghi ra. Thay vào đó là sử dụng _ để compiler tự động infer ra. Ví dụ:

let a = vec![1u8, 2, 3, 4];

a.iter().collect::<Vec<_>>();

Sẵn tiện nói về Iterator chúng ta cũng có thể sử dụng turbofish syntax với sum()product().

fn sum<S>(self) -> S where S: Sum<Self::Item>
fn product<P>(self) -> P where P: Product<Self::Item>

Cú pháp như sau:

[1, 2, 3, 4].iter().sum::<u32>()
[1, 2, 3, 4].iter().product::<u32>()

Generic Struct

Trong trường hợp compiler không có đủ thông tin để infer khi tạo generic struct, chúng ta cũng có thể sử dụng turbofish syntax. Ví dụ struct Vec có định nghĩa như sau

pub struct Vec<T> { /* fields omitted */ }

Ví dụ để khởi tạo Vec mới với Vec::new() ta có thể viết

Vec::<u8>::new()

Nhớ là ta bỏ turbofish sau Vec:: không phải sau method new bởi vì struct sử dụng generic type chứ không phải method new. Hơi bựa nhưng nó vẫn thỏa quy tắc của turbofish. Một ví dụ khác

std::collections::HashSet::<u8>::with_capacity(10)

Ta đang tạo một Hashset với 10 phần tử, bởi vì Hashset struct có định nghĩa như sau

pub struct HashSet<T, S = RandomState> { /* fields omitted */ }

Chúng ta có thể sử dụng cú pháp này với mọi Rust collections.

Generic Enum

Tuy nhiên Enum lại không theo quy tắc trên, bởi vì enum trong Rust không được scoped tại enum name, do đó ta đặt turbofish sau enum variant. Ví dụ hãy xem enum Result được dùng rất nhiều trong Rust

#[must_use]
pub enum Result<T, E> {
  Ok(T),
  Err(E),
}

Chúng ta sử dụng như thế này:

Result::Ok::<u8, ()>(10)
Result::Err::<u8, ()>(())

Và bởi vì Result thường được prelude (import sẵn) trong Rust, thực tế mọi người sẽ viết như thế này:

Ok::<u8, ()>(10)
Err::<u8, ()>(())

Reference

Rust 🦀Rust, Vietnamese, Rust Tiếng Việt, Rust Basic