1 tháng học Rust - Collections

1 tháng học Rust - Collections

Những mẩu tin ngắn hàng ngày dành cho bạn
  • Đây! Một vấn đề mà từ xưa đến nay mình cứ thắc mắc mãi, và cho đến hôm qua thì mọi thứ đã sáng tỏ.

    Bình thường mọi người dùng height: 100vh để đặt chiều cao bằng với viewport của màn hình. Trên máy tính thì không vấn đề gì, thậm chí giả lập kích thước của điện thoại thông minh thì mọi thứ vẩn ổn. Nhưng khi mở trên điện thoại thì height 100vh lúc nào cũng vượt quá viewport. Ủa!? Là sao???

    Lý giải cho điều này là do trên thiết bị di động có cách tính viewport khác với máy tính. Nó thường bị can thiệp hay ảnh hưởng bởi thanh địa chỉ, thanh điều hướng của nền tảng mà bạn đang sử dụng. Vậy nên nếu muốn 100vh trên di động đúng bằng viewport thì cần phải làm thêm một bước thiết lập lại viewport.

    Dễ lắm, đầu tiên cần tạo một css variable --vh ở ngay thẻ script đầu trang.

    function updateViewportHeight() { const viewportHeight = globalThis.innerHeight; document.documentElement.style.setProperty('--vh', `${viewportHeight * 0.01}px`); } updateViewportHeight(); window.addEventListener('resize', updateViewportHeight);

    Sau đó thay vì dùng height: 100vh thì chuyển thành height: calc(var(--vh, 1vh) * 100). Thế là xong.

    » Xem thêm
  • Cả ngày hôm nay mình dành thời gian để làm giao diện tiếp thị cho gói hội viên của 2coffee.dev. Vậy là cuối cùng thì cũng chính thức đi vào vào con đường mà 5 năm trước cũng không ngờ đến được: "Bán một cái gì đó". Người ta thường nói "Cho đi để nhận lại", bên cạnh đó cũng có câu "Nếu giỏi một cái gì đó, đừng làm nó miễn phí". Nếu theo dõi đủ lâu, bạn đọc sẽ thấy chẳng có gì mình giấu giếm. Biết gì viết nấy, và đôi khi nhờ viết ra mà nhận lại được sự góp ý của độc giả. Từ đó giúp mình hoàn thiện bản thân nhiều hơn.

    Membership là tính năng mà mình sắp sửa giới thiệu. Trở thành hội viên của blog, bạn sẽ có một số đặc quyền nhất định, ví dụ như truy cập vào các bài viết chỉ dành riêng cho hội viên. Các bài viết này về các chủ đề chuyên sâu và được hệ thống hoá sao cho dễ đọc và dễ nắm bắt nhất. Qua đó cung cấp thêm nhiều kiến thức và trau dồi kỹ năng cho bạn đọc.

    Để đạt được đến ngày hôm nay là công rất lớn của các bạn đọc giả, của những người yêu mến 2coffee.dev. Nhờ các bạn mà blog mới có ngày hôm nay. Bên cạnh đó, bản thân mình cũng phải thay đổi liên tục, phải vượt ra khỏi vùng an toàn, làm những điều mà trước nay không dám. Dù sao đi nữa thì đây cũng mới là khởi đầu cho mọi sự gian nan. Nhưng đừng bao giờ nản nha các bạn ơi 😄

    » Xem thêm
  • Ngày nay, 1 triệu (1M) tác vụ đồng thời sẽ tiêu tốn bao nhiêu bộ nhớ? Đó là câu hỏi của hez2010 và anh đã quyết định đi tìm câu trả lời, bằng cách thử nghiệm một chương trình đơn giản trên nhiều ngôn ngữ lập trình khác nhau: How Much Memory Do You Need in 2024 to Run 1 Million Concurrent Tasks?

    Tóm tắt lại thì Rust vẫn vô đối, nhưng vị trí thứ 2 mới làm tôi cảm thấy ngạc nhiên 😳

    » Xem thêm

Vấn đề

Vậy là một tháng đã trôi qua, mà đúng ra là thêm khoảng 5-6 ngày nữa kể từ lúc tôi đặt tay xuống viết series tự học Rust của mình. Như lời bộc bạch ngay từ ban đầu, rằng đó như là cách “cam kết” việc học đồng thời tóm tắt lại những gì mà mình học được, chia sẻ, và khi ai đó đọc được, có thể họ sẽ góp ý để chỉ ra lỗi sai của tôi ở đâu đó.

Kết thúc một tháng, chặng đường chúng ta đi được thậm chí còn chưa được một nửa so với mục tiêu đề ra. Thú thật, với lượng thời gian ít ỏi, đọc đã vất vả, viết lại còn khó hơn gấp bội, chưa kể thời gian dành cho việc khác. Thực ra không phải tôi muốn đổ lỗi cho hoàn cảnh mà ngay từ đầu, bản thân đã lường trước được việc này, chỉ có điều mình sẵn sàng đối diện và luôn nỗ lực từng ngày, làm được đến đâu thì hay đến đó vì ít ra không dặm chân tại chỗ.

Dĩ nhiên series về Rust vẫn chưa dừng lại, chỉ có điều tôi sẽ giãn bài viết ra, thay vào đó là chỗ cho các bài khác để tránh sự nhàm chán cho cả tôi và cả bạn đọc.

Nhưng thành thật mà nói, series tự học Rust có thể là thành công lớn nhất trong chuỗi thời gian một tháng của tôi, bằng chứng là có rất nhiều người đọc, có người góp ý, chỉ ra lỗi sai, thậm chí là cả lời đề nghị học chung… Đó quả thật là những điều đáng quý. Một lần nữa cảm ơn bạn đọc đã dành thời gian cho tôi, cảm giác có ai đó dành sự quan tâm cho mình, sự háo hức của tôi dành cho các bạn giống như là một đứa trẻ không ngủ được vì biết rằng, ngày mai được cha mẹ đưa đi khám phá vùng đất trong mơ.

