impl Trait

impl Trait có thể được dùng trong hai vai trò:

  1. như là một kiểu đối số
  2. như là một kiểu trả về

Dùng như là một kiểu đối số

Nếu hàm của bạn có chung một đặc điểm nhưng bạn không quan tâm đến kiểu cụ thể, thì bạn có thể đơn giản hóa việc khai báo hàm bằng cách sử dụng impl Trait làm kiểu đối số.

Ví dụ, hãy xem xét đoạn mã dưới đây:

fn parse_csv_document<R: std::io::BufRead>(src: R) -> std::io::Result<Vec<Vec<String>>> {
    src.lines()
        .map(|line| {
            // Với từng dòng trong file
            line.map(|line| {
                // Nếu dòng được đọc thành công, xử lý nó, nếu không thì trả về lỗi
                line.split(',') // Tách dòng thành những phần nhỏ hơn được chia tách bởi dấu phẩy
                    .map(|entry| String::from(entry.trim())) // Xóa khoảng trắng trước và sau
                    .collect() // Tổng hợp các chuỗi của dòng thành một Vec<String>
            })
        })
        .collect() // Tổng hợp các dòng thành một Vec<Vec<String>>
}

parse_csv_document là một hàm tổng quát, cho phép nó nhận bất kỳ kiểu gì có cài BufRead, như là BufReader<File> hay [u8], mà không quan trọng R là kiểu gì, và R chỉ được dùng để định nghia kiểu của src, vì thế phương thức có thể được viết:

fn parse_csv_document(src: impl std::io::BufRead) -> std::io::Result<Vec<Vec<String>>> {
    src.lines()
        .map(|line| {
            // Với từng dòng trong file
            line.map(|line| {
                // Nếu dòng được đọc thành công, xử lý nó, nếu không thì trả về lỗi
                line.split(',') // Tách dòng thành những phần nhỏ hơn được chia tách bởi dấu phẩy
                    .map(|entry| String::from(entry.trim())) // Xóa khoảng trắng trước và sau
                    .collect() // Tổng hợp các chuỗi của dòng thành một Vec<String>
            })
        })
        .collect() // Tổng hợp các dòng thành một Vec<Vec<String>>
}

Lưu ý rằng sử dụng impl Trait như là một kiểu tham số có nghĩa là bạn không thể nêu rõ hình thức phương thức được sử dụng, ví dụ parse_csv_document::<std::io::Empty>(std::io::empty()) sẽ không hoạt động với ví dụ thứ hai.

Dùng như một kiểu trả về

Nếu hàm của bạn trả về một kiểu có triển khai MyTrait, bạn có thể viết kiểu trả về của nó là -> impl MyTrait. Điều này giúp đơn giản hóa kiểu trả về của bạn rất nhiều!

use std::iter;
use std::vec::IntoIter;

// Hàm này kết hợp hai `Vec<i32>` và trả về một iterator.
// Hãy xem kiểu trả về của nó phức tạp thế nào!
fn combine_vecs_explicit_return_type(
    v: Vec<i32>,
    u: Vec<i32>,
) -> iter::Cycle<iter::Chain<IntoIter<i32>, IntoIter<i32>>> {
    v.into_iter().chain(u.into_iter()).cycle()
}

// Đây là một hàm tương tự, nhưng kiểu trả về của nó sử dụng `impl Trait`.
// Hãy xem nó đơn giản hơn đến mức nào!
fn combine_vecs(
    v: Vec<i32>,
    u: Vec<i32>,
) -> impl Iterator<Item=i32> {
    v.into_iter().chain(u.into_iter()).cycle()
}

fn main() {
    let v1 = vec![1, 2, 3];
    let v2 = vec![4, 5];
    let mut v3 = combine_vecs(v1, v2);
    assert_eq!(Some(1), v3.next());
    assert_eq!(Some(2), v3.next());
    assert_eq!(Some(3), v3.next());
    assert_eq!(Some(4), v3.next());
    assert_eq!(Some(5), v3.next());
    println!("all done");
}

Quan trọng hơn nữa, một số kiểu trong Rust không thể ghi ra. Ví dụ, mỗi closure đều có kiểu trả về của nó. Trước cú pháp impl Trait, bạn phải cấp phát trên heap để trả về một closure. Nhưng giờ bạn có thể làm điều đó một cách tĩnh, như thế này:

// Trả về một hàm cho cộng thêm `y` vào đầu vào
fn make_adder_function(y: i32) -> impl Fn(i32) -> i32 {
    let closure = move |x: i32| { x + y };
    closure
}

fn main() {
    let plus_one = make_adder_function(1);
    assert_eq!(plus_one(2), 3);
}

Bạn có thể dùng impl Trait để trả về một iterator sử dụng các closure map hoặc filter! Điều này khiến việc dùng mapfilter dễ hơn. Nhưng các kiểu closure không có tên, bạn không thể ghi ra kiểu trả về một cách rõ ràng nếu hàm của bạn trả về một bộ lặp các closures . Nhưng với impl Trait bạn có thể làm điều này một cách dễ dàng:

fn double_positives<'a>(numbers: &'a Vec<i32>) -> impl Iterator<Item = i32> + 'a {
    numbers
        .iter()
        .filter(|x| x > &&0)
        .map(|x| x * 2)
}

fn main() {
    let singles = vec![-3, -2, 2, 3];
    let doubles = double_positives(&singles);
    assert_eq!(doubles.collect::<Vec<i32>>(), vec![4, 6]);
}