rust高级特征

文章目录

  • 不安全的rust
    • 解引用裸指针
      • 裸指针与引用和智能指针的区别
      • 裸指针使用解引用运算符 *,这需要一个 unsafe 块
      • 调用不安全函数或方法
      • 在不安全的代码之上构建一个安全的抽象层
    • 使用 extern 函数调用外部代码
      • rust调用C语言函数
      • rust接口被C语言程序调用
    • 访问或修改可变静态变量
      • 静态变量
      • 可变静态变量
    • 实现不安全 trait
    • 访问联合体中的字段
  • 高级trait
    • 关联类型在 trait 定义中指定占位符类型
      • 使用关联类型:针对一个类型的实现
      • 使用泛型:针对多个类型的实现
    • 默认泛型类型参数和运算符重载
      • 重载标准库Add操作符,实现Add特征可以让Point支持Add操作符
    • 调用同名方法
      • 如果是特征和结构体类型定义的都是关联函数,情况则不同
    • 超特征Super trait
    • 新类型newtype 模式用于在外部类型上实现外部 trait
      • 新类型可以提供轻量级的封装机制
      • 创建类型别名
      • 从不返回的 never type
        • continue
        • panic!
        • 有着 ! 类型的表达式是 loop
    • 动态类型DST
      • str
      • 特征是动态长度类型的:编译时借助Sized trait
  • 高级函数
    • 函数指针fn(x)->y
    • 三种闭包类型:Fn,FnMut,FnOnce
    • 元组结构体和元组结构体成员
    • 返回一个闭包
    • 声明(Declarative)宏
    • 过程(Procedural)宏
      • 如何编写自定义 derive 宏:只适用于结构体和枚举
      • 类属性宏:适用于结构体、枚举、函数
      • 类函数宏
  • 参考

不安全的rust

不安全的rust提供的能力如下:

  • 解引用裸指针
  • 调用不安全的函数或方法
  • 访问或修改可变静态变量
  • 实现不安全 trait
  • 访问 union 的字段

unsafe 并不会关闭借用检查器或禁用任何其他 Rust 安全检查:如果在不安全代码中使用引用,它仍会被检查。

unsafe 关键字只是提供了那五个不会被编译器检查内存安全的功能。你仍然能在不安全块中获得某种程度的安全。

解引用裸指针

裸指针与引用和智能指针的区别

  • 允许忽略借用规则,可以同时拥有不可变和可变的指针,或多个指向相同位置的可变指针
  • 不保证指向有效的内存
  • 允许为空
  • 不能实现任何自动清理功能

eg:如何从引用同时创建不可变和可变裸指针

#![allow(unused)]
fn main() {
    let mut num = 5;

    let r1 = &num as *const i32; //不可变裸指针
    let r2 = &mut num as *mut i32; //可变裸指针
}

裸指针使用解引用运算符 *,这需要一个 unsafe 块

#![allow(unused)]
fn main() {
    let mut num = 5;

    let r1 = &num as *const i32;
    let r2 = &mut num as *mut i32;

    unsafe {
        println!("r1 is: {}", *r1);
        println!("r2 is: {}", *r2);
    }
}

编译

▶ cargo run
    Blocking waiting for file lock on build directory
   Compiling bobo v0.1.0 (/home/wangji/code/rust/bobo)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 9.83s
     Running `target/debug/bobo`
r1 is: 5
r2 is: 5

调用不安全函数或方法

#![allow(unused)]
fn main() {
    unsafe fn dangerous() {}

    // 必须在一个单独的 unsafe 块中调用 dangerous 函数
    unsafe {
        dangerous();
    }
}

在不安全的代码之上构建一个安全的抽象层

问题代码

#![allow(unused)]
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]);
}

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..])
}

使用slice处理裸指针

#![allow(unused)]
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;
// 切片slice包括指向某个数据的指针和数据的长度
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
    let len = slice.len();
    let ptr = slice.as_mut_ptr(); // 返回指向slice中第一个元素的裸指针

    assert!(mid <= len);

    unsafe {
        (
            slice::from_raw_parts_mut(ptr, mid),
            slice::from_raw_parts_mut(ptr.add(mid), len - mid),
        )
    }
}

