1 tháng học Rust - Những điều cơ bản của ngôn ngữ lập trình Rust

1 tháng học Rust - Những điều cơ bản của ngôn ngữ lập trình Rust

Tin ngắn hàng ngày dành cho bạn
  • Mấy hôm trước OpenAI giới thiệu Deep Research - một công cụ duyệt web để nghiên cứu và cho ra một bản tổng hợp chỉ trong vài chục phút - so với nhiều giờ làm việc đối với con người, theo như họ công bố.

    Tính năng này hiện chỉ có sẵn cho người dùng Pro. Mặc dù chưa được dùng thử, nhưng qua nhiều bài viết đều nhấn mạnh vào sự ấn tượng trước khả năng của công cụ mới này. Nếu vẫn chưa biết Deep Research làm được gì thì bạn cứ hình dung như thế này: Nói với nó "Tôi cần thông tin nghiên cứu về lượng tiêu thụ cà phê của thế giới trong năm ngoái". Thế thôi! Ngồi chờ một lúc để nó tìm kiếm và tổng hợp lại kết quả và gửi lại cho bạn một bài báo cáo chi tiết. Chà, ghê thật chứ!

    Ngay lập tức huggingface đã lên một bài viết cố gắng tái tạo lại công cụ này theo cách của họ. Chi tiết tại Open-source DeepResearch – Freeing our search agents. Và không có gì ngạc nhiên khi cả 2 đều mang hơi hướng của AI Agents.

    » Xem thêm
  • Sống đủ lâu trong thế giới Internet, bạn có thể thấy rằng mọi người ở đây khá háo hức chạy theo xu hướng và chúng lan truyền với tốc độ chóng mặt.

    Chỉ vài tháng trước, chúng ta vẫn còn kinh ngạc về trí thông minh của các mô hình ngôn ngữ lớn (LLM) có thể trả lời giống như con người, và ngay sau đó, chúng đã được cập nhật với khả năng suy nghĩ và lý luận đáng kinh ngạc. Chúng được ứng dụng rộng rãi không chỉ trong lĩnh vực lập trình. Gần đây, thuật ngữ AI Agents đã tạo nên một sự khuấy động.

    Vậy, AI Agents là gì? Trong bài viết ngắn này, tất nhiên là không thể đưa ra một định nghĩa ngắn gọn nhưng toàn diện. Bạn đọc có thể tham khảo bài viết rất chi tiết này tại đây Agents | Chip Huyền. Để dễ hình dung hơn, AI Agents có thể được coi là một người hoặc một thực thể nào đó. Bản thân các Agents được trang bị tất cả các công cụ cần thiết. Từ đó, các Agents có thể kết hợp chúng để hoàn thành một nhiệm vụ mà chúng ta giao.

    Vẫn còn hơi mơ hồ phải không? Một ví dụ thực tế là khi bạn ra lệnh cho các Agents truy cập Facebook vào lúc 8 giờ tối mỗi ngày, kiểm tra bất kỳ tin tức nổi bật nào từ bạn bè, sau đó gửi tóm tắt đến Telegram. Vậy là xong!

    » Xem thêm
  • Hôm qua đến nay, lượt truy cập tới từ Facebook tăng đột biến. Thường như thế là do ai đó chia sẻ bài viết của blog vào một nhóm nào đó.

    Cơ mà lần này là liên kết trực tiếp đến trang chủ luôn. Tò mò ghê, không biết ai chia sẻ, chia sẻ ở đâu nữa. Muốn biết để tìm hiểu "insight" ghê 🥹

    » Xem thêm

Vấn đề

Một câu chuyện muôn thuở, cái cơ bản lúc nào cũng dễ học, cho đến khi chúng ta bắt tay vào áp dụng trong thực tế thì nhiều vấn đề phát sinh. Tại sao trong tài liệu chỉ hướng dẫn vài ba câu lệnh đơn giản, nhưng khi đọc một dự án thực tế thì lại...toàn cú pháp đến từ sao hỏa vậy chứ? @@.

Lý thuyết và thực tế

Thật ra là tài liệu phải hướng dẫn chúng ta từ cơ bản đến nâng cao chứ không tự nhiên giới thiệu ngay những cú pháp khó hiểu làm gì. Nếu nắm chắc được kiến thức cơ bản, chúng ta vẫn có thể viết được chương trình đầu tiên, sau này khi trở nên quá quen rồi hoặc tiếp xúc thêm nhiều dự án trong thực tế, tự nhiên cảm giác làm quen với những cái mới lại trở nên nhanh hơn. Vì thế kiến thức cơ bản là nền móng vững chắc cho chúng ta trước khi muốn làm gì đó lớn lao hơn.

Tuần vừa rồi, tôi đã lướt qua từ đầu đến cuối tài liệu của Rust, sau đó tập trung vào cú pháp cùng với khái niệm cơ bản đầu tiên.

