Category Archives: Rust – Lập trình Rust

Rust – Phương thức

Nếu bạn đã học lập trình hướng đối tượng thì bạn sẽ biết đến khái niệm phương thức  method. Phương thức là các hàm được định nghĩa bên trong các lớp – class, và chỉ có các đối tượng được tạo ra từ lớp mới có thể gọi các hàm được.

Trong Rust thì chúng ta định nghĩa các phương thức cho các struct.

Giả sử chúng ta có đoạn code như sau:

struct blog {
    name: &’static str,
    publish: i32
}

fn main() {     
    let mut phocode: blog = blog{
        name: "phocode.com",
        publish: 2016
    };
println!("{:?} was launched in {}", phocode.name, phocode.publish);
}

Trong đoạn code trên chúng ta định nghĩa kiểu struct có tên blog với 2 phần tử là namepublish và tạo một biến với kiểu struct này có tên là phocode, sau đó chúng ta in một đoạn chuỗi giới thiệu về đối tượng này. Output như sau:

"phocode.com" was launched in 2016

Bây giờ thay vì gọi hàm println!() một cách tự do như thế, chúng ta có thể viết riêng cho lớp blog một phương thức để sử dụng, đoạn code như sau:

struct blog {    
name: &’static str,    
publish: i32
}
impl blog {    
fn selfIntroduce(&self) {        
println!("{:?} was launched in {}", self.name, &self.publish);    
}
}
fn main() {         
let mut phocode: blog = blog{        
name: "phocode.com",        
publish: 2016    
};      
phocode.selfIntroduce();
}

Đoạn code trên vẫn cho ra output như cũ, chỉ khác ở chỗ là bây giờ chúng ta gọi phương thức của struct có tên là selfIntroduce(), chứ không dùng hàm riêng lẻ nữa.

Theo như đoạn code trên thì để viết phương thức cho một struct thì chúng ta viết với cú pháp sau:

impl <tên_struct>

    fn <tên_phương_thức>() {

        ...

    }

}

Và khi gọi các phương thức thì chúng ta ghi tên của các biến tạo ra từ struct đó, rồi tới dấu chấm và tên phương thức.

Ngoài ra trong đoạn code ví dụ trên, chúng ta còn đưa vào phương thức tham số là &self, đây là biến tham chiếu tới biến đã gọi hàm đó, ở đây là biến phocode trong hàm main(), với self thì bạn có thể đọc dữ liệu từ các phần tử và có thể gọi các phương thức khác. Lưu ý khi khai báo có kí tự & nhưng khi sử dụng thì không có.

Khi gọi phương thức mà tham số là &self thì chúng ta không cần phải truyền cái gì vào khi gọi hàm, cứ để trống không là selfIntroduce().

Bây giờ chúng ta viết thêm một hàm như sau:

struct blog {    
name: &’static str,    
publish: i32
}
impl blog {    
fn selfIntroduce(&self) {        
println!("{:?} was launched in {}", self.name, &self.publish);    
}    
fn changeName(&mut self, newName: &’static str) {        
println!("Changing name from {:?} to {:?}", self.name, newName);        
self.name = newName;            
}
}
fn main() {         
let mut phocode: blog = blog{        
name: "phocode.com",        
publish: 2016    
};      
phocode.selfIntroduce();    
phocode.changeName("phocode.net");    
phocode.selfIntroduce();
}

Chúng ta viết thêm phương thức changeName() nhận vào một tham số là newName kiểu str để thay đổi giá trị của phần tử name. 

Lưu ý khi thay đổi giá trị của biến self thì chúng ta phải khai báo tham số là &mut self,  nếu không Rust sẽ báo lỗi. Khi truyền giá trị thì chúng ta chỉ cần truyền vào chuối là được.

"phocode.com" was launched in 2016
Changing name from "phocode.com" to "phocode.net"
"phocode.net" was launched in 2016

