1 tháng học Rust - ứng dụng CLI đầu tiên

1 tháng học Rust - ứng dụng CLI đầu tiên

Tin ngắn hàng ngày dành cho bạn
  • Từ lâu rồi suy nghĩ làm thế nào để tăng sự hiện diện thương hiệu, cũng như người dùng cho blog. Nghĩ đi nghĩ lại thì chỉ có cách chia sẻ lên mạng xã hội hoặc trông chờ họ tìm kiếm, cho đến khi...

    In cái áo này được cái tắc đường khỏi phải lăn tăn, càng đông càng vui vì hàng trăm con mắt nhìn thấy cơ mà 🤓

    (Có tác dụng thật nha 🤭)

    » Xem thêm
  • Một vòng của sự phát triển nhiều dự án khá là thú vị. Tóm tắt lại trong 3 bước: Thấy một cái gì đó phức tạp -> Làm cho nó đơn giản đi -> Thêm thắt tính năng cho đến khi nó phức tạp... -> Quay trở lại vòng lặp mới.

    Tại sao lại như vậy? Để mình lấy 2 ví dụ cho các bạn thấy.

    Markdown ra đời với mục tiêu tạo ra một định dạng văn bản thô "dễ viết, dễ đọc, dễ dàng chuyển thành một dạng gì đó như HTML". Vì thời đó chẳng ai đủ kiên nhẫn mà vừa ngồi viết vừa thêm định dạng cho văn bản hiển thị ở trên web như thế nào. Ấy vậy mà giờ đây người ta đang "nhồi nhét" hoặc tạo ra các biến thể dựa trên markdown để bổ sung thêm nhiều định dạng mới đến mức... chẳng nhớ nổi hết cú pháp.

    React cũng là một ví dụ. Từ thời PHP, việc khát khao tạo ra một cái gì đó tách biệt hẳn giao diện người dùng và phần xử lý logic chính của ứng dụng thành 2 phần riêng biệt cho dễ đọc, dễ viết. Kết quả là các thư viện UI/UX phát triển rất mạnh mẽ, mang lại khả năng tương tác với người dùng rất tốt, còn phần logic ứng dụng thì nằm ở một máy chủ riêng biệt. Bộ đôi Front-end, Back-end cũng từ đấy mà thịnh hành, không thể thiếu anh bồi bàn REST API. Ấy vậy mà giờ đây React trông cũng không khác biệt gì so với PHP là mấy, kéo theo là cả Vue, Svelte... lại cùng quy tất cả về một mối.

    Cơ mà không phải vòng lặp là xấu, ngược lại vòng lặp này mang tính tiến hoá nhiều hơn là "cải lùi". Nhiều khi lại tạo ra được cái hay hơi cái cũ thế là người ta lại dựa trên cái hay đó để tiếp tục lặp. Nói cách khác là chắc lọc tinh hoa từng tí một tí một á 😁

    » Xem thêm
  • Song song với các dự án chính thức thì thi thoảng mình vẫn thấy các dự án "bên lề" nhằm tối ưu hoặc cải tiến ngôn ngữ theo khía cạnh nào đó. Ví dụ nature-lang/nature là một dự án hướng tới cải tiến Go, mang lại một số thay đổi nhằm giúp cho việc sử dụng Go trở nên thân thiện hơn.

    Nhìn lại mới thấy hao hao JavaScript 😆

    » Xem thêm

Vấn đề

1 năm trước, tôi đã hô hào rằng sẽ quyết tâm học Rust trong vòng 1 tháng. Và kết quả thì như bạn đã thấy, chuỗi bài viết về quá trình học Rust vẫn chưa kết thúc. Như vậy có thể coi đó là một sự thất bại rồi phải không? Không biết bạn đọc nghĩ sao nhưng tôi thì nghĩ không hẳn là như thế.

Ngôn ngữ lập trình suy cho cùng là một công cụ để giải bài toán. Biết thêm được một cái mới, hẳn kinh nghiệm làm bài sẽ nhiều hơn, chưa kể còn giải được theo cách tối ưu. Bên cạnh việc học, tôi cũng có nhiều việc phải làm nữa. Những công việc đó có độ ưu tiên cao hơn nên buộc mình phải bắt tay vào làm sớm hơn.