Cài đặt

Rust hỗ trợ đa nền tảng. Bạn có thể cài đặt Rust thông qua câu lệnh duy nhất nếu đang sử dụng macOS, Linux.

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Hoặc tham khảo thêm các cách khác tại Other Rust Installation Methods.

Chương trình Hello world

Một chương trình in ra dòng chữ "Hello world!" trong Rust rất đơn giản.

fn main() {
    println!("Hello world!");
}

println! là một dạng macro, hiểu đơn giản nó là dạng viết tắt của một hàm, trong trường hợp này, nếu không sử dụng macro, có thể bạn sẽ phải viết một đoạn mã dài hơn. Trong Rust, bạn sẽ làm quen nhiều hơn với các macro khác để đẩy nhanh tốc độ viết mã. println! có chức năng tương tự như console.log trong JS, in mọi thứ ra màn hình console.

Cú pháp để "build" chương trình thành dạng binary.

$ rustc main.rs

Sau đó có thể chạy chương trình bằng cách

$ ./main
Hello world!

Để chạy được trương trình Rust, trước tiên chúng ta cần biên dịch chúng thành mã máy, không giống như JS, nơi chương trình được thực thi ngay lập tức mà không cần qua bước dịch.

Rust cũng cung cấp cơ chế chạy mã ngay lập tức, nhưng trong bài viết này tôi sẽ tạm thời chưa nói đến.

Biến, kiểu dữ liệu và hàm

Khai báo một biến trong Rust bằng từ khóa let hoặc const.

let a: i32 = 1;
# hoặc tự phát hiện kiểu dữ liệu
let a = 1; // a: i32

Vì là một ngôn ngữ định kiểu (static typed) nên biến trong Rust cần được khai báo kiểu khi khởi tạo, hoặc Rust tự động nhận ra kiểu cho lần khai báo đầu tiên.

Mặc định biến của Rust không thể thay đổi giá trị, sử dụng từ khóa mut để khai báo các biến có thể thay đổi. Thật thú vị đúng không!

let mut a: i32 = 1;
a = 2;

Hàm số trong Rust phải là một giá trị được không thay đổi được. Rust không cho phép khai báo hàm số có giá trị thay đổi theo biến.

const A: i32 = 1;
# hoặc
const A: i32 = 1 + 2;

# tính toán từ một biến sẽ bị lỗi
const A: i32 = a + 1; // lỗi

Các kiểu dữ liệu cơ bản của Rust bao gồm char, string, integer, float, boolean... và các cấu trúc dữ liệu như Tuple, Array... Để biết thêm chi tiết, bạn đọc có thể xem tại Data Types.

Hàm số trong Rust có thể trả về hoặc không trả về dữ liệu, nếu có dữ liệu, đồng nghĩa với việc bạn cần phải khai báo cả kiểu dữ liệu trả về.

# Hàm không trả về dữ liệu
fn another_function() {
    println!("Another function.");
}

# Hàm trả về dữ liệu
fn five() -> i32 {
    return 5;
}
# Hoặc viết ngắn gọn
fn five() -> i32 {
    5
}

# Hàm có tham số
fn five(n: i32) -> i32 {
    n
}

Kết thúc dòng trong Rust bắt buộc phải có dấu ;, tuy nhiên nếu không sử dụng return thì kết thúc hàm giá trị trả về là dòng không có dấu ;.

if...else và vòng lặp

if...else trong Rust đóng vai trò như là một biểu thức, có nghĩa là nó có thể trả về giá trị hoặc là không, giống như hàm.

# if...else không trả về giá trị
let number = 3;

if number < 5 {
    println!("condition was true");
} else {
    println!("condition was false");
}

# if...else trả về giá trị và có thể gán vào biến
let condition = true;
let number = if condition { 5 } else { 6 };

Khi có giá trị trả về, đồng nghĩa với việc bạn phải luôn luôn có else và kiểu dữ liệu trả về phải giống nhau.

Không giống như JS, biểu thức đánh giá trong if có thể là dữ liệu hoặc là các phép logic, trong Rust thì nó bắt buộc phải là biểu thức logic, có nghĩa là true/false.

Cú pháp cơ bản để lặp là sử dụng loop.

let mut counter = 0;

let result = loop {
    counter += 1;

    if counter == 10 {
        break counter * 2;
    }
};

Ngoài ra cũng có while.

let mut number = 3;

while number != 0 {
    println!("{number}!");

    number -= 1;
}

Và cả for nữa.

let a = [10, 20, 30, 40, 50];

for element in a {
    println!("the value is: {element}");
}

for có thể được sử dụng trong bất cứ kiểu dữ liệu nào "có thể lặp" tương tự như JS. Ví dụ như:

