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

Những mẩu tin ngắn hàng ngày dành cho bạn
  • Một phần mềm giúp chuyển đổi văn bản thành giọng nói do một lập trình viên người Việt làm ra - J2TEAM - Text to Speech (Free). Bạn có thể chuyển đổi hàng chục ngôn ngữ sang hàng chục giọng đọc tự nhiên khác nhau. Điều đặc biệt là nó miễn phí.

    Đánh giá sơ bộ thì chuyển đổi văn bản dài hoặc văn bản bằng tiếng Việt thuần thì rất tốt. Còn dính thêm các từ tiếng Anh thì nó đọc hơi buồn cười 😅

    » Xem thêm
  • Quá ghê ghớm, Codeium - vốn được biết đến như một đối thủ của Github Copilot, khi nó cho người dùng dùng miễn phí không giới hạn. Mới đây họ giới thiệu thêm Windsurf Editor - không chỉ còn là VSCode Extentions nữa mà là một trình Editor luôn - cạnh tranh trực tiếp với Cursor. Và điểm chính là nó... hoàn toàn miễn phí 🫣.

    » Xem thêm
  • Tin vui đầu này, Github Copilot đã chính thức có bản Free cho tất cả mọi người.

    Github Copilot là một trợ lý AI code cùng chúng ta, nó có thể tự động hoàn thành mã, trò chuyện hoặc sửa lỗi. Hiện đang hỗ trợ nhiều trình soạn thảo, IDE phổ biến như VSCode, JetBrains, XCode...

    Phiên bản miễn phí đang bị giới hạn 2000 Suggestions, và khoảng 50 requests đến tính năng Chat hàng tháng. Sau đó bạn có thể nâng cấp lên các phiên bản cao cấp hơn với giá từ 10$.

    Theo đánh giá của mình thì Copilot rất tốt và xứng đáng trong tầm giá, đang dùng hàng ngày 😄

    » 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

5 bài học sâu sắc

Mỗi sản phẩm đi kèm với những câu chuyện. Thành công của người khác là nguồn cảm hứng cho nhiều người theo sau. 5 bài học rút ra được đã thay đổi con người tôi mãi mãi. Còn bạn? Hãy bấm vào ngay!

Mỗi sản phẩm đi kèm với những câu chuyện. Thành công của người khác là nguồn cảm hứng cho nhiều người theo sau. 5 bài học rút ra được đã thay đổi con người tôi mãi mãi. Còn bạn? 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.
Author

Xin chào, tôi tên là Hoài - một anh Dev kể chuyện bằng cách viết ✍️ và làm sản phẩm 🚀. Với nhiều năm kinh nghiệm lập trình, tôi đã đóng góp một phần công sức cho nhiều sản phẩm mang lại giá trị cho người dùng tại nơi đang làm việc, cũng như cho chính bản thân. Sở thích của tôi là đọc, viết, nghiên cứu... Tôi tạo ra trang Blog này với sứ mệnh mang đến những bài viết chất lượng cho độc giả của 2coffee.dev.Hãy theo dõi tôi qua các kênh LinkedIn, Facebook, Instagram, Telegram.

Bạn thấy bài viết này có ích?
Không

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