Nhiều lần tôi có ý thôi không học nữa. Nhưng bằng một lý do nào đó mà chuỗi bài viết về Rust vẫn được bạn đọc yêu thích. Tôi không rõ tại sao, vì gần như những gì viết ra chỉ là các bản tóm tắt hoặc có thêm đôi ba lời giải thích cách hiểu của mình về Rust. Thậm chí còn không được đầy đủ so với tài liệu gốc (dù sao thì tôi vẫn khuyên bạn nên đọc bản gốc 😅). Có vẻ như thứ mà nhiều người muốn thấy là một người đồng hành. À! Ít ra thì đang có người học Rust giống như mình.

Một năm vừa rồi, chúng ta hầu như đã học được gần hết kiến thức cơ bản của Rust, về biến, kiểu dữ liệu và cả một số cấu trúc dữ liệu cơ bản. Riêng phần khó nhất - quyền sở hữu thì tôi tin là phải bắt tay vào làm dự án thực sự mới phát sinh ra nhiều vấn đề, chứ mà lý thuyết thì ai mà chẳng đọc được đúng không.

Vậy thì đây cũng là thời điểm để làm một bài thực hành, ứng dụng những gì đã học được để tạo một công cụ dòng lệnh (CLI). Nhưng đến đây tôi chợt nhớ ra trước đó mình cũng có tạo ra một ứng dụng CLI để tạo và upload hình ảnh thumbnail trong bài viết Ứng dụng CLI để tăng hiệu suất trong công việc. Sẽ thật tuyệt vời nếu viết lại được công cụ này bằng Rust.

Ứng dụng trước đó đang giải quyết công việc xử lý kích thước và tải lên ảnh hàng loạt các bài viết trên blog. Tưởng tượng bạn có một hình ảnh, bất kể định dạng nào, kích thước bao nhiêu thì khi đưa vào CLI này nó sẽ tạo ra một loạt các hình ảnh với kích thước khác nhau rồi tải chúng lên R2 của Cloudflare. Thứ chúng ta nhận được sau đó là tất cả đường dẫn đến hình ảnh ở trên.

Nhưng suy đi cũng phải ngẫm lại, nếu mang hết tính năng của CLI cũ sang thì mất kha khá thời gian đấy, cho nên trong một bài viết ngắn này, tôi sẽ đơn giản đi một chút. Bỏ qua công đoạn xử lý ảnh đi, giờ chỉ cần nhập vào một hình ảnh thì nó tải lên R2 thôi.

Bắt đầu nào!

Đầu tiên tạo một project Rust mới bằng cargo, đặt tên là img-cli.

$ cargo new img-cli

Chạy thử chương trình.

$ cargo run
   Compiling img-cli v0.1.0 (/Users/hoaitx/src/hoaitx/img-cli)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.35s
     Running `target/debug/img-cli`
Hello, world!

Bây giờ chúng ta cần lấy được tham số là đường dẫn đến hình ảnh cần được tải lên. Một cái gì đó giống như là:

$ img-cli path_to_image

Thì trong Rust cách đơn giản nhất để lấy các tham số đằng sau lệnh là args.

fn main() {
    let args: Vec<String> = env::args().collect();
    let file_path = &args[1];
}

API tải ảnh lên R2 gọi qua PUT, với body là binary chứa dữ liệu ảnh cần tải lên. Một điều cần lưu ý là tên của ảnh tải lên R2 được trích xuất từ đường dẫn API. Ngoài ra, cần truyền thêm một tham số bí mật là 'X-Custom' trong headers để xác thực quyền tải lên hình ảnh.

Nhưng trước tiên, viết một hàm để đọc dữ liệu file ảnh.

fn read_file(path: &str) -> Result<Vec<u8>, std::io::Error> {
    let mut file = File::open(path)?;
    let mut file_bytes = Vec::new();
    file.read_to_end(&mut file_bytes)?;
    Ok(file_bytes)
}

