主頁 > 後端開發 > Rust編程語言入門之高級特性

Rust編程語言入門之高級特性

2023-04-25 08:04:32 後端開發

高級特性

主要內容

  • 不安全 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) 之后

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

標籤:其他

上一篇:Go中的有限狀態機FSM的詳細介紹

下一篇:返回列表

標籤雲
其他(157994) Python(38099) JavaScript(25390) Java(17999) C(15217) 區塊鏈(8259) C#(7972) AI(7469) 爪哇(7425) MySQL(7140) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5328) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4559) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2430) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1960) Web開發(1951) HtmlCss(1923) python-3.x(1918) 弹簧靴(1913) C++(1911) xml(1889) PostgreSQL(1873) .NETCore(1855) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust編程語言入門之高級特性

    高級特性 主要內容 不安全 Rust 高級 Trait 高級 型別 高級函式和閉包 宏 一、不安全 Rust 匹配命名變數 隱藏著第二個語言,它沒有強制記憶體安全保證:Unsafe Rust(不安全的 Rust) 和普通的 Rust 一樣,但提供了額外的“超能力” Unsafe Rust 存在的原因: ......

    uj5u.com 2023-04-25 08:04:32 more
  • Go中的有限狀態機FSM的詳細介紹

    1、FSM簡介 1.1 有限狀態機的定義 有限狀態機(Finite State Machine,FSM)是一種數學模型,用于描述系統在不同狀態下的行為和轉移條件。 狀態機有三個組成部分:狀態(State)、事件(Event)、動作(Action),事件(轉移條件)觸發狀態的轉移和動作的執行。動作的執 ......

    uj5u.com 2023-04-25 08:04:21 more
  • docker-compose一鍵部署java開源專案

    這一年干的很多事都是為了降低我的開源專案訊息推送平臺austin使用門檻。 如果想學Java專案的,強烈推薦我的開源專案訊息推送平臺Austin(8K stars) ,可以用作畢業設計,可以用作校招,可以看看生產環境是怎么推送訊息的。開源專案訊息推送平臺austin倉庫地址: 訊息推送平臺🔥推送下 ......

    uj5u.com 2023-04-25 08:04:13 more
  • iOS開發 - Swift Codable協議實戰:快速、簡單、高效地完成JSON和M

    Codable 是 Swift 4.0 引入的一種協議,它是一個組合協議,由 Decodable 和 Encodable 兩個協議組成。它的作用是將模型物件轉換為 JSON 或者是其它的資料格式,也可以反過來將 JSON 資料轉換為模型物件。 ......

    uj5u.com 2023-04-25 08:04:04 more
  • python中的全域變數與區域變數

    1,區域變數與全域變數 1,定義 區域變數:就是在函式體內的變數,在python中冒號“:”后面的變數都是區域變數,當然區域與全域也是一個相對的概念。比如出現函式嵌套的情況。 全域變數:就是在模塊中所有函式都可以呼叫的變數,一般在函式體外被定義。 2,使用程序 函式內的區域變數,在函式體外是不可以使 ......

    uj5u.com 2023-04-25 08:03:58 more
  • 【SSM】一、了解Sping 框架

    〇、Maven 0.1 什么是Maven? Apache Maven is a software project management and comprehension tool. Based on the concept of a project object model (POM), Mave ......

    uj5u.com 2023-04-25 08:03:54 more
  • 面試最常問的陣列轉樹,樹轉陣列 c++ web框架paozhu實作

    剛畢業同學,找作業常被問 二維陣列轉樹,樹轉二維陣列 需要支持無限層級實作,如果你了解這個語言那么實作起來還要一番思考 c++ web框架 paozhu使用 需要實作資料庫表資料到前臺選單實作,就是這種功能 二維陣列轉樹,樹轉二維陣列 保存時候樹二維陣列,展示時候樹樹狀。 這個技術難點在于無限遞回, ......

    uj5u.com 2023-04-25 08:03:49 more
  • C語言實驗報告范例

    實驗報告四 一, 實驗型別:設計型 二, 實驗室: 三, 指導老師: 四, 日期: 五, 實驗名稱:if分支陳述句的嵌套 六, 實驗目的: 1, 學習if嵌套結構,能夠用C語言編程解決日常生活的實體 2, 明確if陳述句在實作分支結構控制陳述句方面的特點和優勢 3, 熟練掌握關系運算子、關系運算式、邏輯運 ......

    uj5u.com 2023-04-25 08:03:45 more
  • Perl教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Perl入門教程 - 從基本到高級概念的簡單簡單步驟了解Perl,包括簡介,環境,語法,資料型別,變數,標量,陣列,哈希,IF ... ELSE,回圈,運算子,日期和時間,子程式,參考,格式,檔案I / O,目錄,錯誤處理,特殊變數,編碼標準,正則運算式,發送電子郵件,套接字編程,面向物件 ......

    uj5u.com 2023-04-25 08:03:39 more
  • Go語言入門11(泛型)

    泛型 問題解決 一個計算sum的函式 func sum(slice []int) int { var res int for _, value := range slice { res += value } return res } ? 如果需要提供對int,float64,string三種資料型別 ......

    uj5u.com 2023-04-25 08:03:35 more