使用 extern 函数调用外部代码

extern,有助于创建和使用 外部函数接口(Foreign Function Interface, FFI)。外部函数接口是一个编程语言用以定义函数的方式,其允许不同(外部)编程语言调用这些函数。

如何集成 C 标准库中的 abs 函数。

rust调用C语言函数

extern "C" {
    fn abs(input: i32) -> i32;
}

fn main() {
    unsafe {
        println!("Absolute value of -3 according to C: {}", abs(-3));
    }
}

rust接口被C语言程序调用

还需增加 #[no_mangle] 标注来告诉 Rust 编译器不要 mangle 此函数的名称。Mangling 发生于当编译器将我们指定的函数名修改为不同的名称时,这会增加用于其他编译过程的额外信息,不过会使其名称更难以阅读。

每一个编程语言的编译器都会以稍微不同的方式 mangle 函数名,所以为了使 Rust 函数能在其他语言中指定,必须禁用 Rust 编译器的 name mangling。

#![allow(unused)]
fn main() {
    #[no_mangle]
    pub extern "C" fn call_from_c() {
        println!("Just called a Rust function from C!");
    }
}

访问或修改可变静态变量

静态变量

rust中全局变量就是静态变量

static HELLO_WORLD: &str = "Hello, world!";

fn main() {
    println!("name is: {}", HELLO_WORLD);
}

可变静态变量

访问不可变静态变量必须使用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);
    }
}

实现不安全 trait

#![allow(unused)]
fn main() {
    unsafe trait Foo {
        // methods go here
    }

    unsafe impl Foo for i32 {
        // method implementations go here
    }
}

访问联合体中的字段

高级trait

关联类型在 trait 定义中指定占位符类型

关联类型(associated types)是一个将类型占位符与 trait 相关联的方式,这样 trait 的方法签名中就可以使用这些占位符类型。trait 的实现者会针对特定的实现在这个类型的位置指定相应的具体类型。如此可以定义一个使用多种类型的 trait,直到实现此 trait 时都无需知道这些类型具体是什么。

使用关联类型:针对一个类型的实现

#![allow(unused)]
fn main() {}

pub trait Iterator {
    //一个带有关联类型的 trait 的例子是标准库提供的 Iterator trait。它有一个叫做 Item 的关联类型来替代遍历的值的类型。
    type Item;

    fn next(&mut self) -> Option<Self::Item>;
}
struct Counter {}
impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        // --snip--
        Some(0)
    }
}

// 报错!!
impl Iterator for Counter {
    type Item = u16;

    fn next(&mut self) -> Option<Self::Item> {
        // --snip--
        Some(0)
    }
}


使用泛型:针对多个类型的实现

使用泛型修改:可以支持多个类型

#![allow(unused)]
fn main() {}

pub trait Iterator<T> {
    fn next(&mut self) -> Option<T>;
}
struct Counter {}
impl Iterator<u32> for Counter {
    fn next(&mut self) -> Option<u32> {
        // --snip--
        Some(0)
    }
}

impl Iterator<u16> for Counter {
    fn next(&mut self) -> Option<u16> {
        // --snip--
        Some(0)
    }
}

默认泛型类型参数和运算符重载

重载标准库Add操作符,实现Add特征可以让Point支持Add操作符

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 });
}

rust标准库Add特征源码:

  • rhs:right hand side表示右手边,Self表示实现Add特征的类型

比较陌生的部分是尖括号中的 RHS=Self:这个语法叫做 默认类型参数(default type parameters)

RHS 是一个泛型类型参数(“right hand side” 的缩写),它用于定义 add 方法中的 rhs 参数。如果实现 Add trait 时不指定 RHS 的具体类型,RHS 的类型将是默认的 Self 类型,也就是在其上实现 Add 的类型。

在这里插入图片描述

注意如果想加的类型不同,那么泛型则需要指定

#![allow(unused)]
fn main() {}

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))
    }
}

默认参数类型主要用于如下两个方面:

  • 扩展类型而不破坏现有代码。
  • 在大部分用户都不需要的特定情况进行自定义。