read_file nhận vào một đường dẫn đến hình ảnh, trả về dữ liệu kiểu Vec<u8> nếu thành công. Ngược lại, nó sẽ trả về lỗi. Vec<u8> là kiểu dữ liệu được dùng trong body của API.

Tiếp theo, viết một hàm tải ảnh lên R2. Ở đây tôi sử dụng thư viện reqwest để gọi API. Thư viện này thường đi kèm với tokio để xử lý bất đồng bộ. Tokio là một runtime cung cấp môi trường xử lý bất đồng bộ cho Rust. Để đơn giản, chúng ta sẽ tạm thời không dùng đến mà chỉ sử dụng API blocking của reqwest.

Cài đặt reqwest:

$ cargo add reqwest --features blocking

Lưu ý là sau khi gọi API, kết quả trả về là một JSON có chứa liên kết đến hình ảnh vừa tải lên. Chúng ta cần lấy được liên kết này và in ra màn hình. Để làm được cần phải tiến hành mapping được các trường dữ liệu vào một kiểu dữ liệu struct. Cài thêm 2 thư viện serdeserde_json để xử lý.

$ cargo add serde --features derive
$ cargo add serde_json

Khai báo một struct Response để mapping phản hồi của API.

#[derive(serde::Deserialize, serde::Serialize)]
struct Response {
    full: String,
}

Ráp chúng lại với nhau thành một hàm up_file.

#[derive(serde::Deserialize, serde::Serialize)]
struct Response {
    full: String,
}

fn up_file(path: &str) -> Result<String, Box<dyn std::error::Error>> {
    let url = "https://static-img.2coffee.dev";
    let client = reqwest::blocking::Client::new();
    let file_path = path;

    // lấy ra tên file chính là chuỗi cuối cùng sau dấu '/'
    let file_name = file_path
        .split('/')
        .last()
        .unwrap_or("unknown")
        .to_string();

    let file_bytes = read_file(file_path)?;

    let response = client
        .put(format!("{}/{}", url, file_name))
        .header("Content-Type", "application/x-binary")
        .header("X-Custom-Auth-Key", "XXX")
        .body(file_bytes)
        .send();

    let response_struct: Response =
        serde_json::from_str(response?.text()?.as_str())?;

    Ok(response_struct.full)
}

Đưa các hàm vào main.

fn main() {
    let args: Vec<String> = env::args().collect();
    let file_path = &args[1];
    let url = up_file(file_path);

    print!("{}", url.unwrap());
}

Chạy thử

$ cargo run -- '/Users/hoaitx/Downloads/hoaitx.jpg'
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.20s
     Running `target/debug/rs /Users/hoaitx/Downloads/hoaitx.jpg`
https://static-img.2coffee.dev/hoaitx.jpg

Mọi thứ hoạt động!

Trong bài viết này chúng ta đã vận dụng những kiến thức cơ bản và thêm cả cách sử dụng một số thư viện để gọi API cũng như xử lý kết quả phản hồi. Quả là khi bắt tay vào làm thì mới thấy khó khăn. Các bài viết sau đó, chúng ta sẽ tiến dần tới nhiều khái niệm nâng cao trong Rust. Hy vọng mọi người sẽ tiếp tục ủng hộ series bài viết về Rust này trong tương lai!

Cao cấp
Hello

Bí mật ngăn xếp của Blog

Là một lập trình viên, bạn có tò mò về bí mật công nghệ hay những khoản nợ kỹ thuật về trang blog này? Tất cả bí mật sẽ được bật mí ngay bài viết dưới đây. Còn chờ đợi gì nữa, hãy bấm vào ngay!

Là một lập trình viên, bạn có tò mò về bí mật công nghệ hay những khoản nợ kỹ thuật về trang blog này? Tất cả bí mật sẽ được bật mí ngay bài viết dưới đây. Còn chờ đợi gì nữa, hãy bấm vào ngay!

Xem tất cả

Đăng ký nhận thông báo bài viết mới

hoặc
* Bản tin tổng hợp được gửi mỗi 1-2 tuần, huỷ bất cứ lúc nào.

Bình luận (0)

Nội dung bình luận...