高級特性
主要內容
- 不安全 Rust
- 高級 Trait
- 高級 型別
- 高級函式和閉包
- 宏
一、不安全 Rust
匹配命名變數
- 隱藏著第二個語言,它沒有強制記憶體安全保證:Unsafe Rust(不安全的 Rust)
- 和普通的 Rust 一樣,但提供了額外的“超能力”
- Unsafe Rust 存在的原因:
- 靜態分析是保守的,
- 使用 Unsafe Rust:我知道自己在做什么,并承擔相應風險
- 計算機硬體本身就是不安全的,Rust需要能夠進行底層系統編程
- 靜態分析是保守的,
Unsafe 超能力
- 使用 unsafe 關鍵字來切換到 unsafe Rust,開啟一個塊,里面放著 Unsafe 代碼
- Unsafe Rust 里可執行的四個動作(unsafe 超能力):
- 解參考原始指標
- 呼叫 unsafe 函式或方法
- 方法或修改可變的靜態變數
- 實作 unsafe trait
- 注意:
- Unsafe 并沒有關閉借用檢查或停用其它安全檢查
- 任何記憶體安全相關的錯誤必須留在 unsafe 塊里
- 盡可能隔離 Unsafe 代碼,最好將其封裝在安全的抽象里,提供安全的API
解參考原始指標
- 原始指標
- 可變的:*mut T
- 不可變的:*const T,意味著指標在解參考后不能直接對其進行賦值
- 注意:這里的 * 不是解參考符號,它是型別名的一部分,
- 與參考不同,原始指標:
- 允許通過同時具有不可變和可變指標或多個執行同一位置的可變指標來忽略借用規則
- 無法保證能指向合理的記憶體
- 允許為null
- 不實作任何自動清理
- 放棄保證的安全,換取更好的性能/與其它語言或硬體介面的能力
解參考原始指標
fn main() {
let mut num = 5;
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;
unsafe {
println!("r1: {}", *r1);
println!("r2: {}", *r2);
}
let address = 0x012345usize;
let r = address as *const i32;
unsafe {
println!("r: {}", *r); // 報錯 非法訪問
}
}
- 為什么要用原始指標?
- 與 C 語言進行介面
- 構建借用檢查器無法理解的安全抽象
呼叫 unsafe 函式或方法
- unsafe 函式或方法:在定義前加上了 unsafe 關鍵字
- 呼叫前需手動滿足一些條件(主要靠看檔案),因為Rust無法對這些條件進行驗證
- 需要在 unsafe 塊里進行呼叫
unsafe fn dangerous() {}
fn main() {
unsafe {
dangerous();
}
}
創建 Unsafe 代碼的安全抽象
- 函式包含 unsafe 代碼并不意味著需要將整個函式標記為 unsafe
- 將 unsafe 代碼包裹在安全函式中是一個常見的抽象
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
let len = slice.len();
assert!(mid <= len);
(&mut slice[..mid], &mut slice[mid..]) // 報錯 cannot borrow `*slice` as mutable more than once at a time
}
fn main() {
let mut v = vec![1, 2, 3, 4, 5, 6];
let r = &mut v[..];
let (a, b) = r.split_at_mut(3);
assert_eq!(a, &mut [1, 2, 3]);
assert_eq!(b, &mut [4, 5, 6]);
}
修改之后:
use std::slice;
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
let len = slice.len();
let ptr = slice.as_mut_ptr()
assert!(mid <= len);
unsafe {
(
slice::from_raw_parts_mut(ptr, mid),
slice::from_raw_parts_mut(ptr.add(mid), len = mid),
)
}
}
fn main() {
let address = 0x012345usize;
let r = address as *mut i32;
let slice: &[i32] = unsafe {
slice::from_raw_parts_mut(r, 10000)
};
}
使用 extern 函式呼叫外部代碼
- extern 關鍵字:簡化創建和使用外部函式介面(FFI)的程序,
- 外部函式介面(FFI,Foreign Function Interface):它允許一種編程語言定義函式,并讓其它編程語言能呼叫這些函式
extern "C" {
fn abs(input: i32) -> i32;
}
fn main() {
unsafe {
println!("Absolute value of -3 according to C: {}", abs(-3));
}
}
- 應用二進制介面(ABI,Application Binary Interface):定義函式在匯編層的呼叫方式
- “C” ABI 是最常見的ABI,它遵循 C 語言的ABI
從其它語言呼叫 Rust 函式
- 可以使用 extern 創建介面,其它語言通過它們可以呼叫 Rust 的函式
- 在 fn 前添加 extern 關鍵字,并指定 ABI
- 還需添加
#[no_mangle]
注解:避免 Rust 在編譯時改變它的名稱
#[no_mangle]
pub extern "C" fn call_from_c() {
println!("Just called a Rust function from C!");
}
fn main() {}
訪問或修改一個可變靜態變數
- Rust 支持全域變數,但因為所有權機制可能產生某些問題,例如資料競爭
- 在 Rust 里,全域變數叫做靜態(static)變數
static HELLO_WORLD: &str = "Hello, world!";
fn main() {
println!("name is: {}", HELLO_WORLD);
}
靜態變數
- 靜態變數與常量類似
- 命名:SCREAMING_SNAKE_CASE
- 必須標注型別
- 靜態變數只能存盤 'static 生命周期的參考,無需顯示標注
- 訪問不可變靜態變數是安全的
常量和不可變靜態變數的區別
- 靜態變數:有固定的記憶體地址,使用它的值總會訪問同樣的資料
- 常量:允許使用它們的時候對資料進行復制
- 靜態變數:可以是可變的,訪問和修改靜態可變變數是不安全(unsafe)的
static mut COUNTER: u32 = 0;
fn add_to_count(inc: u32) {
unsafe {
COUNTER += inc;
}
}
fn main() {
add_to_count(3);
unsafe {
println!("COUNTER: {}", COUNTER);
}
}
實作不安全(unsafe)trait
- 當某個 trait 中存在至少一個方法擁有編譯器無法校驗的不安全因素時,就稱這個 trait 是不安全的
- 宣告 unsafe trait:在定義前加 unsafe 關鍵字
- 該 trait 只能在 unsafe 代碼塊中實作
unsafe trait Foo {
// methods go here
}
unsafe impl Foo for i32 {
// method implementations go here
}
fn main() {}
何時使用 unsafe 代碼
- 編譯器無法保證記憶體安全,保證 unsafe 代碼正確并不簡單
- 有充足理由使用 unsafe 代碼時,就可以這樣做
- 通過顯示標記 unsafe,可以在出現問題時輕松的定位
二、高級 Trait
在 Trait 定義中使用關聯型別來指定占位型別
- 關聯型別(associated type)是 Trait中的型別占位符,它可以用于Trait的方法簽名中:
- 可以定義出包含某些型別的 Trait,而在實作前無需知道這些型別是什么
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
fn main() {
println!("Hello, world!");
}
關聯型別與泛型的區別
泛型 | 關聯型別 |
---|---|
每次實作 Trait 時標注型別 | 無需標注型別 |
可以為一個型別多次實作某個 Trait(不同的泛型引數) | 無法為單個型別多次實作某個 Trait |
例子:
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
pub trait Iterator2<T> {
fn next(&mut self) -> Option<T>;
}
struct Counter {}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
None
}
}
impl Iterator2<String> for Counter {
fn next(&mut self) -> Option<String> {
None
}
}
impl Iterator2<u32> for Counter {
fn next(&mut self) -> Option<u32> {
None
}
}
fn main() {
println!("Hello, world!");
}
默認泛型引數和運算子多載
- 可以在使用泛型引數時為泛型指定一個默認的具體型別,
- 語法:
<PlaceholderType=ConcreteType>
- 這種技術常用于運算子多載(operator overloading)
- Rust 不允許創建自己的運算子及多載任意的運算子
- 但可以通過實作 std::ops 中列出的那些 trait 來多載一部分相應的運算子
例子一:
use std::ops::Add;
#[derive(Debug, PartialEq)]
struct Point {
x: i32,
y: i32,
}
impl Add for Point {
type Output = Point;
fn add(self, other: Point) -> Point {
Point {
x: self.x + other.x,
y: self.y + other.y,
}
}
}
fn main() {
assert_eq!(Point {x: 1, y: 0} + Point {x: 2, y: 3},
Point {x: 3, y: 3}
);
}
例子二:
use std::ops::Add;
struct Millimeters(u32);
struct Meters(u32);
impl Add<Meters> for Millimeters {
type Output = Millimeters;
fn add(self, other: Meters) -> Millimeters {
Millimeters(self.0 + (other.0 * 1000))
}
}
fn main() {
}
默認泛型引數的主要應用場景
- 擴展一個型別而不破壞現有代碼
- 允許在大部分用戶都不需要的特定場景下進行自定義
完全限定語法(Fully Qualified Syntax)如何呼叫同名方法
例子一:
trait Pilot {
fn fly(&self);
}
trait Wizard {
fn fly(&self);
}
struct Human;
impl Pilot for Human {
fn fly(&self) {
println!("This is your captain speaking.");
}
}
impl Wizard for Human {
fn fly(&self) {
println!("Up!");
}
}
impl Human {
fn fly(&self) {
println!("*waving arms furiously*");
}
}
fn main() {
let persion = Human;
person.fly(); // Human 本身的 fly 方法
Pilot::fly(&person);
Wizard::fly(&person);
}
例子二:
trait Animal {
fn baby_name() -> String;
}
struct Dog;
impl Dog {
fn baby_name() -> String {
String::from("Spot")
}
}
impl Animal for Dog {
fn baby_name() -> String {
String::from("puppy")
}
}
fn main() {
println!("A baby dog is called a {}", Dog::baby_name()); // Dog 本身的關聯方法
}
完全限定語法(Fully Qualified Syntax)如何呼叫同名方法
- 完全限定語法:
<Type as Trait>::function(receiver_if_method, netx_arg, ...);
- 可以在任何呼叫函式或方法的地方使用
- 允許忽略那些從其它背景關系能推匯出來的部分
- 當 Rust 無法區分你期望呼叫哪個具體實作的時候,才需使用這種語法
trait Animal {
fn baby_name() -> String;
}
struct Dog;
impl Dog {
fn baby_name() -> String {
String::from("Spot")
}
}
impl Animal for Dog {
fn baby_name() -> String {
String::from("puppy")
}
}
fn main() {
println!("A baby dog is called a {}", Dog::baby_name()); // Dog 本身的關聯方法
println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
}
使用 supertrait 來要求 trait 附帶其它 trait 的功能
- 需要在一個 trait 中使用其它 trait 的功能:
- 需要被依賴的 trait 也被實作
- 那個被間接依賴的 trait 就是當前 trait 的 supertrait
use std::fmt;
trait OutlinePrint: fmt::Display {
fn outline_print(&self) {
let output = self.to_string();
let len = output.len();
println!("{}", "*".repeat(len + 4));
println!("*{}*", " ".repeat(len + 2));
println!("* {} *", output);
println!("*{}*", " ".repeat(len + 2));
println!("{}", "*".repeat(len + 4));
}
}
struct Point {
x: i32,
y: i32,
}
impl OutlinePrint for Point {}
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
fn main() {}
使用 newtype 模式在外部型別上實作外部 trait
- 孤兒規則:只有當 trait 或型別定義在本地包時,才能為該型別實作這個 trait
- 可以通過 newtype 模式來繞過這一規則
- 利用 tuple struct (元組結構體)創建一個新的型別
use std::fmt;
struct Wrapper(Vec<String>);
impl fmt::Display for Wrapper {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[{}]", self.0.join(", "))
}
}
fn main() {
let w = Wrapper(vec![String::from("hello"), String::from("world")]);
println!("w = {}", w);
}
三、高級型別
使用 newtype 模式實作型別安全和抽象
- newtype 模式可以:
- 用來靜態的保證各種值之間不會混淆并表明值的單位
- 為型別的某些細節提供抽象能力
- 通過輕量級的封裝來隱藏內部實作細節
使用型別別名創建型別同義詞
- Rust 提供了型別別名的功能:
- 為現有型別生產另外的名稱(同義詞)
- 并不是一個獨立的型別
- 使用 type 關鍵字
- 主要用途:減少代碼字符重復
例子一:
type Kilometers = i32;
fn main() {
let x: i32 = 5;
let y: Killometers = 5;
println!("x + y = {}", x + y);
}
例子二:
fn takes_long_type(f: Box<dyn Fn() + Send + 'static>) {
// --snip--
}
fn returns_long_type() -> Box<dyn Fn() + Send + 'static> {
Box::new(|| println!("hi"))
}
fn main() {
let f: Box<dyn Fn() + Send + 'static> = Box::new(|| println!("hi"));
}
修改之后:
type Thunk = Box<dyn Fn() + Send + 'static>;
fn takes_long_type(f: Thunk) {
// --snip--
}
fn returns_long_type() -> Thunk {
Box::new(|| println!("hi"))
}
fn main() {
let f: Thunk = Box::new(|| println!("hi"));
}
例子三:
use std::io::Error;
use std::fmt;
pub trait Write {
fn write(&mut self, buf: &[u8]) -> Result<usize, Error>;
fn flush(&mut self) -> Result<(), Error>;
fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>;
fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Error>;
}
fn main() {
}
修改之后:
use std::fmt;
// type Result<T> = Result<T, std::io::Error>; // 宣告在 std::io 中
type Result<T> = std::io::Result<T>;
pub trait Write {
fn write(&mut self, buf: &[u8]) -> Result<usize>;
fn flush(&mut self) -> Result<()>;
fn write_all(&mut self, buf: &[u8]) -> Result<()>;
fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<()>;
}
fn main() {
}
Never 型別
- 有一個名為 ! 的特殊型別:
- 它沒有任何值,行話稱為空型別(empty type)
- 我們傾向于叫它 never 型別,因為它在不回傳的函式中充當回傳型別
- 不回傳值的函式也被稱作發散函式(diverging function)
例子一:
fn bar() -> ! { // 報錯 回傳單元型別 不匹配
}
fn main() {}
例子二:
fn main() {
let guess = "";
loop {
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue, // ! never 型別
};
}
}
注意:never 型別的運算式可以被強制的轉化為任意其它型別
例子三:
impl<T> Option<T> {
pub fn unwrap(self) -> T {
match self {
Some(val) => val,
None => panic!("called `Option::unwrap()` on a `None` value"), // !
}
}
}
例子四:
fn main() {
println!("forever");
loop {
println!("and ever");
}
}
動態大小和 Sized Trait
- Rust 需要在編譯時確定為一個特定型別的值分配多少空間,
- 動態大小的型別(Dynamically Sized Types,DST)的概念:
- 撰寫代碼時使用只有在運行時才能確定大小的值
- str 是動態大小的型別(注意不是 &str):只有運行時才能確定字串的長度
- 下列代碼無法正常作業:
let s1: str = "Hello there!";
let s2: str = "How's it going?";
- 使用 &str 來解決:
- str 的地址
- str 的長度
- 下列代碼無法正常作業:
Rust使用動態大小型別的通用方式
- 附帶一些額外的元資料來存盤動態資訊的大小
- 使用動態大小型別時總會把它的值放在某種指標后邊
另外一種動態大小的型別:trait
- 每個 trait 都是一個動態大小的型別,可以通過名稱對其進行參考
- 為了將 trait 用作 trait 物件,必須將它放置在某種指標之后
- 例如 &dyn Trait 或 Box
(Rc ) 之后
- 例如 &dyn Trait 或 Box
Sized trait
- 為了處理動態大小的型別,Rust 提供了一個 Sized trait 來確定一個型別的大小在編譯時是否已知
- 編譯時可計算出大小的型別會自動實作這一 trait
- Rust 還會為每一個泛型函式隱式的添加 Sized 約束
fn generic<T>(t: T) {}
fn generic<T: Sized>(t: T) {} // 上面的generic 會隱式的轉化為這種
fn main() {}
- 默認情況下,泛型函式只能被用于編譯時已經知道大小的型別,可以通過特殊語法解除這一限制
?Sized trait 約束
fn generic<T>(t: T) {}
fn generic<T: Sized>(t: T) {}
fn generic<T: ?Sized>(t: &T) {} // ? 只能用在 sized上
fn main() {}
- T 可能是也可能不是 Sized
- 這個語法只能用在 Sized 上面,不能被用于其它 trait
四、高級函式和閉包
函式指標
- 可以將函式傳遞給其它函式
- 函式在傳遞程序中會被強制轉換成 fn 型別
- fn 型別就是 “函式指標(function pointer)”
fn add_one(x: i32) -> i32 {
x + 1
}
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
f(arg) + f(arg)
}
fn main() {
let answer = do_twice(add_one, 5);
println!("The answer is: {}", answer);
}
函式指標與閉包的不同
- fn 是一個型別,不是一個 trait
- 可以直接指定 fn 為引數型別,不用宣告一個以 Fn trait 為約束的泛型引數
- 函式指標實作了全部3種閉包 trait(Fn、FnMut、FnOnce):
- 總是可以把函式指標用作引數傳遞給一個接收閉包的函式
- 所以,傾向于搭配閉包 trait 的泛型來撰寫函式:可以同時接收閉包和普通函式
- 某些情景,只想接收 fn 而不接收閉包:
- 與外部不支持閉包的代碼互動:C 函式
例子一
fn main() {
let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec<String> = list_of_numbers
.iter().map(|i| i.to_string()).collect();
let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec<String> = list_of_numbers
.iter().map(ToString::to_string).collect();
}
例子二
fn main() {
enum Status {
Value(u32),
Stop,
}
let v = Status::Value(3);
let list_of_statuses: Vec<Status> = (0u32..20).map(Status::Value).collect();
}
回傳閉包
- 閉包使用 trait 進行表達,無法在函式中直接回傳一個閉包,可以將一個實作了該 trait 的具體型別作為回傳值,
fn returns_closure() -> Fn(i32) -> i32 { // 報錯 沒有一個已知的大小
|x| x + 1
}
fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
Box::new(|x| x + 1)
}
fn main() {
}
五、宏
宏 macro
- 宏在Rust里指的是一組相關特性的集合稱謂:
- 使用 macro_rules! 構建的宣告宏(declarative macro)
- 3 種程序宏
- 自定義 #[derive] 宏,用于 struct 或 enum,可以為其指定隨 derive 屬性添加的代碼
- 類似屬性的宏,在任何條目上添加自定義屬性
- 類似函式的宏,看起來像函式呼叫,對其指定為引數的 token 進行操作
函式與宏的差別
- 本質上,宏是用來撰寫可以生成其它代碼的代碼(元編程,metaprogramming)
- 函式在定義簽名時,必須宣告引數的個數和型別,宏可處理可變的引數
- 編譯器會在解釋代碼前展開宏
- 宏的定義比函式復雜得多,難以閱讀、理解、維護
- 在某個檔案呼叫宏時,必須提前定義宏或將宏引入當前作用域:
- 函式可以在任何位置定義并在任何位置使用
macro_rules! 宣告宏(棄用)
- Rust 中最常見的宏形式:宣告宏
- 類似 match 的模式匹配
- 需要使用 marco_rules!
// let v: Vec<u32> = vec![1, 2, 3];
#[macro_export]
macro_rules! vec {
($($x:expr),*) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x);
)*
temp_vec
}
};
}
// let mut temp_vec = Vec::new();
// temp_vec.push(1);
// temp_vec.push(2);
// temp_vec.push(3);
// temp_vec
基于屬性來生成代碼的程序宏
- 這種形式更像函式(某種形式的程序)一些
- 接收并操作輸入的 Rust 代碼
- 生成另外一些 Rust 代碼作為結果
- 三種程序宏:
- 自定義派生
- 屬性宏
- 函式宏
- 創建程序宏時:
- 宏定義必須單獨放在它們自己的包中,并使用特殊的包型別
use proc_macro;
#[some_attribute]
pub fn some_name(input: TokenStream) -> TokenStream {
}
自定義 derive 宏
- 需求:
- 創建一個 hello_macro 包,定義一個擁有關聯函式 hello_macro 的 HelloMacro trait
- 我們提供一個能自動實作 trait 的程序宏
- 在它們的型別上標注 #[derive(HelloMacro)],進而得到 hello_macro 的默認實作
? cd rust
~/rust
? cargo new hello_macro --lib
Created library `hello_macro` package
~/rust
? cd hello_macro
hello_macro on master [?] via ?? 1.67.1
? c
hello_macro on master [?] via ?? 1.67.1
?
hello_macro on master [?] is ?? 0.1.0 via ?? 1.67.1
? cd ..
~/rust
? cargo new hello_macro_derive --lib
Created library `hello_macro_derive` package
~/rust
? cd hello_macro_derive
hello_macro_derive on master [?] via ?? 1.67.1
? c
hello_macro_derive on master [?] via ?? 1.67.1
?
hello_macro_derive 代碼:
Cargo.toml
[package]
name = "hello_macro_derive"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
proc-macro = true
[dependencies]
syn = "2.0.13"
quote = "1.0.26"
src/lib.rs
extern crate proc_macro;
use crate::proc_macro::TokenStream;
use quote::quote;
use syn;
// #[derive(HelloMacro)]
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
// Construct a representation of Rust code as a syntax tree
// that we can manipulate
let ast = syn::parse(input).unwrap();
// Build the trait implementation
impl_hello_macro(&ast)
}
fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let gen = quote! {
impl HelloMacro for #name {
fn hello_macro() {
println!("Hello, Macro! My name is {}", stringify!(#name));
}
}
};
gen.into()
}
// DeriveInput {
// // --snip--
// ident: Ident {
// ident: "Pancakes",
// span: #0 bytes(95..103)
// },
// data: Struct(
// DataStruct {
// struct_token: Struct,
// fields: Unit,
// semi_token: Some(
// Semi
// )
// }
// )
// }
hello_macro 代碼:
main.rs
use hello_macro::HelloMacro;
struct Pancakes;
impl HelloMacro for Pancakes {
fn hello_macro() {
println!("Hello, Macro! My name is Pancakes!");
}
}
fn main() {
Pancakes::hello_macro();
}
// use hello_macro::HelloMacro;
// use hello_macro_derive::HelloMacro;
// #[derive(HelloMacro)]
// struct Pancakes;
// fn main() {
// Pancakes::hello_macro();
// }
lib.rs
pub trait HelloMacro {
fn hello_macro();
}
編譯
hello_macro_derive on master [?] is ?? 0.1.0 via ?? 1.67.1
? cargo build
Compiling unicode-ident v1.0.8
Compiling proc-macro2 v1.0.56
Compiling quote v1.0.26
Compiling syn v2.0.15
Compiling hello_macro_derive v0.1.0 (/Users/qiaopengjun/rust/hello_macro_derive)
Finished dev [unoptimized + debuginfo] target(s) in 1.54s
hello_macro_derive on master [?] is ?? 0.1.0 via ?? 1.67.1
?
hello_macro on master [?] is ?? 0.1.0 via ?? 1.67.1
? cargo build
Compiling hello_macro v0.1.0 (/Users/qiaopengjun/rust/hello_macro)
Finished dev [unoptimized + debuginfo] target(s) in 0.26s
hello_macro on master [?] is ?? 0.1.0 via ?? 1.67.1
?
創建
~/rust
? cargo new pancakes
Created binary (application) `pancakes` package
~/rust
? cd pancakes
pancakes on master [?] via ?? 1.67.1
? c
pancakes on master [?] via ?? 1.67.1
?
main.rs 檔案
use hello_macro::HelloMacro;
use hello_macro_derive::HelloMacro;
#[derive(HelloMacro)]
struct Pancakes;
fn main() {
Pancakes::hello_macro();
}
Cargo.toml 檔案
[package]
name = "pancakes"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
hello_macro = {path = "../hello_macro"}
hello_macro_derive = {path = "../hello_macro_derive"}
運行
pancakes on master [?] is ?? 0.1.0 via ?? 1.67.1
? cargo run
Compiling hello_macro v0.1.0 (/Users/qiaopengjun/rust/hello_macro)
Compiling pancakes v0.1.0 (/Users/qiaopengjun/rust/pancakes)
Finished dev [unoptimized + debuginfo] target(s) in 0.10s
Running `target/debug/pancakes`
Hello, Macro! My name is Pancakes
pancakes on master [?] is ?? 0.1.0 via ?? 1.67.1
?
類似屬性的宏
- 屬性宏與自定義 derive 宏類似
- 允許創建新的屬性
- 但不是為 derive 屬性生成代碼
- 屬性宏更加靈活:
- derive 只能用于 struct 和 enum
- 屬性宏可以用于任意條目,例如函式
// #[route(GET, "/")]
// fn index() {}
// #[proc_macro_attribute]
// pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {}
類似函式的宏
- 函式宏定義類似于函式呼叫的宏,但比普通函式更加靈活
- 函式宏可以接收 TokenStream 作為引數
- 與另外兩種程序宏一樣,在定義中使用 Rust 代碼來操作 TokenStream
// let sql = sql(SELECT * FROM posts WHERE id=1);
// #[proc_macro]
// pub fn sql(input: TokenStream) -> TokenStream {}
本文來自博客園,作者:QIAOPENGJUN,轉載請注明原文鏈接:https://www.cnblogs.com/QiaoPengjun/p/17351330.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/551059.html
標籤:其他
下一篇:返回列表