Lưu ý là Rust không hỗ trợ quá tải phương thức như trong các ngôn ngữ khác, tức là changeName(&mut self, newName: &'static str)changeName(&mut self) đểu không được phép tồn tại, chúng ta phải đặt tên khác nhau.

Rust – Lỗi Exception

Nếu bạn đã từng học các ngôn ngữ lập trình phổ biến như Java, C#.. thì chắc hẳn bạn cũng đã biết về khái niệm lỗi Exceptionlỗi ngoại lệ. Đây là các lỗi chỉ xảy ra trong quá trình chạy các chương trình chứ không thể nào biết trước được như lỗi sai cú pháp, và đây cũng là loại lỗi khó tìm nhất.

Ví dụ phổ biến nhất cho người mới tìm hiểu về lỗi ngoại lệ là ví dụ về phép chia cho 0. Chẳng hạn như chúng ta viết một chương trình máy tính bỏ túi có thực hiện phép chia, thì nếu chúng ta không kiểm tra mẫu số có bằng 0 hay không, chương trình sẽ báo lỗi khi người dùng nhập số 0, trong một số trường hợp thì chương trình sẽ bị crash.

fn main() {
let x = 3;
let y = 0;
println!("{} / {} = {}", x, y, x / y);
}

Khi đó Rust sẽ dừng toàn bộ các đoạn code phía sau đó và in một câu thông báo lỗi như sau:

thread 'main' panicked at 'attempt to divide by zero'

Panic

Panic là một hàm trong Rust cho phép chúng ta in câu thông báo lỗi, ví dụ về cách sử dụng như sau:

fn main() {
let x = 2017;
let y = 0;
if(y == 0) {
panic!("y cannot be zero");
}
println!("{} / {} = {}", x, y, x / y);
}

Hàm panic!() sẽ dừng chương trình lại và in một dòng thông báo lỗi lên màn hình, các câu lệnh phía sau cũng sẽ không được thực hiện nữa.

thread 'main' panicked at 'y cannot be zero', src\main.rs:5:8

Ngoài ra Rust cũng có nhiều hàm khác như assert!(), assert_qe!()… ví dụ

fn main() {
let x =2017;
assert!(x == 9999);
assert_eq!(x, 9999);

let site = "phocode.com";
assert!(site =="micode.com", "The site’s name is incorrect");
}

Hàm assert!() nhận vào tham số đầu tiên là một giá trị true/false hoặc một biểu thức tính ra true/false. Nếu giá trị là false thì hàm này sẽ thực hiện panic!() ngay tại vị trí đó, ngoài ra chúng ta cũng có thể truyền thêm tham số thứ 2 để chỉ rõ vị trí trong phần thông báo lỗi hơn.

Hàm assert_eq!() thì nhận vào 2 tham số và so sánh 2 tham số đó có bằng nhau hay không.

Các hàm assert!()assert_eq!() trên sẽ lần lượt cho ra câu thông báo lỗi như sau nếu có:

thread 'main' panicked at 'assertion failed: x == 9999'
thread 'main' panicked at 'assertion failed: `(left == right)`
 left: `2017`,
 right: `9999`'
thread 'main' panicked at 'The site's name is incorrect'

Các hàm assert!() kiểu này thường được dùng để viết unit test. Chúng ta sẽ tìm hiểu sau.

Rust – Kiểu Generic

Kiểu Generic là kiểu dữ liệu cho phép chúng ta chỉ viết code một lần mà có thể sử dụng cho nhiều kiểu dữ liệu khác nhau. Nếu bạn đã từng lập trình Java hay C++ thì nó cũng tương tự như kiểu Generic trong Java và template trong C++ vậy.

Chúng ta có thể áp dụng kiểu generic vào struct và hàm trong Rust. Để các struct và hàm có thể hiểu được kiểu generic thì chúng ta phải thêm 3 kí tự sau đây vào sau tên của struct hoặc hàm:

<T>

Đầu tiên là dấu bé hơn <, rồi đến kí tự T, rồi đến dấu lớn hơn >. Chúng ta cũng có thể sử dụng kí tự khác ngoài T. Ví dụ:

struct MyGenericStruct<T> {
value: T
}

fn main() {
let year: MyGenericStruct<i32> = MyGenericType { value: 2017 };
let pi: MyGenericStruct<f64> = MyGenericType { value: 3.1415 };
let site: MyGenericStruct<&str>= MyGenericType { value: "phocode.com" };
}

Trong ví dụ trên chúng ta định nghĩa kiểu MyGenericStruct có một thuộc tính là value có kiểu là  generic.

Khi tạo các đối tượng generic thì chúng ta khai báo như struct bình thường và đưa kèm kiểu dữ liệu cụ thể mà chúng ta muốn sử dụng, chẳng hạn ở trên chúng ta tạo 3 đối tượng MyGenericStruct với 3 kiểu dữ liệu khác nhau là i32, f64str. Giá trị mà chúng ta khởi tạo cũng phải khớp với kiểu đã khai báo, nếu không Rust sẽ báo lỗi.

Khi sử dụng với hàm cũng vậy, chúng ta khai báo <T> trước tên hàm, bên trong nếu chúng ta nhận vào tham số là kiểu generic thì chúng ta cũng khai báo tên kiểu với <T>. Nếu hàm trả về giá trị của kiểu generic thì chúng ta chỉ cần ghi T là dược, ví dụ:

struct MyGenericType<T> {
value: T
}

fn getValue<T>(param: MyGenericType<T>) -> T {
return param.value;
}

fn main() {
let year: MyGenericType<i32> = MyGenericType { value: 2017 };
let pi: MyGenericType<f64> = MyGenericType { value: 3.1415 };
let site: MyGenericType<&str> = MyGenericType { value: "phocode.com" };
println!("Year: {:?}", getValue(year));
println!("PI = {:?}", getValue(pi));
println!("Site name: {:?}", getValue(site));
}

Trong đoạn code trên chúng ta định nghĩa hàm getValue() nhận vào tham số có kiểu MyGenericStruct tên là param, hàm này cũng trả về giá trị kiểu T, bên trong chúng ta cho trả về thuộc tính value của tham số.

Year: 2017
PI = 3.1415
Site name: "phocode.com"

Kiểu Option Result mà chúng ta đã học cũng là kiểu generic, định nghĩa của kiểu Option như sau:

enum Option<T> {
Some(T),
None
}

Như bạn thấy thì kiểu Option có thể nhận 1 trong 2 giá trị là một đối tượng Some hoặc None. Và đối tượng Some ở đây nhận một kiểu generic nên chúng ta có thể truyền vào giá trị gì cũng được, ví dụ:

fn main() {
let year: Option<i32> = Some(2017);
let none: Option<f64> = None;
let site: Option<&str> = Some("phocode.com");
println!("Year: {:?}", year);
println!("None: {:?}", none);
println!("Site name: {:?}", site);
}

Do đó kiểu Option là một kiểu dữ liệu generic được xây dựng sẵn khá mạnh mẽ.

Year: Some(2017)
None: None
Site name: Some("phocode.com")

Rust – Iterator

Iterator là một lớp đại diện cho các phần tử trong các kiểu dữ liệu danh sách như array, vector, slice… từ phần tử đầu tiên tới phần tử cuối cùng. Trong Rust, để lấy các đối tượng này thì chúng ta dùng phương thức next() trong các đối tượng danh sách.

Ví dụ:

fn main() {
let langs = ["Rust", "C++", "Java", "Python"];
let mut langs_iter = langs.iter();
println!("{:?}", langs_iter.next());
println!("{:?}", langs_iter.next());
println!("{:?}", langs_iter.next());
println!("{:?}", langs_iter.next());
println!("{:?"}. langs_iter.next());
}

