Skip to content

Latest commit

 

History

History
1308 lines (1002 loc) · 36.4 KB

2.3-slides.md

File metadata and controls

1308 lines (1002 loc) · 36.4 KB
title description duration
Module 2.3 - Smart pointers & Macros
Smart pointers & Macros
30 minutes

Smart Pointers

  • Allow multiple owners of data
  • Reference counted – “Rc”
    • Data deleted only when last owner is dropped
  • Atomic reference counted – “Arc”
    • Safe to use with multiple threads

Notes: Nội dung chính của slide: Allow multiple owners of data:

Smart pointers cho phép nhiều owner cùng sở hữu dữ liệu. Reference counted – “Rc”:

Data deleted only when last owner is dropped: "Rc" là viết tắt của Reference Counted, nghĩa là số lượng tham chiếu đến dữ liệu sẽ được đếm. Dữ liệu sẽ chỉ bị xóa khi owner cuối cùng bị loại bỏ. Atomic reference counted – “Arc”:

Safe to use with multiple threads: "Arc" là viết tắt của Atomic Reference Counted, nghĩa là số lượng tham chiếu được đếm theo cách an toàn với nhiều luồng (thread). Arc an toàn để sử dụng với nhiều luồng vì nó đảm bảo tính nhất quán dữ liệu trong môi trường đa luồng.


use std::rc::Rc;

#[derive(Debug)]
struct Vehicle {
    vin: String,
}

#[derive(Debug)]
struct Door {
    vehicle: Rc<Vehicle>,
}

let car = Rc::new(Vehicle {
    vin: "123".to_owned(),
});

let left_door = Door {
    vehicle: Rc::clone(&car),
};

let right_door = Door {
    vehicle: Rc::clone(&car),
};

drop(car);

println!("vehicle = {:?}", left_door.vehicle);

Notes: Import và định nghĩa các cấu trúc dữ liệu:

rust Copy code use std::rc::Rc;

#[derive(Debug)] struct Vehicle { vin: String, }

#[derive(Debug)] struct Door { vehicle: Rc, } use std::rc::Rc: Import Rc từ thư viện chuẩn của Rust. Định nghĩa cấu trúc Vehicle với một trường vin kiểu String. Định nghĩa cấu trúc Door với một trường vehicle kiểu Rc. Tạo một instance của Vehicle được bọc trong một Rc:

rust Copy code let car = Rc::new(Vehicle { vin: "123".to_owned(), }); Tạo một instance của Vehicle với vin là "123" và bọc nó trong một Rc. Tạo các instance của Door và clone Rc của Vehicle:

rust Copy code let left_door = Door { vehicle: Rc::clone(&car), };

let right_door = Door { vehicle: Rc::clone(&car), }; Tạo left_door và right_door với vehicle là bản clone của Rc. Rc::clone(&car) tạo một bản sao của Rc mà không sao chép dữ liệu bên trong Vehicle. Điều này chỉ tăng reference count. Drop instance car:

rust Copy code drop(car); drop(car) giảm reference count của Rc. Dữ liệu bên trong Vehicle sẽ không bị giải phóng vì vẫn còn các owner khác (left_door.vehicle và right_door.vehicle). In ra vehicle từ left_door:

rust Copy code println!("vehicle = {:?}", left_door.vehicle); Sử dụng println! để in ra vehicle từ left_door. Vì Vehicle vẫn còn owner (reference count > 0), dữ liệu bên trong nó vẫn tồn tại và sẽ được in ra. Kết quả Chạy đoạn mã này sẽ in ra:

css Copy code vehicle = Vehicle { vin: "123" } Tóm tắt Rc (Reference Counted) cho phép nhiều owner sở hữu cùng một dữ liệu. Reference count được tăng lên mỗi khi clone Rc. Dữ liệu bên trong Rc chỉ bị giải phóng khi reference count giảm về 0. drop(car) giảm reference count, nhưng dữ liệu vẫn tồn tại vì left_door và right_door vẫn giữ reference.


Declarative Macros

Notes: Macros Khai báo


Writing Declarative Macros

  • Consist of two parts: Matchers and Transcribers
  • Matchers define input patterns to match upon
    • The input patterns are different than patterns used in (for example) match and if let
      • Completely different ruleset
    • Multiple matchers may be defined for one macro
      • Checked from top to bottom
  • Transcribers read the input captured by the matchers and then emit Rust code
    • Code transcribed completely replaces the macro invocation
  • Macros must appear before usage in code

Notes: Macros khai báo trong Rust bao gồm hai phần chính: Matchers và Transcribers.