for number in (1..4) {
    println!("{number}!");
}

(1..4) là một cấu trúc mảng u32, vì thế for sẽ lặp qua từng phần tử và thực hiện các câu lệnh bên trong dấu móc.

Structs

Struct là một cấu trúc dữ liệu với các thuộc tính và phương thức kèm theo. Có thể hình dung struct giống như là Class trong lập trình hướng đối tượng.

struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

let user = User {
    active: true,
    username: String::from("hoaitx"),
    email: String::from("[email protected]"),
    sign_in_count: 1,
};

Ngoài ra struct có cú pháp gần giống như Spread syntax như JS để ghi đè dữ liệu.

let user2 = {
    username: String::from("2coffee"),
    email: String::from("[email protected]"),
    ..user
};

Để khai báo phương thức cho struct, sử dụng cú pháp impl.

impl User {
    fn sayName(&self) -> String {
        self.username
    }

    fn sayEmail(&self) -> String {
        self.email
    }
}

let user = User {
    active: true,
    username: String::from("hoaitx"),
    email: String::from("[email protected]"),
    sign_in_count: 1,
};

println!("user say name {}", user.sayName);

self giống như this trong Class, sử dụng self để trỏ đến các thuộc tính và phương thức trong chính nó.

Khi không có self trong phương thức, các hàm đó được gọi là hàm liên kết. Chúng được sử dụng để tạo ra một phiên bản được xác định trước của struct.

impl User {
    fn default(size: u32) -> User {
        Self {
            active: false,
            username: String::from(""),
            email: String::from(""),
            sign_in_count: 0,
        }
    }
}

let defaultUser = User::default();

Điều này làm chúng ta liên tưởng đến phương thức String::from().

Giải thích một chút về bộ nhớ trong Rust

Có rất nhiều điều mới ở đây. Ví dụ cú pháp String::from() để tạo ra một chuỗi trong Rust, tại sao lại phải phức tạp như thế? Trong khi JS hoặc nhiều ngôn ngữ khác chỉ cần bỏ chúng vào dấu nháy đơn ('') hoặc nháy kép (""). Để làm rõ, chúng ta cần làm quen với bộ nhớ stack và heap trong Rust.

Bộ nhớ stack dùng để lưu giá trị của biến có kích thước cố định ví dụ như char, str, int32, int64... còn heap thì ngược lại, kích thước dữ liệu phụ thuộc vào thời gian chạy (runtime). Dữ liệu trong dấu nháy kép ("") được xác định là char Dữ liệu trong dấu nháy kép ("") là str, còn khi sử dụng String:from() nó là String và được lưu trữ trong heap.

Thế tóm lại là str với String chỉ khác nhau ở chỗ lưu trữ? Đúng vậy, nhưng sự khác biệt rõ ràng hơn ở cơ chế quản lý bộ nhớ của Rust. Rust không có bộ thu gom rác tự động cho nên nó phải sinh ra cơ chế quản lý bộ nhớ cho các biến, hay nói cách khác là vòng đời của biến trong chương trình.

Tất cả các biến trong Rust theo mặc định là bất biến!? Đúng vậy, nghĩa là chúng không thể thay đổi được sau khi khai báo. Đừng lo lắng, Rust vẫn cho phép chúng ta khai báo biến có thể thay đổi được, đổi lại là một cơ chế quản lý bộ nhớ "có 102". Chúng ta tiếp tục tìm hiểu nó trong bài viết tiếp theo nhé!

Cao cấp
Hello

Tôi & khao khát "chơi chữ"

Bạn đã thử viết? Và rồi thất bại hoặc chưa ưng ý? Tại 2coffee.dev chúng tôi đã có quãng thời gian chật vật với công việc viết. Đừng nản chí, vì giờ đây chúng tôi đã có cách giúp bạn. Hãy bấm vào để trở thành hội viên ngay!

Bạn đã thử viết? Và rồi thất bại hoặc chưa ưng ý? Tại 2coffee.dev chúng tôi đã có quãng thời gian chật vật với công việc viết. Đừng nản chí, vì giờ đây chúng tôi đã có cách giúp bạn. Hãy bấm vào để trở thành hội viên 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 (1)

Nội dung bình luận...
Avatar
Ẩn danh1 năm trước
''Dữ liệu trong dấu nháy kép ("") được xác định là char''. theo mình biết thì char là kiểu dữ liệu 4 bytes và trong rust nó hỗ trợ unicode. Còn char mà bạn nhắc đến trong bài viết được đặt trong "" thì nó là str
Trả lời
Avatar
Xuân Hoài Tống1 năm trước
Cảm ơn bạn đã chỉ ra sai sót, mình đã cập nhật lại bài viết :D
Bấm hoặc cuộn mạnh để sang bài mới