Đầu tiên để lấy ra đối tượng Iterator thì chúng ta dùng hàm iter() có sẵn trong mảng. Sau đó để lấy phần tử tiếp theo trong Iterator thì chúng ta gọi hàm next(), hàm next() sẽ trả về đối tượng Some hoặc None chứ không phải là các giá trị thông thường.

Đối tượng Some sẽ được trả về khi Iterator vẫn lấy được giá trị, khi đã lấy ra phần tử cuối cùng trong danh sách thì hàm next() sẽ trả về đối tượng None.

Some("Rust")
Some("C++")
Some("Java")
Some("Python")
None

Iterator thường được dùng trong vòng lặp như sau:

fn main() {
let langs = ["Rust", "C++", "Java", "Python"];
for lang in langs.iter() {
println!("{}", lang);
}
}

Việc lặp thông qua Iterator giúp chúng ta tránh được một số lỗi như lỗi Index out of bound – tức lỗi chỉ số nằm ngoài phạm vi mảng.

Rust
C++
Java
Python

Ngoài việc dùng hàm iter() thì Rust có một cú pháp rút gọn khác là dùng kí tự & như sau:

fn main() {
let langs = ["Rust", "C++", "Java", "Python"];
for lang in &langs {
println!("{}", lang);
}
}

Chúng ta cũng có thể cho Iterator đi theo hướng từ cuối danh sách về đầu danh sách bằng cách gọi hàm rev() như sau:

fn main() {
let langs = ["Rust", "C++", "Java", "Python"];
for lang in langs.iter().rev() {
println!("{}", lang);
}
}

Python
Java
C++
Rust

Rust – Hàm High Order

Trong phần này chúng ta sẽ tìm hiểu về kiểu hàm higher order.

Thuật ngữ hàm higher order có nghĩa là một hàm có thể nhận tham số là một hàm khác, vì hàm thực chất cũng là các đối tượng nên cũng có thể truyền vào làm tham số được, khái niệm này cũng gần giống với khái niệm hàm callback vậy, chỉ khác ở chỗ là hàm higher order được gọi một cách tự do hơn, trong bài này mình sẽ không đi phân tích sự khác nhau giữa higher ordercallback.

Cú pháp để định nghĩa một hàm higher order như sau:

fn tên_hàm<tên kiểu : Fn(danh_sách_tham_số)>(danh_sách_tham_số) {

}

Ví dụ:

fn main() {
higherOrder(childFunction);
}

fn higherOrder<Ham: Fn()>(f: Ham) {
println!("This is the higher-order function calling to the child function");
f();
}

fn childFunction() {
println!("This is the child function");
}

Trong đoạn code trên, chúng ta định nghĩa một hàm higher order có tên là higherOrder(), hàm này nhận vào một tham số là một hàm khác có kiểu là Ham và chúng ta đặt tên tham số là f. Chúng ta định nghĩa tên và tham số của kiểu Ham ngay phía trước cặp dấu ngoặc tròn ().

Về cú pháp định nghĩa kiểu Ham thì chúng ta định nghĩa bên trong cặp dấu <>, đầu tiên là tên của kiểu dữ liệu hàm, rồi tới từ khóa Fn và cặp dấu ngoặc tròn, bên trong chúng ta cũng có thể khai báo các tham số, ở đoạn code trên chúng ta không có tham số nào.

Khi gọi hàm higher order thì chúng ta truyền tham số là tên của hàm khác, nếu hàm nhận vào có tham số thì chúng ta cũng mở dấu ngoặc và đưa tham số vào như bình thường, lưu ý là nếu hàm con không nhận tham số (như trong ví dụ trên) thì chúng ta không đưa cặp dấu ngoặc tròn vào.

This is the higher-order function calling to the child function
This is the child function

Truyền hàm closure – hay hàm anonymous

Chúng ta cũng có thể truyền các hàm closure thay vì định nghĩa một hàm một cách bình thường, ví dụ:

fn main() {
let square = |number| {
return (number * number);
};
higherOrder(square, 12);
}

fn higherOrder<HamSquare: Fn(i32) -> i32>(f: HamSquare, n: i32) {
println!("{}^{} = {}", n, n, f(n));
}

Trong đoạn code trên chúng ta định nghĩa hàm con có tên là square ngay bên trong hàm main(), và do đó square được gọi là hàm closure, hàm này nhận vào một số nguyên và trả về bình phương của số đó.

Sau đó bên trong hàm higherOrder(), chúng ta định nghĩa kiểu HamSquare nhận vào một số nguyên và trả về một số nguyên, mục đích là để giống với cú pháp của hàm square, ngoài ra chúng ta còn cho nhận thêm một số nguyên nữa để có thể truyền vào hàm con này.

12^12 = 144

Ngoài ra Rust cũng cho phép chúng ta truyền phần định nghĩa hàm closure vào bên trong lời gọi hàm luôn, ví dụ:

fn main() {
higherOrder(|number| {
return (number * number);
}, 12);
}

fn higherOrder<HamSquare: Fn(i32) -> i32>(f: HamSquare, n: i32) {
println!("{}^{} = {}", n, n, f(n));
}

Đoạn code trên vẫn cho ra kết quả cũ, chỉ khác là chúng ta truyền phần định nghĩa hàm vào trong lời gọi hàm luôn, tức là không còn tồn tại cái tên square nữa.

 

Rust – Câu lệnh match

Câu lệnh match có chức năng tương tự với câu lệnh switch trong các ngôn ngữ khác vậy.

Câu lệnh match sẽ kiểm tra xem một biến có giá trị bằng một giá trị nào đó hay không, nếu có thì thực hiện các câu lệnh tương ứng. Cú pháp của câu lệnh match như sau:

match <biến_kiểm_tra> {

<giá_trị> => <câu_lệnh>,

_ => <câu_lệnh>

};

Ví dụ:

fn main() { 
let monthNum: i32 = 4;
match monthNum {
1 => println!("January"),
2 => println!("February"),
3 => println!("March"),
4 => println!("April"),
5 => println!("May"),
6 => println!("June"),
7 => println!("July"),
8 => println!("August"),
9 => println!("September"),
10 => println!("October"),
11 => println!("November"),
12 => println!("December"),
_ => println!("Unknown")
};
}

Trong đoạn code trên chúng ta khai báo biến monthNum có giá trị 4, sau đó chúng ta kiểm tra biến này với câu lệnh match, câu lệnh này sẽ kiểm tra xem giá trị trong biến monthNum giống với giá trị nào trong danh sách, nếu tìm thấy một giá trị giống thì câu lệnh phía sau đó được thực hiện, nếu không giống thì câu lệnh trong giá trị _ sẽ được thực hiện.

April

Giá trị _ giống như giá trị default trong các ngôn ngữ khác, trong Rust thì chúng ta bắt buộc phải có _. Khi câu lệnh match tìm được một giá trị giống với giá trị được kiểm tra thì sẽ thực hiện các câu lệnh phía sau đó rồi dừng luôn chứ không kiểm tra tiếp nữa, do đó ở đây chúng ta không cần câu lệnh break như trong các ngôn ngữ khác.

Câu lệnh match cũng có thể dùng để trả về giá trị, điều kiện là câu lệnh được thực thi cuối cùng trong match phải là một câu lệnh tính toán hoặc có cho ra một giá trị nào đó, ví dụ:

fn main() {
let monthNum: i32 = 9999;
let monthStr: &str = match monthNum {
1 => "January",
2 => "February",
3 => "March",
4 => "April",
5 => "May",
6 => "June",
7 => "July",
8 => "August",
9 => "September",
10 => "October",
11 => "November",
12 => "December",
_ => "Unknown"
};
println!("{}", monthStr)
}

Để gán giá trị của match thì chúng ta chỉ cần dùng toán tử = là được.

Unknown

Chúng ta cũng có thể kiểm tra trong một khoảng giá trị nếu dùng giá trị kiểu số bằng kí tự ..., ví dụ:

fn main() { 
let mark: f32 = 8.5;
match mark {
8.0…10.0 => println!("Excellent"),
6.5…7.9 => println!("Good"),
5.0…6.4 => println!("Average"),
0.0…4.9 => println!("Poor"),
_ => println!("Cannot grade")
};
}

Excellent

Chúng ta cũng hay dùng match để kiểm tra giá trị của enum, ngoài ra chúng ta cũng có thể dùng toán tử | để kiểm tra nhiều giá trị cùng một lúc, ví dụ:

fn main() {
enum LANG {
Assembly, Scala,
C, Cpp, Rust,
Java, CSharp, Go,
HTML, CSS

let rustLang: LANG = LANG::Rust;
match rustLang {
LANG::Assembly | LANG::Scala => println!("Low level language"),
LANG::C | LANG::Cpp | LANG::Rust => println!("High level language"),
LANG::Java | LANG::CSharp | LANG::Go => println!("Very high level language"),
LANG::HTML | LANG::CSS => println!("Marker language"),
_ => println!("Unknown language")
};
}

High level language

Câu lệnh match cũng rất hay thường được dùng để bắt lỗi, chẳng hạn với các hàm ok().expect() như trong bài trước, ví dụ:

use std::io;
fn main() {
let mut buf = String::new();
println!("Your age: ");
io::stdin().read_line(&mut buf);
let buf_i32: Result<i32, _> = buf.trim().parse();
match buf_i32 {
Ok(i) => println!("Ok, your age in 2017 is {}", 2017 – i),
Err(i) => println!("Error, cannot parse number")
};
println!("Done");
}

Nếu như trong bài trước thì chúng ta gọi hàm ok() rồi tới expect() bình thường, thì nếu chúng ta không nhập vô chữ số mà nhập các kí tự thì Rust sẽ thông báo lỗi, và các câu lệnh sau đó sẽ không được thực thi. Còn trong đoạn code trên, chúng ta kiểm tra xem giá trị trong buf_i32 là một đôi tượng Ok hay là Err, và ứng với mỗi trường hợp chúng ta sẽ thực thi các câu lệnh khác nhau, do đó Rust sẽ không còn báo lỗi nữa và các câu lệnh phía sau vẫn sẽ được thực hiện.

Your age:
phocode.com
Error, cannot parse number
Done

Rust – Input

Trong phần này chúng ta sẽ tìm hiểu cách đọc dữ liệu.

Module io trong crate std Rust cung cấp rất nhiều các hàm và hỗ trợ đọc/ghi dữ liệu từ dòng dữ liệu tiêu chuẩn của máy tính. Ở đây chúng ta chỉ cần hiểu là chúng ta có thể lấy được những gì gõ từ bàn phím chuột, rồi từ đó có thể ghi lên các thiết bị xuất như màn hình, loa…

Chúng ta sẽ tìm hiểu về các thuật ngữ module và crate sau, trước mắt chúng ta chỉ cần biết là để sử dụng module và crate thì chúng ta sử dụng từ khóa use với tên module ở đầu file. Ví dụ:

use std::io;

fn main() {     
let mut buf = String::new();    
println!("Your name: ");    
io::stdin().read_line(&mut buf);    
println!("Hello, {}", buf);
}

Trong đoạn code trên, chúng ta sử dụng module std:io bằng cách sử dụng câu lệnh use, sau đó chúng ta gọi đến hàm stdin() của module này, hàm này sẽ tạo một đối tượng Stdin, và chúng ta dùng phương thức read_line() để đọc dữ liệu từ bàn phím rồi gán vào biến buf. Chúng ta sẽ tìm hiểu về khái niệm đối tượng và phương thức trong các bài sau.

Khi biên dịch và chạy, chương trình sẽ đọc dữ liệu từ dòng dữ liệu vào từ bàn phím cho đến khi đọc được dữ liệu của phím Enter thì dừng, và do đó chúng ta phải gõ một cái gì đó vào màn hình rồi bấm enter, đoạn code trên sẽ in ra những gì mà chúng ta gõ.

Your name:
Phocode
Hello, Phocode

Ngoài ra khi biên dịch Rust sẽ in một câu cảnh cáo là:

warning: unused `std::result::Result` which must be used

Vì hàm read_line() trả về một đối tượng Result, nên chúng ta nên tận dụng các hàm lấy kết quả của đối tượng này, lớp Result có 2 kiểu kết quả trả về, một là Ok (thành công), hai là Err (lỗi). Để có thể lấy được giá trị của 2 kết quả này thì chúng ta lần lượt gọi tới hàm ok()expect() của đối tượng này. Ví dụ:

use std::io;

fn main() {     
let mut buf = String::new();    
println!("Your name: ");    
io::stdin().read_line(&mut buf).ok().expect("error!");      
println!("Hello, {}", buf);
}

Hàm ok() sẽ chuyển đối tượng Result thành một đối tượng Option, sau đó chúng ta gọi hàm expect() của đối tượng này, hàm này sẽ  trả về giá trị của đối tượng Option, nếu có lỗi thì hàm này sẽ in câu thông báo lỗi mà chúng ta đã truyền vào lên màn hình.

Hiện tại thì đoạn code trên sẽ khó có thể in ra chuỗi bên trong expect() được vì Rust sẽ luôn luôn chờ cho đến khi chúng ta bấm phim enter và bắt đầu đọc, kể cả khi chúng ta không đưa kí tự nào vào, cách duy nhất để kiểm tra hàm expect() là tiến hành đọc từ các dòng dữ liệu khác như file, mạng internet… chúng ta sẽ tìm hiểu về chủ đề này sau.

Ngoài việc đọc các chuỗi bình thường, chúng ta cũng có thể đọc các kiểu dữ liệu khác như số, ví dụ:

use std::io;

fn main() {     
let mut buf = String::new();    
println!("Your birth year: ");      
io::stdin().read_line(&mut buf)
.ok()
.expect("error!");      
let buf_i32: Result<u32, _> = buf.trim().parse();    
println!("Your age in 2017 is {}", 2017 – buf_i32.ok().expect("Error"));
}

Chúng ta cũng đọc một chuỗi như bình thường, sau đó nếu chúng ta, chuyển sang kiểu u32 bằng cách dùng hàm trim() để loại bỏ kí tự enter trong chuỗi, rồi dùng hàm parse() để chuyển sang kiểu dữ liệu khác, ở đây là kiểu Result<u32, _>. Chúng ta sẽ tìm hiểu về cú pháp này sau.

Khi đã có đối tượng Result, chúng ta có thể gọi ok()expect() để lấy ra giá trị u32 gốc và có thể tính toán trên giá trị đó bình thường.

Your birth year:
1993
Your age in 2017 is 24

 

Rust – Enum

Enum là kiểu tập hợp các danh từ, tức là chỉ có các tên chứ không có giá trị, tuy nhiên các phần tử trong enum vẫn được coi là các kiểu dữ liệu mới.

Để tạo enum thì chúng ta dùng cú pháp sau:

enum <tên_enum> { tên các kiểu dữ liệu }

Ví dụ:

fn main() {
enum LANGUAGES { Rust, Cpp, Java, Python }
}

Muốn tạo một biến có kiểu dữ liệu của mộ trong các phần tử của enum thì chúng ta gọi tên enum, sau đó dùng dấu 2 chấm 2 lần và ghi tên của kiểu dữ liệu đó ra, ví dụ:

fn main() {
enum LANGUAGES { Rust, Cpp, Java, Python }
let rust = LANGUAGES::Rust;
}

Tuy nhiên nếu muốn các phần tử trong enum có thể lưu trữ giá trị thì chúng ta có thể định nghĩa enum như sau:

fn main() {
enum LANGUAGES {
Rust(i32),
Cpp(i32),
Java(i32),
Python(i32)
}

let rust = LANGUAGES::Rust(2010);
let cpp = LANGUAGES::Cpp(1983);
}

Enum được sử dụng rất nhiều trong câu lệnh match, chúng ta sẽ tìm hiểu câu lệnh này trong bài sau.

Rust – Struct

Trong phần này chúng ta sẽ tìm hiểu về kiểu dữ liệu struct.

Trong quá trình code, sẽ có một số trường hợp chúng ta cần phải gom nhóm một vài biến liên quan với nhau lại, các biến này có thể có kiểu dữ liệu giống nhau và khác nhau. Struct là một tính năng cho phép chúng ta tạo ra một kiểu dữ liệu mới, trong kiểu dữ liệu mới này chứa nhiều biến có liên quan với nhau, các biến có kiểu dữ liệu khác nhau hoặc giống nhau.

Để định nghĩa một struct thì chúng ta dùng cú pháp sau:

struct <tên_struct>(<danh sách các kiểu dữ liệu);

Sau đó khi nào muốn tạo các biến có kiểu dữ liệu struct tương ứng thì chúng ta gọi tên struct rồi đưa vào danh sách các giá trị. Ví dụ:

struct blog(&’static str, i32);
fn main() {
let phocode = blog("PhoCode", 2016);
}

Struct được định nghĩa kiểu này còn có tên gọi là tuple-struct vì cú pháp khá giống với kiểu tuple. Để lấy các phần tử trong struct kiểu này thì chúng ta có thể dùng cú pháp như sau:

struct blog(&’static str, i32);
fn main() {
let phocode = blog("PhoCode", 2016);
let blog(name, date) = phocode;
println!("{} – {}", name, date);
}

Đoạn code trên sẽ in ra kết quả như sau:

PhoCode - 2016

Kiểu tuple-struct mà chúng ta sử dụng ở phía trên không có nhiều lợi ích mấy, thông thường chúng ta sẽ sử dụng kiểu struct có thể định nghĩa cả tên của các biến bên trong nó như sau:

struct blog{
name: &’static str,
date: i32
}

Cú pháp ở đây khác cú pháp trên ở chỗ là mỗi phần tử của struct sẽ là một cặp <khóa>:<giá_trị>, chứ không còn là tên các kiểu dữ liệu đơn lẻ nữa. Ngoài ra chúng ta dùng cặp dấu ngoặc nhọn {} thay cho cặp dấu ngoặc tròn (), và không có dấu chấm phẩy cuối cùng. Để khởi tạo một biến thuộc kiểu struct này thì chúng ta khai báo như sau:

struct blog{
name: &’static str,
date: i32
}
fn main() {
let mut phocode = blog{
name: "PhoCode", 
date: 2016
};
println!("{} was launched in {}", phocode.name, phocode.date);
}

Ở đây chúng ta gọi tên struct, phía sau là cặp dấu ngoặc nhọn {}, rồi đến các phần tử cũng theo cú pháp <khóa>:<giá_trị>, kết thúc bằng dấu chấm phẩy. Để lấy các phần tử của struct này thì chúng ta dùng dấu chấm rồi gọi tên phần tử.

PhoCode was launched in 2016

Lưu ý là nếu chúng ta muốn thay đổi giá trị của các phần tử thì chúng ta phải khai báo biến của struct này là mut, và để gán giá trị mới cho các phần tử thì chúng ta cũng dùng dấu chấm để lấy phần tử rồi gán như bình thường, ví dụ:

phocode.name = "Lorem Ipsum";
phocode.date = 11111

Rust – Tuple

Trong phần này chúng ta sẽ tìm hiểu về kiểu Tuple.

Tuple là kiểu dữ liệu dạng danh sách nhưng khác với array và vector ở chỗ là chúng ta có thể lưu các phần tử với các kiểu dữ liệu khác nhau. Cú pháp để khởi tạo một tuple giống với cú pháp khởi tạo array, chỉ khác là ở đây chúng ta dùng cặp dấu ngoặc tròn thay cho cặp dấu ngoặc vuông, ví dụ:

fn main() {
let tup = ("phocode.com", 2017);
println!("{:?}", tup);
}

Bên trong tuple chúng ta có thể truyền các phần tử với các kiểu dữ liệu bất kì.

("phocode.com", 2017)

Tuple cũng lưu các phần tử theo chỉ số và bắt đầu từ 0, để lấy các phần tử theo chỉ số thì chúng ta dùng dấu chấm, ví dụ:

fn main() {
let tup = ("phocode.com", 2017);
println!("tup(1) = {}", tup.0); // tup(1) = phocode.com
println!("tup(2) = {}", tup.1); // tup(2) = 2017
}

Ngoài ra chúng ta có một kiểu cú pháp gán từng phần tử của tuple cho nhiều biến cùng một lúc như sau:

fn main() {
let tup = ("phocode.com", 2017);
let (name, year) = tup;
println!("name = {}", name);
println!("year = {}", year);
}

Ở đây chúng ta ghi danh sách tên các biến cần được gán và đưa vào trong cặp dấu ngoặc tròn (), mỗi biến cách nhau bằng dấu phẩy, rồi gán giá trị của tuple cho các biến này.

name = phocode.com
year = 2017

Chúng ta cũng có thể cho hàm trả về giá trị tuple như sau:

fn main() {
println!("{:?}", personal_info("Long", 24)); // ("Long", 24)
}

fn personal_info(name: &str, age: i32) -> (&str, i32) {
return (name, age);
}