Matchers Matchers định nghĩa các mẫu đầu vào để khớp. Các mẫu đầu vào này khác với các mẫu được sử dụng trong match và if let. Có một tập luật hoàn toàn khác. Có thể định nghĩa nhiều matcher cho một macro. Các matcher được kiểm tra từ trên xuống dưới. Transcribers Transcribers đọc đầu vào được các matcher khớp và sau đó phát ra mã Rust. Mã được chuyển hoàn toàn thay thế lời gọi macro. Lưu ý quan trọng Macros phải xuất hiện trước khi sử dụng trong mã.


Matchers

  • Matchers consist of four components:
    • Metavariables
    • Fragment specifiers
    • Repetitions (covered later)
    • Glyphs: anything not listed above
      • Dollar ($) is used by metavariables and cannot be used for glyphs
  • Whitespace is ignored
    • Can be used for clarity in macro and by invoker

Notes: Matchers trong Rust macros bao gồm bốn thành phần chính:

Metavariables

Là các biến đại diện cho một giá trị đầu vào trong macro. Được đánh dấu bằng dấu $ (ví dụ: $var). Fragment Specifiers

Chỉ định loại đoạn mã mà metavariable sẽ đại diện. Ví dụ: expr cho biểu thức, ty cho loại, ident cho định danh. Repetitions

Sử dụng để lặp lại một mẫu nhiều lần. Sẽ được đề cập chi tiết sau. Glyphs

Là bất kỳ ký tự nào không thuộc các loại trên. Dấu $ được sử dụng bởi metavariables và không thể được sử dụng cho glyphs.


Metavariables

  • Contain Rust code supplied by macro invoker
  • Used by the transcriber to make substitutions
    • Metavariable will be substituted with the code provided by the invoker
  • Metavariables start with a dollar ($)
  $fn
  $my_metavar
  $varname

Notes: Metavariables: Chứa mã Rust được cung cấp bởi người gọi macro

Các metavariables sẽ được thay thế bằng đoạn mã mà người gọi cung cấp khi gọi macro. Sử dụng bởi transcriber để thực hiện các thay thế

Metavariable sẽ được thay thế bằng mã được cung cấp bởi người gọi. Metavariables bắt đầu bằng ký hiệu đô la ($)

Ví dụ về Metavariables: $fn: Metavariable đại diện cho một hàm hoặc tên hàm. $my_metavar: Metavariable với tên tùy chỉnh. $varname: Metavariable đại diện cho một tên biến.


Fragment Specifiers

  • Fragment specifiers determine what kind of data is allowed in a metavariable
  • Available specifiers are:
    • item
    • block
    • stmt
    • pat_param / pat
    • expr
    • ty
    • ident
    • path
    • tt
    • meta
    • lifetime
    • vis
    • literal

Notes:

Fragment Specifiers trong Rust Macros Fragment Specifiers: Fragment specifiers xác định loại dữ liệu nào được phép trong một metavariable.

Các specifiers có sẵn là:

item block stmt pat_param / pat expr ty ident path tt meta lifetime


Creating a Macro

macro_rules! your_macro_name {
    ($metavariable_name:fragment_specifier) => {
        // Can use $metavariable_name
    };
    ($a:ident, $b:literal, $c:tt) => {
        // Can use $a $b $c
    };
    () => {};
}    ^     ^
     |     | 
Matcher    Transcriber

Notes: Cấu trúc của một Macro Macros trong Rust được định nghĩa với cú pháp sau đây:

rust Copy code macro_rules! your_macro_name { ($metavariable_name:fragment_specifier) => { // Có thể sử dụng $metavariable_name }; ($a:ident, $b:literal, $c:tt) => { // Có thể sử dụng $a $b $c }; () => {}; } Thành phần của Macro Matcher:

Đây là phần đầu của macro, xác định các mẫu (patterns) mà macro sẽ khớp. Matcher chứa các metavariables (ví dụ: $metavariable_name, $a, $b, $c) và các fragment specifiers. Transcriber:

Đây là phần sau của macro, chứa mã sẽ được tạo ra khi macro được gọi. Transcriber sử dụng các metavariables từ phần matcher để tạo mã. Ví dụ Dưới đây là một ví dụ về cách tạo macro:

rust Copy code macro_rules! my_macro { ($name:ident) => { println!("Hello, {}!", stringify!($name)); }; ($a:expr, $b:expr) => { println!("Sum: {}", $a + $b); }; }

fn main() { my_macro!(world); // Hello, world! my_macro!(5, 10); // Sum: 15 } Trong ví dụ này:

Matcher đầu tiên khớp với một identifier ($name:ident) và in ra "Hello, [name]!". Matcher thứ hai khớp với hai biểu thức ($a:expr, $b:expr) và in ra tổng của chúng. Giải thích Metavariables: Chứa mã Rust do invoker macro cung cấp. Chúng bắt đầu bằng dấu $. Fragment Specifiers: Xác định loại dữ liệu nào được phép trong một metavariable (ví dụ: ident, literal, tt, expr). Hy vọng điều này giúp bạn hiểu rõ hơn về cách tạo và sử dụng macros trong Rust!


Glyphs

macro_rules! demo {
    (_ wow! _ any | thing? yes.#meta (^.^)) => { };
}

demo!(_ wow! _ any|thing?yes.#meta(^.^));

Notes: Trong Rust, Glyphs là những ký hiệu đặc biệt được sử dụng trong các macro để khớp và thao tác với các mẫu mã nguồn. Glyphs không bao gồm ký hiệu $, vì ký hiệu này được sử dụng bởi các metavariables.

Dưới đây là một ví dụ về việc sử dụng glyphs trong macro:

rust Copy code macro_rules! demo { (_ wow! _ any | thing? yes.#meta (^.^)) => { // Transcriber }; }

fn main() { demo!(_ wow! _ any | thing? yes.#meta (^.^)); } Giải thích Macro Definition:

macro_rules! demo định nghĩa một macro tên là demo. Matcher (_ wow! _ any | thing? yes.#meta (^.^)) khớp với một mẫu cụ thể bao gồm các glyph _, !, |, ?, #, (, ), và các từ wow, any, thing, yes, meta, và chuỗi (^.^). Invocation:

Trong main, demo!(_ wow! _ any | thing? yes.#meta (^.^)); gọi macro demo với các tham số khớp với matcher đã định nghĩa. Các glyphs cho phép macro trong Rust khớp và thao tác với các mẫu mã nguồn phức tạp, giúp việc viết macro trở nên linh hoạt và mạnh mẽ hơn.

Nếu bạn có thêm câu hỏi nào khác hoặc cần giải thích chi tiết hơn, hãy cho tôi biết!


Fragment Specifier: item

macro_rules! demo {
    ($i:item) => { $i };
}

demo!(const a: char = 'g');
demo! {fn hello(){}}
demo! {mod demo{}}
struct MyNum(i32);
demo! {
    impl MyNum {
        pub fn demo(&self) {
            println!("my num is {}", self.0);
        }
    }
}

Notes: Trong Rust, item là một trong các fragment specifiers cho phép macro xử lý toàn bộ một mục (item). Một item có thể là một hàm, một struct, một enum, một module, một hằng số, v.v.

Dưới đây là một ví dụ sử dụng fragment specifier item trong macro:

rust Copy code macro_rules! demo { ($i:item) => { $i }; }

demo!(const a: char = 'g'); demo! { fn hello() {} } demo! { mod demo {} } struct MyNum(i32); demo! { impl MyNum { pub fn demo(&self) { println!("my num is {}", self.0); } } }

fn main() { let num = MyNum(10); num.demo(); } Giải thích Macro Definition:

macro_rules! demo định nghĩa một macro tên là demo. Matcher ($i:item) khớp với một item và gán nó vào metavariable $i. Transcriber { $i } thay thế macro invocation bằng nội dung của $i. Macro Invocations:

demo!(const a: char = 'g'); tạo ra một hằng số a. demo! { fn hello() {} } tạo ra một hàm hello. demo! { mod demo {} } tạo ra một module demo. demo! { impl MyNum { ... } } tạo ra một implementation cho struct MyNum. Output:

Khi chạy hàm main, struct MyNum được tạo ra và hàm demo được gọi, in ra my num is 10. Sử dụng fragment specifier item trong macro giúp việc xử lý và tạo ra các thành phần phức tạp của mã nguồn trở nên dễ dàng hơn, cho phép viết các macro linh hoạt và mạnh mẽ.

Nếu bạn có thêm câu hỏi nào khác hoặc cần giải thích chi tiết hơn, hãy cho tôi biết!


Fragment Specifier: block

macro_rules! demo {
    ($b:block) => { $b };
}

let num = demo!(
    {
        if 1 == 1 { 1 } else { 2 }
    }
);

Notes: Trong Rust, block là một fragment specifier được sử dụng trong macro để khớp với một khối mã (block of code). Một khối mã thường được bao quanh bởi dấu ngoặc nhọn {} và chứa nhiều biểu thức hoặc câu lệnh.

Dưới đây là một ví dụ sử dụng fragment specifier block trong macro:

rust Copy code macro_rules! demo { ($b:block) => { $b }; }

fn main() { let num = demo!({ if 1 == 1 { 1 } else { 2 } });

println!("The number is: {}", num);

} Giải thích Macro Definition:

macro_rules! demo định nghĩa một macro tên là demo. Matcher ($b:block) khớp với một khối mã và gán nó vào metavariable $b. Transcriber { $b } thay thế macro invocation bằng nội dung của $b. Macro Invocation:

let num = demo!({ ... }); truyền một khối mã vào macro demo. Khối mã này chứa một biểu thức điều kiện if else trả về 1 nếu điều kiện đúng và 2 nếu điều kiện sai. Output:

Khi chạy hàm main, giá trị của num sẽ là 1 vì điều kiện 1 == 1 là đúng. Kết quả được in ra là: The number is: 1. Sử dụng fragment specifier block trong macro cho phép bạn xử lý các khối mã lớn và phức tạp một cách linh hoạt và mạnh mẽ.

Nếu bạn có thêm câu hỏi nào khác hoặc cần giải thích chi tiết hơn, hãy cho tôi biết!


Fragment Specifier: stmt

macro_rules! demo {
    ($s:stmt) => { $s };
}

demo!( let a = 5 );
let mut myvec = vec![];
demo!( myvec.push(a) );

Notes: Trong Rust, stmt là một fragment specifier được sử dụng trong macro để khớp với một câu lệnh (statement). Một câu lệnh có thể là khai báo biến, gọi hàm, hay bất kỳ câu lệnh nào khác trong Rust.

Dưới đây là một ví dụ sử dụng fragment specifier stmt trong macro:

rust Copy code macro_rules! demo { ($s:stmt) => { $s }; }

fn main() { demo!( let a = 5 ); let mut myvec = vec![]; demo!( myvec.push(a) );

println!("{:?}", myvec); // Output sẽ là [5]

} Giải thích Macro Definition:

macro_rules! demo định nghĩa một macro tên là demo. Matcher ($s:stmt) khớp với một câu lệnh và gán nó vào metavariable $s. Transcriber { $s } thay thế macro invocation bằng nội dung của $s. Macro Invocation:

demo!( let a = 5 ); truyền một câu lệnh khai báo biến vào macro demo. demo!( myvec.push(a) ); truyền một câu lệnh gọi hàm vào macro demo. Output:

Trong hàm main, biến a được khai báo và gán giá trị 5. Một vector myvec được khai báo và khởi tạo rỗng. Giá trị của a được đẩy vào vector myvec. Kết quả là myvec chứa giá trị [5] và được in ra. Sử dụng fragment specifier stmt trong macro cho phép bạn xử lý và thay thế các câu lệnh trong mã nguồn một cách linh hoạt và mạnh mẽ.

Nếu bạn có thêm câu hỏi nào khác hoặc cần giải thích chi tiết hơn, hãy cho tôi biết!


Fragment Specifier: pat / pat_param

macro_rules! demo {
    ($p:pat) => {{
        let num = 3;
        match num {
            $p => (),
            1 => (),
            _ => (),
        }
    }};
}

demo!( 2 );

Notes: Trong Rust, pat và pat_param là các fragment specifier được sử dụng trong macro để khớp với một pattern (mẫu). Những pattern này thường được sử dụng trong các câu lệnh match, let, và if let.

Dưới đây là một ví dụ sử dụng fragment specifier pat trong macro:

rust Copy code macro_rules! demo { ($p:pat) => {{ let num = 3; match num { $p => (), 1 => (), _ => (), } }}; }

fn main() { demo!(2); // Sử dụng macro với pattern là 2 } Giải thích Macro Definition:

macro_rules! demo định nghĩa một macro tên là demo. Matcher ($p:pat) khớp với một pattern và gán nó vào metavariable $p. Transcriber {{ ... }} chứa một khối lệnh trong đó có một câu lệnh match sử dụng $p. Macro Invocation:

demo!(2) truyền pattern 2 vào macro demo. Output:

Trong hàm main, biến num được khai báo và gán giá trị 3. Câu lệnh match so sánh num với pattern được truyền vào (2), với 1, và _ (default case). Do num là 3, pattern _ sẽ khớp và câu lệnh match sẽ thực hiện nhánh tương ứng với _. Sử dụng fragment specifier pat trong macro cho phép bạn xử lý và thay thế các pattern trong mã nguồn một cách linh hoạt và mạnh mẽ.

Nếu bạn có thêm câu hỏi nào khác hoặc cần giải thích chi tiết hơn, hãy cho tôi biết!


Fragment Specifier: expr

macro_rules! demo {
    ($e:expr) => { $e };
}

demo!( loop {} );
demo!( 2 + 2 );
demo!({
    panic!();
});

Notes: Trong Rust, expr là một fragment specifier được sử dụng trong macro để khớp với một biểu thức (expression). Các biểu thức có thể là các phép toán số học, các khối lệnh, vòng lặp, hoặc các macro khác.

Dưới đây là một ví dụ sử dụng fragment specifier expr trong macro:

rust Copy code macro_rules! demo { ($e:expr) => { $e }; }

fn main() { demo!( loop {} ); // Sử dụng macro với biểu thức vòng lặp demo!( 2 + 2 ); // Sử dụng macro với biểu thức phép toán demo!({ // Sử dụng macro với khối lệnh chứa macro khác panic!(); }); } Giải thích Macro Definition:

macro_rules! demo định nghĩa một macro tên là demo. Matcher ($e:expr) khớp với một biểu thức và gán nó vào metavariable $e. Transcriber { $e } thay thế macro invocation bằng biểu thức $e. Macro Invocation:

demo!(loop {}) truyền một biểu thức vòng lặp loop {} vào macro demo. demo!(2 + 2) truyền một biểu thức phép toán 2 + 2 vào macro demo. demo!({ panic!(); }) truyền một khối lệnh chứa macro panic!() vào macro demo. Sử dụng fragment specifier expr trong macro cho phép bạn xử lý và thay thế các biểu thức trong mã nguồn một cách linh hoạt và mạnh mẽ.

Nếu bạn có thêm câu hỏi nào khác hoặc cần giải thích chi tiết hơn, hãy cho tôi biết!


Fragment Specifier: ty

macro_rules! demo {
    ($t:ty) => {{
        let d: $t = 4;
        fn add(lhs: $t, rhs: $t) -> $t {
            lhs + rhs
        }
    }};
}

demo!(i32);
demo!(usize);

Notes: Trong Rust, ty là một fragment specifier được sử dụng trong macro để khớp với một kiểu dữ liệu (type). Các kiểu dữ liệu có thể là các loại dữ liệu cơ bản như i32, usize, hay các kiểu dữ liệu phức tạp hơn.

Dưới đây là một ví dụ sử dụng fragment specifier ty trong macro:

rust Copy code macro_rules! demo { ($t:ty) => {{ let d: $t = 4; fn add(lhs: $t, rhs: $t) -> $t { lhs + rhs } }}; }

fn main() { demo!(i32); demo!(usize); } Giải thích Macro Definition:

macro_rules! demo định nghĩa một macro tên là demo. Matcher ($t:ty) khớp với một kiểu dữ liệu và gán nó vào metavariable $t. Transcriber { ... } thay thế macro invocation bằng một khối mã chứa kiểu dữ liệu $t. Macro Invocation:

demo!(i32) truyền kiểu dữ liệu i32 vào macro demo. demo!(usize) truyền kiểu dữ liệu usize vào macro demo. Trong khối mã của macro, biến d được khai báo với kiểu $t, và hàm add được định nghĩa để cộng hai biến có kiểu $t. Điều này cho phép macro xử lý các kiểu dữ liệu khác nhau một cách linh hoạt.

Nếu bạn có thêm câu hỏi nào khác hoặc cần giải thích chi tiết hơn, hãy cho tôi biết!


Fragment Specifier: ident

macro_rules! demo {
    ($i:ident, $i2:ident) => {
        fn $i() {
            println!("hello");
        }
        let $i2 = 5;
    };
}

demo!(say_hi, five);

say_hi();
assert_eq!(5, five);

Notes: Trong Rust, ident là một fragment specifier được sử dụng trong macro để khớp với một định danh (identifier). Các định danh có thể là tên của các biến, hàm, hoặc các thực thể khác.

Dưới đây là một ví dụ sử dụng fragment specifier ident trong macro:

rust Copy code macro_rules! demo { ($i:ident, $i2:ident) => { fn $i() { println!("hello"); } let $i2 = 5; }; }

fn main() { demo!(say_hi, five);

say_hi();
assert_eq!(5, five);

} Giải thích Macro Definition:

macro_rules! demo định nghĩa một macro tên là demo. Matcher ($i:ident, $i2:ident) khớp với hai định danh và gán chúng vào các metavariable $i và $i2. Transcriber { ... } thay thế macro invocation bằng một khối mã chứa các định danh $i và $i2. Macro Invocation:

demo!(say_hi, five) truyền hai định danh say_hi và five vào macro demo. Trong khối mã của macro, hàm say_hi được định nghĩa và in ra "hello" khi được gọi. Biến five được khai báo và gán giá trị 5. Sau đó, hàm say_hi được gọi và giá trị của biến five được so sánh với 5 bằng assert_eq!.

Nếu bạn có thêm câu hỏi nào khác hoặc cần giải thích chi tiết hơn, hãy cho tôi biết!


Fragment Specifier: path

macro_rules! demo {
    ($p:path) => {
        use $p;
    };
}

demo!(std::collections::HashMap);

Notes: Trong Rust, path là một fragment specifier được sử dụng trong macro để khớp với một đường dẫn (path). Đường dẫn này có thể là các tên không gian (namespace) hoặc các mô-đun (module).

Dưới đây là một ví dụ sử dụng fragment specifier path trong macro:

rust Copy code macro_rules! demo { ($p:path) => { use $p; }; }

fn main() { demo!(std::collections::HashMap);

// Bây giờ có thể sử dụng HashMap mà không cần phải thêm std::collections::
let mut map: HashMap<i32, i32> = HashMap::new();
map.insert(1, 2);
println!("{:?}", map);

} Giải thích Macro Definition:

macro_rules! demo định nghĩa một macro tên là demo. Matcher ($p:path) khớp với một đường dẫn và gán nó vào metavariable $p. Transcriber { use $p; } thay thế macro invocation bằng một khối mã use chứa đường dẫn $p. Macro Invocation:

demo!(std::collections::HashMap) truyền đường dẫn std::collections::HashMap vào macro demo. Khi macro demo được gọi với đường dẫn std::collections::HashMap, nó sẽ tạo ra mã use std::collections::HashMap;, giúp sử dụng HashMap trực tiếp mà không cần phải chỉ định đầy đủ đường dẫn trong phần còn lại của mã.

Nếu bạn có thêm câu hỏi nào khác hoặc cần giải thích chi tiết hơn, hãy cho tôi biết!


Fragment Specifier: tt

macro_rules! demo {
    ($t:tt) => {
        $t
    };
}

demo!(loop);

demo!({
    println!("hello");
});

Notes: Trong Rust, tt (token tree) là một fragment specifier được sử dụng trong macro để khớp với một cây token. Điều này có nghĩa là nó có thể khớp với bất kỳ đoạn mã nào, bao gồm các biểu thức, khối mã, hoặc thậm chí là các câu lệnh.

Dưới đây là một ví dụ sử dụng fragment specifier tt trong macro:

rust Copy code macro_rules! demo { ($t:tt) => { $t }; }

fn main() { demo!(loop { println!("This is a loop!"); break; });

demo!({
    println!("Hello from inside a block!");
});

} Giải thích Macro Definition:

macro_rules! demo định nghĩa một macro tên là demo. Matcher ($t:tt) khớp với bất kỳ đoạn mã nào và gán nó vào metavariable $t. Transcriber { $t } thay thế macro invocation bằng đoạn mã $t. Macro Invocation:

demo!(loop { println!("This is a loop!"); break; }) truyền một vòng lặp loop vào macro demo. demo!({ println!("Hello from inside a block!"); }) truyền một khối mã vào macro demo. Khi macro demo được gọi với bất kỳ đoạn mã nào, nó sẽ thay thế đoạn mã đó vào nơi macro được gọi.

Nếu bạn có thêm câu hỏi nào khác hoặc cần giải thích chi tiết hơn, hãy cho tôi biết!


Fragment Specifier: meta

macro_rules! demo {
    ($m:meta) => {
        #[derive($m)]
        struct MyNum(i32);
    };
}

demo!(Debug);

Notes: Trong Rust, meta là một fragment specifier được sử dụng trong macro để khớp với các meta items (các thành phần siêu dữ liệu) trong cú pháp thuộc tính (attribute syntax). Điều này có nghĩa là nó có thể khớp với các thuộc tính như #[derive(Debug)], #[test], v.v.

Dưới đây là một ví dụ sử dụng fragment specifier meta trong macro:

rust Copy code macro_rules! demo { ($m:meta) => { #[derive($m)] struct MyNum(i32); }; }

fn main() { demo!(Debug);

let num = MyNum(42);
println!("{:?}", num);

} Giải thích Macro Definition:

macro_rules! demo định nghĩa một macro tên là demo. Matcher ($m:meta) khớp với một meta item và gán nó vào metavariable $m. Transcriber #[derive($m)] struct MyNum(i32); thay thế macro invocation bằng đoạn mã này, chèn metavariable $m vào vị trí thuộc tính derive. Macro Invocation:

demo!(Debug) truyền meta item Debug vào macro demo. Khi macro demo được gọi với meta item Debug, nó sẽ tạo ra một struct MyNum với derive attribute Debug.

Nếu bạn có thêm câu hỏi nào khác hoặc cần giải thích chi tiết hơn, hãy cho tôi biết!


Fragment Specifier: lifetime

macro_rules! demo {
    ($l:lifetime) => {
        let a: &$l str = "sample";
    };
}

demo!('static);

Notes: Trong Rust, lifetime là một fragment specifier được sử dụng trong macro để khớp với một lifetime specifier. Điều này có nghĩa là nó có thể khớp với các lifetime như 'a, 'static, v.v.

Dưới đây là một ví dụ sử dụng fragment specifier lifetime trong macro:

rust Copy code macro_rules! demo { ($l:lifetime) => { let a: &$l str = "sample"; }; }

fn main() { demo!('static); } Giải thích Macro Definition:

macro_rules! demo định nghĩa một macro tên là demo. Matcher ($l:lifetime) khớp với một lifetime specifier và gán nó vào metavariable $l. Transcriber let a: &$l str = "sample"; thay thế macro invocation bằng đoạn mã này, chèn metavariable $l vào vị trí lifetime. Macro Invocation:

demo!('static) truyền lifetime specifier 'static vào macro demo. Khi macro demo được gọi với lifetime specifier 'static, nó sẽ tạo ra biến a có kiểu &'static str.

Nếu bạn có thêm câu hỏi nào khác hoặc cần giải thích chi tiết hơn, hãy cho tôi biết!


Fragment Specifier: vis

macro_rules! demo {
    ($v:vis) => {
        $v fn sample() {}
    };
}

demo!(pub);

Notes: Trong Rust, vis là một fragment specifier được sử dụng trong macro để khớp với một visibility specifier. Điều này có nghĩa là nó có thể khớp với các từ khóa như pub, crate, hoặc để trống nếu không có visibility specifier.

Dưới đây là một ví dụ sử dụng fragment specifier vis trong macro:

rust Copy code macro_rules! demo { ($v:vis) => { $v fn sample() {} }; }

fn main() { demo!(pub); } Giải thích Macro Definition:

macro_rules! demo định nghĩa một macro tên là demo. Matcher ($v:vis) khớp với một visibility specifier và gán nó vào metavariable $v. Transcriber $v fn sample() {} thay thế macro invocation bằng đoạn mã này, chèn metavariable $v vào vị trí visibility. Macro Invocation:

demo!(pub); truyền visibility specifier pub vào macro demo. Khi macro demo được gọi với visibility specifier pub, nó sẽ tạo ra một hàm sample với visibility pub.

Nếu bạn có thêm câu hỏi nào khác hoặc cần giải thích chi tiết hơn, hãy cho tôi biết!


Fragment Specifier: literal

macro_rules! demo {
    ($l:literal) => { $l };
}

let five = demo!(5);
let hi = demo!("hello");

Notes: Trong Rust, literal là một fragment specifier được sử dụng trong macro để khớp với các giá trị literal như số, chuỗi ký tự, hoặc ký tự đơn.

Dưới đây là một ví dụ sử dụng fragment specifier literal trong macro:

rust Copy code macro_rules! demo { ($l:literal) => { $l }; }

fn main() { let five = demo!(5); let hi = demo!("hello");

println!("five = {}", five);
println!("hi = {}", hi);

} Giải thích Macro Definition:

macro_rules! demo định nghĩa một macro tên là demo. Matcher ($l:literal) khớp với một giá trị literal và gán nó vào metavariable $l. Transcriber $l thay thế macro invocation bằng giá trị của metavariable $l. Macro Invocation:

demo!(5); truyền giá trị literal 5 vào macro demo. demo!("hello"); truyền giá trị literal "hello" vào macro demo. Khi macro demo được gọi với giá trị literal, nó sẽ trả về chính giá trị đó.

Nếu bạn có thêm câu hỏi nào khác hoặc cần giải thích chi tiết hơn, hãy cho tôi biết!


Allowed Syntax

  • Some specifiers have restrictions on what can follow

    • Prevent ambiguities between custom syntax and Rust syntax
  • Specifiers with restrictions:

    • expr, stmt, pat, path, ty, vis
    • Compiler error will indicate what is allowed

https://doc.rust-lang.org/reference/macros-by-example.html

Notes: Một số specifier trong Rust có các hạn chế về những gì có thể theo sau chúng để tránh sự mơ hồ giữa cú pháp tùy chỉnh và cú pháp của Rust.

Các specifier có hạn chế: expr (expression) stmt (statement) pat (pattern) path (path) ty (type) vis (visibility) Nếu có lỗi về những gì được phép theo sau một specifier, Rust compiler sẽ báo lỗi chỉ ra rõ ràng.

Tham khảo thêm: Bạn có thể xem thêm chi tiết về macros trong Rust tại tài liệu chính thức: https://doc.rust-lang.org/reference/macros-by-example.html

Nếu bạn cần thêm thông tin hoặc có câu hỏi cụ thể nào khác, hãy cho tôi biết!


Imports

  • When using external crates in a macro, use the full path prefixed with two colons (::)

    • use ::std::collections::HashMap;
  • When using modules from the current crate, use $crate:

    • $crate::module1::func();
  • This helps resolve import issues since macros can be invoked from any location

Notes: Khi sử dụng các crates bên ngoài trong một macro, hãy sử dụng đường dẫn đầy đủ kèm theo hai dấu hai chấm (::).

Sử dụng crates bên ngoài:

rust Copy code use ::std::collections::HashMap; Sử dụng các modules từ crate hiện tại: rust $crate::module1::func(); Điều này giúp giải quyết các vấn đề về import vì các macros có thể được gọi từ bất kỳ vị trí nào.


Declarative Macros

  • A form of metaprogramming (code that writes code)
  • Hygienic:
    • Unable to emit invalid code
    • Data cannot “leak” in to (or out of) a macro
      • Macros cannot capture information like closures
      • All names / bindings / variables must be provided by the caller
  • Uses macro-specific pattern matching to emit code
  • Invoked using an exclamation point: macro_name!()

Notes: Định nghĩa: Một hình thức lập trình metaprogramming (mã viết mã).

Hygienic:

Không thể tạo ra mã không hợp lệ. Dữ liệu không thể "rò rỉ" vào hoặc ra khỏi một macro. Các macro không thể nắm bắt thông tin như closures. Tất cả các tên / ràng buộc / biến phải được cung cấp bởi người gọi. Cơ chế hoạt động: Sử dụng pattern matching đặc biệt cho macro để tạo mã.

Gọi macro: Sử dụng dấu chấm than: macro_name!()


Invoking a Macro

your_macro_name!();
your_macro_name![];
your_macro_name!{}

Notes: Trong Rust, bạn có thể gọi macro bằng cách sử dụng một trong ba cú pháp sau:

your_macro_name!(); your_macro_name![]; your_macro_name!{} Ba cú pháp này đều cho phép bạn gọi cùng một macro nhưng với các dấu phân cách khác nhau.


Valid Positions

  • Macros can only be used in specific parts of Rust code:
    • Expressions & Statements
    • Patterns
    • Types
    • Items & Associated Items
    • macro_rules transcribers
    • External blocks

Notes: Macros trong Rust có thể được sử dụng trong các phần cụ thể của mã như sau:

Expressions & Statements (Biểu thức & Câu lệnh) Patterns (Mẫu) Types (Kiểu) Items & Associated Items (Mục & Mục liên kết) macro_rules transcribers (Người phiên mã macro_rules) External blocks (Khối bên ngoài)


Expression & Statement Position

// Expressions
let nums = vec![1, 2, 3];

match vec![1, 2, 3].as_slice() {
    _ => format!("hello"),
}

// Statements
println!("Hello!");
dbg!(9_i64.pow(2));

Notes: Expressions rust Copy code let nums = vec![1, 2, 3];

match vec![1, 2, 3].as_slice() { _ => format!("hello"), } Statements rust Copy code println!("Hello!"); dbg!(9_i64.pow(2)); Macros trong Rust có thể được sử dụng ở vị trí của các biểu thức và câu lệnh như ví dụ trên. Các macro như vec! và format! được sử dụng trong biểu thức và câu lệnh như println! và dbg! cho phép in ra các giá trị và gỡ lỗi trong Rust.


Pattern Position

macro_rules! pat {
    ($i:ident) => (Some($i))
}

// Patterns
if let pat!(x) = Some(1) {
    assert_eq!(x, 1);
}

match Some(1) {
    pat!(x) => (),
    _ => (),
}

Notes: Macros có thể được sử dụng trong các mẫu (pattern) như trong các ví dụ trên. Macro pat! được định nghĩa để tạo ra mẫu Some($i), và có thể được sử dụng trong các biểu thức if let và match.


Type Position

macro_rules! Tuple {
    ($A:ty, $B:ty) => { ($A, $B) };
}

// Types
type N2 = Tuple!(i32, i32);

let nums: Tuple!(i32, char) = (1, 'a');

Notes: Macros cũng có thể được sử dụng trong các định nghĩa kiểu (type) như trong ví dụ trên. Macro Tuple! được định nghĩa để tạo ra kiểu tuple (A, B) và có thể được sử dụng trong các định nghĩa kiểu và khai báo biến.


Item Position

macro_rules! constant {
    ($name:ident) => { const $name: &'static str = "Jayson"; }
}

macro_rules! newtype {
    ($name:ident, $typ:ty) => { struct $name($typ); }
}

// Items
constant!(NAME);
assert_eq!(NAME, "Jayson");

newtype!(DemoStruct, usize);
let demo = DemoStruct(5);

Notes: Macros trong Rust có thể được sử dụng để định nghĩa các items như constants và structs. Macro constant! trong ví dụ trên định nghĩa một hằng số, trong khi macro newtype! tạo một kiểu struct mới.


Associated Item Position

macro_rules! msg {
    ($msg:literal) => {
        pub fn msg() {
            println!("{}", $msg);
        }
    };
}

struct Demo;

// Associated item
impl Demo {
    msg!("demo struct");
}

Notes: Trong ví dụ này, macro msg! định nghĩa một phương thức msg hiển thị một thông điệp đã cho. Phương thức này sau đó được thêm vào impl của Demo, làm cho nó trở thành một associated item của Demo.


macro_rules Transcribers

// macro_rules transcribers
macro_rules! demo {
    () => {
        println!("{}", format!("demo{}", '!'));
    };
}

demo!();

Notes: Trong ví dụ này, macro demo! không nhận tham số và sẽ in ra chuỗi "demo!". Transcriber của macro là phần bên phải của => trong định nghĩa macro, nơi mà mã Rust thực sự được sinh ra.