调用同名方法

rust允许特征拥有同名的方法,可以让一个类型同时实现这两个特征,也可以让一个类型拥有和特征同名的方法

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 person = Human;
    //显式调用特征的方法
    Pilot::fly(&person);
    Wizard::fly(&person);
    person.fly();
}

编译

 cargo run
   Compiling blog v0.1.0 (/home/wangji/installer/rust/bobo/smartPtr)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 6.29s
     Running `target/debug/blog`
This is your captain speaking.
Up!
*waving arms furiously*

如果是特征和结构体类型定义的都是关联函数,情况则不同

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());
    //调用实现Animal trait的baby_name函数
    println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
}

编译

 cargo run
   Compiling blog v0.1.0 (/home/wangji/installer/rust/bobo/smartPtr)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.33s
     Running `target/debug/blog`
A baby dog is called a Spot
A baby dog is called a puppy

超特征Super trait

一个特征依赖另外一个特征,被依赖的特征就称之为超特征。

问题代码:

#![allow(unused)]
fn main() {}

trait OutlinePrint {
    fn outline_print(&self) {
        // 不知道是否实现了to_string,to_string特征是在dispaly特征中定义的
        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));
    }
}

解决办法继承超特征:fmt::Display特征

#![allow(unused)]
fn main() {}

use std::fmt;

trait OutlinePrint: fmt::Display {
    fn outline_print(&self) {
        // Display 特征中包含to_string()方法
        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));
    }
}

#![allow(unused)]
fn main() {}

use std::fmt;

// 有时我们可能会需要某个 trait 使用另一个 trait 的功能
// 在这种情况下,需要能够依赖相关的 trait 也被实现。这个所需的 trait 是我们实现的 trait 的 父(超) trait(supertrait)
trait OutlinePrint: fmt::Display {
    fn outline_print(&self) {
        // Display 特征中包含to_string()方法
        //因为指定了 OutlinePrint 需要 Display trait,则可以在 outline_print 中使用 to_string, 其会为任何实现 Display 的类型自动实现
        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,
}

//如果要在实现 OutlinePrint 特征的 struct 中调用 outline_print,则必须实现 fmt::Display trait
impl OutlinePrint for Point {}

impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

新类型newtype 模式用于在外部类型上实现外部 trait

孤儿规则(orphan rule),它说明只要 trait 或类型,两者至少有一个在你的crate中定义的时候呢,你才能让你的类型实现你的特征。

如果特征和类型都不是你的crate中定义的,而你仍然想要你的类型实现你的特征,那就需要新类型模式

例如,如果想要在 Vec<T> 上实现 Display,而孤儿规则阻止我们直接这么做,因为 Display trait 和 Vec<T> 都定义于我们的 crate 之外。

可以创建一个包含 Vec<T> 实例的 Wrapper 结构体,接着可以如示例 19-31 那样在 Wrapper 上实现 Display 并使用 Vec<T> 的值:

use std::fmt;

// 如果想在Vec上实现Dispaly特征,但是Vec和Display特征都不是在这个模块定义的,通过创建一个Wrapper封装类型,绕过孤儿规则
// Wrapper是在我们crate中定义的,所以可以正常实现Display特征
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);
}


新类型可以提供轻量级的封装机制

定义一些有语义的元组结构体的类型,比起原始类型更加有语义

struct Age(u32);

struct ID(u32);


fn  main()
{

}

创建类型别名

#![allow(unused)]
fn main() {
    type Kilometers = i32;

    let x: i32 = 5;
    let y: Kilometers = 5;

    println!("x + y = {}", x + y);
}

#![allow(unused)]
fn main() {
    type Thunk = Box<dyn Fn() + Send + 'static>;

    let f: Thunk = Box::new(|| println!("hi"));

    fn takes_long_type(f: Thunk) {
        // --snip--
    }

    fn returns_long_type() -> Thunk {
        // --snip--
        Box::new(|| ())
    }
}

从不返回的 never type

这个名字描述了它的作用:在函数从不返回的时候充当返回值

