1. Trang chủ
  2. » Luận Văn - Báo Cáo

một số khái niệm cơ bản trong rust

27 0 0
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 27
Dung lượng 2,05 MB

Nội dung

-Tuy nhiên thì trong một số trường hợp ta cần liên tục thay đổi giá trị cho 1 biến và trong rust ta có thể để biến thay đổi được bằng cách sử dụng từ khoá mut khi khởi tạo biến.-Chương t

Trang 1

Mục lục Phần 1: Giới thiệu

1 Chương trình đầu tiên

Trang 2

1.1 Hello world:

- Để tạo 1 chương trình trong Rust, đầu tiên cần tạo ra 1 project

để lưu trữ code:

- Tiếp theo, vào file main.rs và viết câu lệnh đơn giản như sau:

- Khi chạy ta sẽ được kết quả:

1.2 Cargo:

- Cargo là một công cụ của rust giúp xây dựng và quản lý chương trình Cargo giúp xây dựng code, tải các thư viện dùng trong đoạn code

- Tạo 1 dự án sử dụng Cargo bằng cách chạy lệnh:

- Cargo sẽ tự tạo cho ta 1 file Cargo.toml và 1 thư mục src, trong thư mục này thì cargo cũng tạo sẵn 1 file main.rs

- File Cargo.toml sẽ cho ta biết format chương trình vừa được tạo:

Khi chương trình được tạo bằng cargo thì các file rs sẽ được đặt vào thư mục src

Ta cũng có thể sử dụng cargo để xây dựng chương trình :

Và chạy :

Trang 3

Khi chương trình gần được hoàn thành thì có thể thêm lựa chọn –release vào câu lệnh cargo build để cargo tự động tối ưu chương trình giúp chương trình chạy nhanh hơn.

1.3 Modules, crates và package:

- Crates là phần code nhỏ nhất mà rust compiler có thể xử lý được trong 1 thời điểm, gồm binary crates và library crates

- Package là tập hợp của nhiều crates cùng thực hiện 1 mục đích nào đó

- Crate là các thực thể đã được biên dịch và phân phối trên máy tính để có thể chạy được Mặc định Rust sẽ quy định các đoạn code một crate được “chứa” trong một module có tên là root một cách ngầm định Sau đó các code này có thể được các coder phân chia thành các module nhỏ hơn, gọi là submodule nằm bên dưới module root

Module cũng có thể được định nghĩa bên trong một module khác, lúc này module đó được gọi là nested Khi biên dịch thì tất cả các module nằm trong một crate sẽ được biên dịch cùng với nhau

Phần 2: Những khái niệm cơ bản trong Rust

Trang 4

-Tuy nhiên thì trong một số trường hợp ta cần liên tục thay đổi giá trị cho 1 biến và trong rust ta có thể để biến thay đổi được bằng cách sử dụng từ khoá mut khi khởi tạo biến.

-Chương trình không còn báo lỗi:

- Trong rust thì bạn có thể tạo 1 biến mới có tên trùng với tên biến

đã được khởi tạo trước đó Trong rust gọi khái niệm đó là shadowing Ví dụ:

Trang 5

- Đầu tiên biến x gán cho 5, sau đó nó tạo một biến x mới rồi cộngthêm giá trị của biến cũ ban đầu thêm 1 Trong phạm vi của cặp dấu ngoặc thứ 2 một biến x mới được tạo ra và nhân giá trị trước

đó là 6 với 2

Kết quả chương trình:

3.4 Kiểu dữ liệu:

Trong rust có 4 kiểu dữ liệu vô hướng(nguyên thuỷ)

* Kiểu nguyên: các kiểu dữ liệu nguyên trong rust:

Ví dụ:

fnmain() {

let x: u32= ; 2

}

x là biến không âm có giá trị bằng 2 với kích thước 32 bit

- Rust phân biệt số nguyên âm và số không âm Nếu biến được gán kiểu i là âm và u là không âm

Trong rust thì cho phép lựa chọn độ dài của biến như trong bảng Đặc biệt với đồ dài là isize với số có dấu và usize với kiểu không dấu sẽ dựa vào kiến trúc của máy tính bạn sử dụng Ví dụ: biến sẽ

có độ dài là 64bit nếu bạn sử dụng máy tính với CPU là 64 bit

* Kiểu thực: có hai kiểu là f32 và f64