Không lan man nữa, ngày hôm nay chúng ta hãy cùng nhau tiếp tục tìm hiểu xem Collections trong Rust gồm có những gì nhé!

Collections

Thư viện chuẩn của Rust bao gồm một số cấu trúc dữ liệu rất hữu ích được gọi là Collections. Hầu hết các loại dữ liệu khác biểu thị một giá trị cụ thể nhưng Collections có thể chứa nhiều giá trị. Không giống như các kiểu dữ liệu mảng và Tuple, dữ liệu mà các Collections này trỏ đến được lưu trữ trên heap, có nghĩa là không cần biết lượng dữ liệu tại thời điểm biên dịch và có thể tăng hoặc giảm khi chương trình chạy. Collections bao gồm:

  • Một vector cho phép bạn lưu trữ một số lượng giá trị có thể thay đổi được cạnh nhau.
  • Một string là một tập hợp các ký tự.
  • Hash map cho phép tạo ra dữ liệu chứa các cặp key-value.

Lưu trữ một danh sách giá trị với Vectors

Ngay từ đầu, bạn đọc gặp rất nhiều ví dụ về kiểu dữ liệu Vectors mà được tạo ra với marco vec!. Đúng vậy, Vectors là kiểu Collections trong Rust, hay nói cách khác nó là tập hợp một danh sách dữ liệu.

Cú pháp để tạo ra một Vectors là:

let v: Vec<i32> = Vec::new();

Ồ thật dài dòng, vì thế hãy dùng marco.

let v = vec![0];

Thêm dữ liệu vào Vectors.

v.push(1);
v.push(2);
v.push(3);

Đọc một phần tử.

let one = &v[1];

Vậy điều gì xảy ra nếu đọc một phần tử “vượt quá phạm vi”?

let nine = &v[9];

Quả thật là không nên, vì thế cách tốt nhất là hãy sử dụng phương thức get của Vectors. get trả về một enum Option, và hãy nhớ lại kiến thức từ bài trước để xử lý giá trị trả về này.

let third: Option<&i32> = v.get(2);
match third {
    Some(third) => println!("The third element is {third}"),  
    None => println!("There is no third element."),  
}

String

String và string trong Rust trông có vẻ giống nhau nhưng thực sự cách chúng hoạt động là khác nhau. Trong khi string được mã hóa (có kiểu dữ liệu &str) và lưu trữ cứng trong dữ liệu nhị phân của ứng dụng thì String lại ngược lại, nó được lưu trong heap và sẵn sàng thay đổi giá trị cũng như kích thước.

Lý giải về điều này, Rust nói rằng kiểu dữ liệu này xử lý tương đối phức tạp, tùy thuộc vào ngôn ngữ lập trình mà có cách triển khai sao cho hợp lý. Với họ, đây là sự lựa chọn trong nhiều cách thức đó.

Cú pháp để tạo ra một String.

let mut s = String::new();

Hoặc tạo ra String từ chuỗi kí tự cho trước.

let s = String::from("initial contents");

Chúng ta cũng có thể chuyển dữ liệu có kiểu &str sang String.

let data = "initial contents";
let s = data.to_string();

Một số toán tử cơ bản trên kiểu String bao gồm:

# Thêm vào một chuỗi kí tự
let mut s = String::from("foo");
s.push_str("bar");

# Chỉ thêm vào một kí tự
s.push("b");

# Hoặc là nối chuỗi bằng toán tử +
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2;

Lưu ý rằng sau phép “+” hai String, s1 sẽ bị giải phóng và phải sử dụng tham chiếu &s2.

Để tránh phức tạp hoặc mất quyền sở hữu, chúng ta có thể tiến hành nối chuỗi bằng marco format!.

let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = format!("{s1}-{s2}");

Tương tự như JS, có thể lặp qua từng kí tự trong chuỗi bằng cách:

for c in "Зд".chars() {
    println!("{c}");
}

Kết quả:

З
д

Key-value với Hash Maps

Trong JavaScript, chúng ta có một đối tượng Map với khả năng lưu trữ dữ liệu dưới dạng key-value, thì trong Rust, Hash Maps cũng làm điều tương tự.

Cú pháp tạo ra một Hash Map.

use std::collections::HashMap;

let mut scores = HashMap::new();

scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);

Đọc giá trị từ một key.

let team_name = String::from("Blue");
let score = scores.get(&team_name).copied().unwrap_or(0); // 10

Sử dụng phương thức get để đọc giá trị, get trả về một Options<&V>, hay là một tham chiếu của kiểu dữ liệu V nào đó. Trong ví dụ trên, copied để lấy ra Option<i32>, nghĩa là 10. unwrap_or xử lý lỗi trong trường hợp cặp key-value cần lấy không tồn tại thì giá trị trả về là 0.

Chúng ta cũng có thể lặp qua các cặp key-value trong Hash.

for (key, value) in &scores {
    println!("{key}: {value}");
}

Cuối cùng, vì tất cả dữ liệu trong Collections này đều nằm trong heap, cho nên chúng sẽ có quyền sở hữu, vì thế hãy thận trong việc xử lý các loại này.

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.
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
seri về Rust có lẽ e sẽ không đọc, nhưng khi nào e học nó thì e sẽ quay lại và đọc những bài viết này
Trả lời
Avatar
Xuân Hoài Tống1 năm trước
Chúc e sớm nắm R trong lòng bàn tay nhé 😄