continue
#![allow(unused)]
fn main() {
    // match 的分支必须返回相同的类型。
    /**
     * 特殊情况:continue 的值是 !。也就是说,当 Rust 要计算 guess 的类型时,它查看这两个分支。
     * 前者是 u32 值,而后者是 ! 值。因为 ! 并没有一个值,Rust 决定 guess 的类型是 u32。
     *
     * 描述 ! 的行为的正式方式是 never type 可以强转为任何其他类型。允许 match 的分支以 continue 结束是
     * 因为 continue 并不真正返回一个值;相反它把控制权交回上层循环,所以在 Err 的情况,事实上并未对 guess 赋值。
     */
    let guess = "3";
    loop {
        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };
        break;
    }
}

panic!
/**
 * Rust 知道 val 是 T 类型,panic! 是 ! 类型,所以整个 match 表达式的结果是 T 类型。
 * 这能工作是因为 panic! 并不产生一个值;它会终止程序
 */
impl<T> Option<T> {
    pub fn unwrap(self) -> T {
        match self {
            Some(val) => val,
            None => panic!("called `Option::unwrap()` on a `None` value"),
        }
    }
}

有着 ! 类型的表达式是 loop
print!("forever ");

loop {
    print!("and ever ");
}

动态类型DST

str

// str。没错,不是 &str
// 解决办法:可以将 str 与所有类型的指针结合:比如 Box<str> 或 Rc<str>,&str 
let s1: str = "Hello there!";
let s2: str = "How's it going?";

//  &T 是一个储存了 T 所在的内存位置的单个值,&str 则是 两个 值:str 的地址和其长度。
// 地址和长度的类型都是usize 
let s1: &str = "Hello there!";
let s2: &str = "How's it going?";

特征是动态长度类型的:编译时借助Sized trait

为了处理 DST,Rust 有一个特定的 trait 来确定一个类型的大小是否在编译时可知:这就是 Sized trait。这个 trait 自动为编译器在编译时就知道其大小的类型实现

fn generic<T>(t: T) {
    // --snip--
}

// 自动转成如下所示
fn generic<T: Sized>(t: T) {
    // --snip--
}

//?Sized:  表示T时已知的或者未知的大小,只是适用于Sized特征
// 将 t 参数的类型从 T 变为了 &T:因为其类型可能不是 Sized 的,所以需要将其置于某种指针之后。
fn generic<T: ?Sized>(t: &T) {
    // --snip--
}

高级函数

函数指针fn(x)->y

函数的类型是 fn (使用小写的 “f” )以免与 Fn 闭包 trait 相混淆。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);
}

编译

 cargo run
    Blocking waiting for file lock on build directory
   Compiling blog v0.1.0 (/home/wangji/installer/rust/bobo/smartPtr)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 9.69s
     Running `target/debug/blog`
The answer is: 12

三种闭包类型:Fn,FnMut,FnOnce

Fn:以不可变形式捕获外部变量

FnMut:以可变的形式捕获外部变量

FnOnce:以转移所有权的方式捕获外部变量

闭包作为参数的好处:既可以传入参数,也可以传入函数指针

fn add_one(x: i32) -> i32 {
    x + 1
}

fn do_twice<T>(f: T, arg: i32) -> i32
where
    T: Fn(i32) -> i32,
{
    f(arg) + f(arg)
}

fn main() {
    let answer = do_twice(add_one, 5);

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

#![allow(unused)]
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();
    //使用函数指针替代
    // 看ToString::to_string)源码可知,任何实现Display特征的类型,都会实现ToString特征(也就可以调用to_string()方法)
    let list_of_strings: Vec<String> = list_of_numbers.iter().map(ToString::to_string).collect();
}

元组结构体和元组结构体成员

#![allow(unused)]
fn main() {
    enum Status {
        // 这些项使用 () 作为初始化语法,这看起来就像函数调用,
        //同时它们确实被实现为返回由参数构造的实例的函数。它们也被称为实现了闭包 trait 的函数指针,
        Value(u32),
        Stop,
    }

    // Status::Value当作一个函数指针,入参就是u32类型,返回值是Status类型
    let list_of_statuses: Vec<Status> = (0u32..20).map(Status::Value).collect();
    // 使用闭包初始化 `Status::Value`
    let list_of_statuses: Vec<Status> = (0u32..20)
        .map(|x| Status::Value(x)) // 闭包
        .collect();
}