Trang 6

- Ví dụ:

* Kiểu Boolean:

*Kiểu kí tự:

* Kiểu dữ liệu tập hợp

Trong rust có hai kiểu dữ liệu tập hợp là mảng và bộ

- Bộ: bộ là tập hợp các giá trị khác nhau, trong rust thì độ dài của bộ có thể thay đổi

Ta có thể gán bộ bằng dấu “:” hoặc “=”

Ví dụ:

Ta có thể truy cập đến các phần tử của bộ bằng kí tự “.” Ví dụ:

Mảng: là tập hợp lưu trữ các giá trị Nhưng khác với bộ các phần

tử trong mảng có cùng kiểu dữ liệu

Ví dụ về mảng trong rust:

Gán kiểu cho mảng:

Trang 7

Truy cập phần tử mảng:

Phần 3: Ownership

Ownership là một khái niệm hoàn toàn mới được Rust giới thiệu,

có chức năng đảm bảo tính an toàn, tối ưu cho bộ nhớ mà không cần đến trình dọn rác (garbage collector) như các ngôn ngữ khác.Các ngôn ngữ lập trình khác nhau thì có những cách tiếp cận khác nhau để quản lý bộ nhớ (memory) Một số ngôn ngữ như Java, Golang , thì sử dụng garbage collection sẽ tự động giải phóng các ô nhớ không còn được sử dụng, các ngôn ngữ khác thì yêu cầu người dùng phải tự giải phóng bộ nhớ bằng tay (điển hình là C/C++) Với Rust, đội ngũ phát triển đã đề xuất một cách tiếp cận mới cho vấn đề quản lý bộ nhớ, đó

là ownership Ownership được định nghĩa là một bộ các quy tắc chỉ định cách quản lý bộ nhớ trong Rust, nếu bất kỳ quy tắc nào bị

vi phạm, chương trình sẽ không được biên dịch

Các quy tắc ownership

Mỗi giá trị trong Rust có một biến được gọi là chủ sở hữu (ownership) của nó

Chỉ có thể có một chủ sở hữu tại một thời điểm

Khi chủ sở hữu đi ra khỏi phạm vi, bộ nhớ lưu giá trị đó sẽ được giải phóng

1 Mỗi giá trị trong Rust có một biến được coi là chủ sở hữu:

2 Chỉ có thể có một chủ sở hữu tại một thời điểm:

Trang 8

- Giả sử một biến phải truyền giá trị cho một hàm, ownership sẽ truyền cho đối số của hàm

=> Nên sau khi gọi hàm xong, biến sẽ không có lại ownership vì giá trị được lưu đã được giải phóng

- Tương tự như trường hợp sau:

- Nếu chạy ta sẽ gặp lỗi như sau :

Trong những trường hợp khi biến không có trait Copy, để tránh việc 2 biến cùng truy cập vào 1 vùng dữ liệu và gây ra lỗi (double free memory, …) khi truyền giá trị vào hàm hay tạo ra 1 biến mới

có giá trị như biến cũ thì Rust sẽ vô hiệu hóa biến lúc đầu và coi như là biến đó không còn sở hữu giá trị nào

- Ownership có thể được trả lại từ 1 hàm khi trả giá trị về Đây chính là khái niệm move trong Rust

Trang 9

*Chú ý: 1 số kiểu dữ liệu nguyên thủy không có phép move là vì chúng được lưu trữ trên Stack nên khi cần tạo ra biến mới có giá trị như biến cũ thì chúng tạo ra cả biến mới và vùng lưu trữ có giá trị bằng giá trị biến cũ.

3 Khi chủ sở hữu ra khỏi phạm vi, thì vùng nhớ lưu giá trị đó sẽ được giải phóng:

- VD đơn giản:

- VD về String và i32:

References và borrowing

- Reference là 1 con trỏ trỏ đến 1 địa chỉ mà chỉ đến vùng nhớ của

1 biến khác Reference cho ta quyền truy cập đến giá trị của một biến mà không cần move

Trang 10

- Tạo ra 1 referece đến 1 biến được gọi là borrowing Ta chỉ mượn giá trị của biến đó chứ ko sở hữu chúng Nên ta sẽ phải trả lại vào

1 lúc nào đó

- Để có quyền thay đổi giá trị biến với Reference, ta cần sử dụng toán tử &mut:

Các quy tắc của Reference:

- Chỉ có 1 mutable reference hoặc bao nhiêu immutable reference cũng được tại bất kỳ thời điểm nào

- Reference phải luôn hợp lệ (tức luôn có giá trị để nó trỏ vào)

Phần 4: Struct

Trang 11

1 Khởi tạo struct:

Để định nghĩa một struct, chúng ta nhập từ khóa struct và đặt tên cho toàn bộ cấu trúc Sau đó, bên trong dấu ngoặc nhọn, chúng ta xác định tên và kiểu của các phần dữ liệu mà chúng ta gọi là trường

Để sử dụng một cấu trúc sau khi đã xác định nó, chúng ta tạo một đối tượng của cấu trúc đó bằng cách chỉ định các giá trị cụ thể cho từng trường Chúng ta tạo một đối tượng bằng cách nêu tên của cấu trúc và sau đó thêm dấu ngoặc nhọn chứa các cặp khóa: giá trị, trong đó các khóa là tên của các trường và giá trị là dữ liệu mà chúng ta muốn lưu trữ trong các trường đó Chúng ta không phải chỉ định các trường theo cùng thứ tự mà đã khai báo chúng trong cấu trúc Nói cách khác, định nghĩa struct giống như một mẫu chung cho kiểu và các đối tượng điền vào mẫu đó với dữ liệu cụ thể để tạo các giá trị của kiểu

Để nhận một giá trị cụ thể từ một cấu trúc, chúng ta có thể sử dụng

ký hiệu dấu chấm Nếu chúng tôi chỉ muốn địa chỉ email của người dùng này, chúng tôi có thể sử dụng user1.email ở bất cứ nơi nào chúng tôi muốn sử dụng giá trị này Nếu trường hợp có thể thay đổi, chúng ta có thể thay đổi giá trị bằng cách sử dụng ký hiệu dấu chấm và gán vào một trường cụ thể

Trang 12

Lưu ý rằng toàn bộ đối tượng phải có thể thay đổi được; Rust không cho phép chúng tôi đánh dấu chỉ một số trường nhất định là

Trang 13

=> Về cơ bản, struct tương tự như class trong java.

Phần 5: Enum

- enum là từ khóa cho phép tạo kiểu dữ liệu mới có giá trị nằm trong các phần tử của enum

VD:

* 1 kiểu enum hay được sử dụng trong Rust : Option

- Nó được sử dụng để diễn tả các giá trị null trong Rust:

Trang 17

- Khi dùng từ khóa impl:

2 Trait:

- Trait định nghĩa các hàm mà 1 kiểu dữ liệu có thể có, khá tương

tự với interface(abstract class) trong java mặc dù có vài khác biệt nhỏ

Trang 19

Ví dụ:

fn main() {

let langs = ["Rust", "C++", "Java", "Python"];

let mut langs_iter = langs.iter();

Đầ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

let langs = ["Rust", "C++", "Java", "Python"];

for lang in langs.iter() {

Output

Trang 20

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() {

- Cú pháp:

VD về 1 closure:

Trang 21

Và kết quả khi chạy:

-> Ta thấy rằng closure giữ lại giá trị của biến my_val sau mỗi lần chạy hàm Ngoài ra, với biến lưu trữ closure increment_closure ta phải có từ khóa mut để cho Rust biết hàm closure sẽ thay đổi giá trị biến

- Nói chung, closures tạo ra một tham chiếu đến các thực thể trongphạm vi của nó, nhưng có một loại closure khác được gọi là move closure chiếm ownership của tất cả các biến mà nó sử dụng

Ta sử dụng từ khóa move để xác định move closure Đây là cú pháp:

Phần 10: Macro

Trong các bài trước chúng ta đã làm việc rất nhiều với macro Mỗi lần chúng ta gọi một hàm mà có dấu chấm than ! ở sau tên thì đó chính là một macro có sẵn trong Rust Những macro mà chúng ta đã sử dụng

là println!, panic!, vec!, assert!… Kể từ bài này mình sẽ gọi chúng ta macro chứ không gọi là hàm như những bài trước nữa.

Trang 22

Tại sao cần đến macro?

Macro cho phép chúng ta tạo nên các cú pháp gọn nhẹ nhưng mạnh mẽ, nhờ đó mà việc lập trình trở nên dễ dàng hơn rất nhiều Chẳng hạn như trong Rust có macro regex! cho phép chúng ta định nghĩa các biểu thức chính quy, các biểu thức chính quy này sẽ được dịch và

sử dụng.

Macro giúp giảm đi việc lặp lại code nhiều lần, chúng ta có thể định nghĩa một macro thực hiện một công việc nào đó

và gọi mỗi khi cần.

Giống như với các ngôn ngữ khác, chúng ta cũng có thể định nghĩa macro của riêng chúng ta trong Rust.

Quy luật mà chúng ta nói tới ở đây có thể hiểu như trong câu lệnh match mà chúng ta đã học (hay câu

lệnh switch của C++, Java…), tức là chúng ta có thể truyền vào các tham số, và Rust sẽ so sánh xem tham số nào phù hợp và chạy đoạn code phía sau đó Ví dụ:

fn main() {

macro_rules! hello {

() => {

Trang 23

là hello!() và luật đầu tiên khớp sẽ chạy, ở đây chuỗi Hello World sẽ được in ra.

expr: biểu thức cho ra một giá trị (vd 2+2, ‘phocode’) item: một thành phần của một crate (chẳng

hạn struct Blog)

block: một khối lệnh (vd { println!(); return 12; } ) stmt: một câu lệnh (vd let x = 3)

pat: mẫu hay patter (vd (17, 'a') )

ty: biến theo sau bởi dấu => hoặc : => as

ident: tên một biến (vd x; str)

path: tên một struct, interface… hợp lệ

(vd Blog::PhoCode)

tt: token tree (bạn có thể tự tìm hiểu thêm)

Ví dụ:

Trang 24

ta cho in giá trị của biểu thức đó ra.

Output

1 Hello Pho Code

Macro nhiều tham số

Chúng ta có thể truyền vào nhiều tham số cho một macro,

Cú pháp này của Rust hơi lạ mắt và khó đọc, bạn có thể

tự suy ra, ở đây mình sẽ không giải thích Bạn có thể viết liền trên một hàng chứ không nhất thiết phải xuống dòng như mình.

Trang 25

từ $arg Khi gọi create_a_function!() là chúng ta đã tạo được một hàm mới và có thể gọi một cách bình thường Ngoài ra ở đây chúng ta còn dùng đến macro stringify để chuyển $arg thành một chuỗi.

Output

1 Calling the phocode function

Gọi macro của crate khác

Đây là trường hợp đã được đề cập trong bài trước , khi chúng ta gọi macro error!() của crate log, chúng ta phải thêm dòng #[macro_use] vào đầu file.

Trang 26

extern crate log;

extern crate env_logger;

extern crate log;

extern c<span data-mce-type="bookmark" style="display: inline-block; width: 0px; overflow: hidden; line-height: 0;" class="mce_SELRES_start"></span>rate env_logger;

Đóng gói:

Rust hỗ trợ tính đóng gói bằng cách tạo ra các đối tượng với thuộc tính bằng struct và các phương thức được khai triển sử dụng từ khóa “impl”

Trang 27

Tham số đầu tiên của bất cứ phương thức nào phải là một biến tham chiếu đến đối tượng gọi hàm

(e.g instanceOfObj.foo()) Rust sử dụng:

&self - biến tham chiếu không thay đổi được

&mut self - biến tham chiếu thay đổi được

Trừu tượng hóa :

Rust có thể xóa đi những khai triển bên trong của đối tượng

Mặc định trường struct và phương thức chỉ được truy cập vào module mà nó nằm trong

Từ khóa pub cho phép chúng truy cập vào các module bênngoài

Đa hình :

Rust hỗ trợ tính đa hình bằng cách sử dụng trait Traits hoạt động tương tự như interface trong Java, cho phép nhiều struct khai triển cùng 1 phương thức

Khi 1 struct khai triển 1 trait, nó cho phép chúng ta tương tác gián tiếp với struct thông qua kiểu của trait mà không cần biết kiểu thực sự là gì

Thừa kế :

Rust không hỗ trợ thừa kế các biến hay hàm của 1 struct

và vì vậy, không có thừa kế 1 cách chính thống như Java Tuy nhiên, 1 số hành động cần thừa kế trong các ngôn ngữ khác

có thể được thay thế bằng trait object

Ngày đăng: 14/05/2024, 16:06

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN

w