返回一个闭包

可以的

#![allow(unused)]
fn main() {}

fn returns_closure() -> impl Fn(i32) -> i32 {
    |x| x + 1
}

错误代码

  • 由于闭包捕获 x,并且 x 可能会在不同的分支中被捕获为不同的值,Rust 必须确保闭包的生命周期与 x 的生命周期一致,而这需要更精确的类型推断。
#![allow(unused)]
fn main() {}

fn returns_closure(x: i32) -> impl Fn(i32) -> i32 {
    if x > 0 {
        move |y| x + y
    } else {
        move |y| x - y
    }
}

解决办法:使用特征对象进行动态派遣

#![allow(unused)]
fn main() {}

fn returns_closure(x: i32) -> Box<dyn Fn(i32) -> i32> {
    if x > 0 {
        Box::new(move |y| x + y)
    } else {
        Box::new(move |y| x - y)
    }
}

动态派遣 (dyn Fn(i32) -> i32):

  • 使用 dyn Fn(i32) -> i32 是 Rust 中的 动态特征对象(动态派遣)(trait object),它允许在运行时确定闭包的类型,而不需要在编译时确定所有具体的类型。
  • 在你的原始代码中,impl Fn(i32) -> i32 是一个 静态派遣 类型,它要求编译器在编译时推断出闭包的具体类型。由于你在 if 和 else 分支中返回不同的闭包,这会使得 Rust 无法统一推断返回的闭包类型,因此需要用动态特征对象来解决。

在原始的代码中,当使用 impl Fn(i32) -> i32 时,Rust 需要在编译时确定返回的闭包类型。但由于你在 if 和 else 分支中创建了两种不同的闭包,它们的类型会有所不同,导致 Rust 无法推导出统一的返回类型。

通过使用 Box<dyn Fn(i32) -> i32>,你将返回值的类型改成了 动态特征对象,这意味着 Rust 在编译时不再需要知道闭包的具体类型,而是通过运行时的动态派遣来决定如何调用闭包。此外,闭包被存储在堆上(通过 Box),因此它们可以拥有不同的大小,而 Rust 不需要在栈上存储这些不同大小的闭包。

声明(Declarative)宏

利用模式匹配(专门匹配代码)进行代码替换

比较难,可以看:The Little Book of Rust Macros

// #[macro_export] 宏导出注解,只要将定义了宏的 crate 引入作用域,宏就应当是可用的
// 此处有一个单边模式 ( $( $x:expr ),* ) ,后跟 => 以及和模式相关的代码块。如果模式匹配,该相关代码块将被执行
// 宏模式所匹配的是 Rust 代码结构而不是值
#[macro_export]
macro_rules! vec {
    ( $( $x:expr ),* ) => {
        {
            let mut temp_vec = Vec::new();
            $(
                temp_vec.push($x);
            )*
            temp_vec
        }
    };
}

// 当以 vec![1, 2, 3]; 调用宏时,$x 模式与三个表达式 1、2 和 3 进行了三次匹配。
//  ( $( $x:expr ),* ) 每次取出一个作为$x的值,然后执行相关代码块替换
let mut temp_vec = Vec::new();
temp_vec.push(1);
temp_vec.push(2);
temp_vec.push(3);
temp_vec

过程(Procedural)宏

三种 过程(Procedural)宏:

  • 自定义 #[derive] 宏在结构体和枚举上指定通过 derive 属性添加的代码
  • 类属性(Attribute-like)宏定义可用于任意项的自定义属性
  • 类函数宏function-like,看起来像函数不过作用于作为参数传递的 token

当前的过程(Procedural)宏必须定义为特殊的crate类型,并且要定义在自己的crate中。

过程宏的本质:
宏操作的源代码构成了输入 TokenStream,宏产生的代码是输出 TokenStream。该函数还附加了一个属性,该属性指定我们正在创建过程宏的类型。我们可以在同一个 crate 中拥有多种过程宏。

use proc_macro;

#[some_attribute]
pub fn some_name(input: TokenStream) -> TokenStream {
}

如何编写自定义 derive 宏:只适用于结构体和枚举

对于自定义衍生的库crate,会库名会加上后缀__derive

  • 发布的仍然需要单独发布,所以这个宏的人,仍然需要导入每一个crate(看main.rs)
 cargo new hello_macro --lib
    Creating library `hello_macro` package
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
~/installer/rust/bobo 
 
~/installer/rust/bobo 
 cd hello_macro/
~/installer/rust/bobo/hello_macro master 
 cargo new hello_macro_drive --lib

hello_macro_drive/src/lib.rs

extern crate proc_macro;

use crate::proc_macro::TokenStream; //读取和修改rust的代码
use quote::quote; //接受语法树结构,然后转成rust代码
use syn; //接受rust代码,转成可操作的语法树结构

#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
    // 将 Rust 代码解析为语法树以便进行操作
    let ast = syn::parse(input).unwrap(); //解析成语法树

    // 构建 trait 实现
    impl_hello_macro(&ast) //转化语法树
}
fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
    let name = &ast.ident;
    let gen = quote! {
        // #name会被替换成实际的类型名
        impl HelloMacro for #name {
            fn hello_macro() {
                // stringify!(#name)可以将表达式转成字符串,于format()!和println!()是不同的
                println!("Hello, Macro! My name is {}", stringify!(#name));
            }
        }
    };
    gen.into() //转成TokenStream
}

src/lib.rs

pub trait HelloMacro {
    fn hello_macro();
}

hello_macro_derive/Cargo.toml

[package]
name = "hello_macro_derive"
version = "0.1.0"
edition = "2021"

[lib]
proc-macro = true

[dependencies]
syn = "1.0"
quote = "1.0"

在这里插入图片描述

测试这个宏

use hello_macro::HelloMacro;
use hello_macro_derive::HelloMacro;

#[derive(HelloMacro)]
struct Pancakes;

fn main() {
    Pancakes::hello_macro();
}

Cargo.toml

[package]
name = "test_orocedural_macros"
version = "0.1.0"
edition = "2021"

[dependencies]
hello_macro = { path = "../hello_macro" }
hello_macro_derive = { path = "../hello_macro/hello_macro_derive" }

在这里插入图片描述

编译

 cargo run
    Updating `rsproxy-sparse` index
     Locking 6 packages to latest compatible versions
      Adding hello_macro v0.1.0 (/home/wangji/installer/rust/bobo/hello_macro)
      Adding hello_macro_derive v0.1.0 (/home/wangji/installer/rust/bobo/hello_macro/hello_macro_derive)
      Adding proc-macro2 v1.0.89
      Adding quote v1.0.37
      Adding syn v1.0.109
      Adding unicode-ident v1.0.13
   Compiling proc-macro2 v1.0.89
   Compiling unicode-ident v1.0.13
   Compiling syn v1.0.109
   Compiling hello_macro v0.1.0 (/home/wangji/installer/rust/bobo/hello_macro)
   Compiling quote v1.0.37
   Compiling hello_macro_derive v0.1.0 (/home/wangji/installer/rust/bobo/hello_macro/hello_macro_derive)
   Compiling test_orocedural_macros v0.1.0 (/home/wangji/installer/rust/bobo/test_orocedural_macros)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.31s
     Running `target/debug/test_orocedural_macros`
Hello, Macro! My name is Pancakes

类属性宏:适用于结构体、枚举、函数

伪代码

//#[route(GET, "/")]会生成代码,会将http请求映射到函数index()上
#[route(GET, "/")]
fn index() {}

// 用过程宏的方式定义这样一个属性,和自定义衍生宏的工作方式是类似的
// 这里有两个 TokenStream 类型的参数;第一个用于属性内容本身,也就是 GET, "/" 部分。第二个是属性所标记的项:
// 在本例中,是 fn index() {} 和剩下的函数体。
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {}

类函数宏

let sql = sql!(SELECT * FROM posts WHERE id=1);

// 与自定义衍生宏很像
// 注意注解使用的是#[proc_macro]
#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {}

参考

  • 第19章~编写不安全的Rust代码

  • 高级特征

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/916969.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

45.第二阶段x86游戏实战2-hook监控实时抓取游戏lua

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 本人写的内容纯属胡编乱造&#xff0c;全都是合成造假&#xff0c;仅仅只是为了娱乐&#xff0c;请不要…

数据结构 ——— 层序遍历链式二叉树

目录 链式二叉树示意图​编辑 何为层序遍历 手搓一个链式二叉树 实现层序遍历链式二叉树 链式二叉树示意图 何为层序遍历 和前中后序遍历不同&#xff0c;前中后序遍历链式二叉树需要利用递归才能遍历 而层序遍历是非递归的形式&#xff0c;如上图&#xff1a;层序遍历的…

Vue3 -- 基于Vue3+TS+Vite项目【项目搭建及初始化】

兼容性注意&#xff1a; Vite 需要 Node.js 版本 18 或 20。然而&#xff0c;有些模板需要依赖更高的 Node 版本才能正常运行&#xff0c;当你的包管理器发出警告时&#xff0c;请注意升级你的 Node 版本。【摘抄自vite官网】 这里我用的node版本是 v18.20.2 创建项目&#xf…

Linux(CentOS 7) yum一键安装mysql8

1、通过yum安装 &#xff08;1&#xff09;下载mysql 在Linux找个地方输入以下命令 wget https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm &#xff08;2&#xff09;安装mysql yum 仓库配置文件 [rootVM-8-15-centos ~]# sudo rpm -Uvh mysql80-c…

第5章-总体设计 5.2 需求转化为规格

5.2 需求转化为规格 1.框式产品&#xff08;1&#xff09;业务规格&#xff0c;这需要满足客户期望、有市场竞争力、颗粒度最合理。&#xff08;2&#xff09;整框规格&#xff0c;包括电源、功耗、散热、可靠性的规格&#xff0c;要保证整款满足环境应用要求。&#xff08;3&a…

Android setTheme设置透明主题无效

【问题现象】 1、首先&#xff0c;你在AndroidManifest.xml中声明一个activity&#xff0c;不给application或者activity设置android:theme, 例如这样&#xff1a; <applicationandroid:allowBackup"true"android:icon"mipmap/ic_launcher"android:lab…

软考教材重点内容 信息安全工程师 第 3 章 密码学基本理论

&#xff08;本章相对老版本极大的简化&#xff0c;所有与算法相关的计算全部删除&#xff0c;因此考试需要了解各个常 用算法的基本参数以及考试中可能存在的古典密码算法的计算&#xff0c;典型的例子是 2021 和 2022 年分别考了 DES 算法中的 S 盒计算&#xff0c;RSA 中的已…

Jmeter基础篇(24)Jmeter目录下有哪些文件夹是可以删除,且不影响使用的呢?

一、前言 Jmeter使我们日常做性能测试最常用的工具之一啦&#xff01;但是我们在和其他同学协同工作的时候&#xff0c;偶尔也会遇到一些问题&#xff0c;例如我想要给别人发送一个Jmeter工具包&#xff0c;但这个文件包往往会很大&#xff0c;比较浪费流量和空间&#xff0c;…

【电子元器件】磁珠常识与选型

1. 什么是磁珠 磁珠是一种电感型EMI静噪滤波器&#xff0c;实物和电感很像&#xff0c;现在用的最多的是铁氧体磁珠。 片状铁氧体磁珠 磁珠的单位是欧姆&#xff0c;根据型号的不同&#xff0c;可以抑制几MHz&#xff5e;几GHz的噪声&#xff0c;经常被用在信号线和电源线上…

PostgreSQL中如果有Left Join的时候索引怎么加

在PostgreSQL中&#xff0c;当你的查询包含多个LEFT JOIN和WHERE条件时&#xff0c;合理地添加索引可以显著提高查询性能。以下是一些具体的优化步骤和建议&#xff1a; 1. 分析查询 使用 EXPLAIN ANALYZE 命令分析你的查询&#xff0c;了解查询的执行计划&#xff0c;识别出连…

【全面系统性介绍】虚拟机VM中CentOS 7 安装和网络配置指南

一、CentOS 7下载源 华为源&#xff1a;https://mirrors.huaweicloud.com/centos/7/isos/x86_64/ 阿里云源&#xff1a;centos-vault-7.9.2009-isos-x86_64安装包下载_开源镜像站-阿里云 百度网盘源&#xff1a;https://pan.baidu.com/s/1MjFPWS2P2pIRMLA2ioDlVg?pwdfudi &…

Linux下MySQL的简单使用

Linux下MySQL的简单使用 导语MySQL安装与配置MySQL安装密码设置 MySQL管理命令myisamchkmysql其他 常见操作 C语言访问MYSQL连接例程错误处理使用SQL 总结参考文献 导语 这一章是MySQL的使用&#xff0c;一些常用的MySQL语句属于本科阶段内容&#xff0c;然后是C语言和MySQl之…

动态规划-背包问题——1049.最后一块石头的重量II

1.题目解析 题目来源 1049.最后一块石头的重量II——力扣 测试用例 2.算法原理 首先需要将该问题转化为0-1背包问题后再做分析 1.状态表示 根据数学中的知识我们知道将一个数字分为两个子数后求这两个子数的最小差值&#xff0c;那么就要求这两个子数尽可能接近于原数字的一…

MarkDown语法入门【保姆级教程】

MarkDown语法介绍 Markdown是一种轻量级标记语言 关于MarkDown语法的定义&#xff0c;官方已经有概述了&#xff1a; Markdown是一种轻量级标记语言&#xff0c;排版语法简洁&#xff0c;让人们更多地关注内容本身而非排版。它使用易读易写的纯文本格式编写文档&#xff0c;可…

5G与4G互通的桥梁:N26接口

5G的商用部署进程将是一个基于4G系统进行的长期的替换、升级、迭代的过程&#xff0c;4G系统是在过渡到5G全覆盖过程中&#xff0c;作为保障用户业务连续性体验这一目的的最好补充。 因此4G/5G融合组网&#xff0c;以及互操作技术将是各大运营商在网络演进中需要重点考虑的问题…

Transformer中的算子:其中Q,K,V就是算子

目录 Transformer中的算子 其中Q,K,V就是算子 一、数学中的算子 二、计算机科学中的算子 三、深度学习中的算子 四、称呼的由来 Transformer中的算子 其中Q,K,V就是算子 “算子”这一称呼源于其在数学、计算机科学以及深度学习等多个领域中的广泛应用和特定功能。以下是…

【UGUI】Unity 游戏开发:背包系统初始化道具教程

在游戏开发中&#xff0c;背包系统是一个非常常见的功能模块。它允许玩家收集、管理和使用各种道具。今天&#xff0c;我们将通过一个简单的示例来学习如何在 Unity 中初始化一个背包系统。我们将使用 Unity 2021.3.7 版本&#xff0c;并结合 C# 脚本来实现这一功能。 1. 场景…

Web端、App端的日志查看

开发和测试过程中&#xff0c;日志是定位问题的重要工具之一。无论是Web端还是App端&#xff0c;日志的作用如同医生的诊断报告&#xff0c;可以帮我们快速找到问题的根源。那么&#xff0c;如何高效查看并分析这些日志呢&#xff1f; 面对Web端和App端的不同特点&#xff0c;…

机器学习基础02_特征工程

目录 一、概念 二、API 三、DictVectorize字典列表特征提取 四、CountVectorize文本特征提取 五、TF-IDF文本1特征词的重要程度特征提取 六、无量纲化预处理 1、MinMaxScaler 归一化 2、StandardScaler 标准化 七、特征降维 1、特征选择 VarianceThreshold 底方差…

SpringCloud-使用FFmpeg对视频压缩处理

在现代的视频处理系统中&#xff0c;压缩视频以减小存储空间、加快传输速度是一项非常重要的任务。FFmpeg作为一个强大的开源工具&#xff0c;广泛应用于音视频的处理&#xff0c;包括视频的压缩和格式转换等。本文将通过Java代码示例&#xff0c;向您展示如何使用FFmpeg进行视…