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 runBlocking waiting for file lock on build directoryCompiling bobo v0.1.0 (/home/wangji/code/rust/bobo)Finished `dev` profile [unoptimized + debuginfo] target(s) in 9.83sRunning `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 runCompiling blog v0.1.0 (/home/wangji/installer/rust/bobo/smartPtr)Finished `dev` profile [unoptimized + debuginfo] target(s) in 6.29sRunning `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 runCompiling blog v0.1.0 (/home/wangji/installer/rust/bobo/smartPtr)Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.33sRunning `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 runBlocking waiting for file lock on build directoryCompiling blog v0.1.0 (/home/wangji/installer/rust/bobo/smartPtr)Finished `dev` profile [unoptimized + debuginfo] target(s) in 9.69sRunning `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
whereT: 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 --libCreating 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 runUpdating `rsproxy-sparse` indexLocking 6 packages to latest compatible versionsAdding 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.89Adding quote v1.0.37Adding syn v1.0.109Adding unicode-ident v1.0.13Compiling proc-macro2 v1.0.89Compiling unicode-ident v1.0.13Compiling syn v1.0.109Compiling hello_macro v0.1.0 (/home/wangji/installer/rust/bobo/hello_macro)Compiling quote v1.0.37Compiling 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.31sRunning `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代码
-
高级特征
相关文章:

rust高级特征
文章目录 不安全的rust解引用裸指针裸指针与引用和智能指针的区别裸指针使用解引用运算符 *,这需要一个 unsafe 块调用不安全函数或方法在不安全的代码之上构建一个安全的抽象层 使用 extern 函数调用外部代码rust调用C语言函数rust接口被C语言程序调用 访问或修改可…...

STM32F407简单驱动步进电机(标准库)
配置 单片机型号:STM32F104ZGT6 步进电机:YK28HB40-01A 驱动器:YKD2204M-Plus 接线方式: pu:接对应的产生PWM的引脚,这里接PF9,对应TIM14_CH1通道! pu-:接单片机的G…...

使用热冻结数据层生命周期优化在 Elastic Cloud 中存储日志的成本
作者:来自 Elastic Jonathan Simon 收集数据对于可观察性和安全性至关重要,而确保数据能够快速搜索且获得低延迟结果对于有效管理和保护应用程序和基础设施至关重要。但是,存储所有这些数据会产生持续的存储成本,这为节省成本创造…...
LeetCode131. 分割回文串(2024冬季每日一题 4)
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。 示例 1: 输入:s “aab” 输出:[[“a”,“a”,“b”],[“aa”,“b”]] 示例 2: 输入:s “a…...

万字长文解读深度学习——训练(DeepSpeed、Accelerate)、优化(蒸馏、剪枝、量化)、部署细节
🌺历史文章列表🌺 深度学习——优化算法、激活函数、归一化、正则化深度学习——权重初始化、评估指标、梯度消失和梯度爆炸深度学习——前向传播与反向传播、神经网络(前馈神经网络与反馈神经网络)、常见算法概要汇总万字长文解读…...

STM32—独立看门狗(IWDG)和窗口看门狗(WWDG)
概述: WDG(Watchdog) 看门狗,看门狗可以监控程序的运行状态,当程序因为设计漏洞、硬件故障、电磁干扰等原因,出现卡死或跑飞现象时,看门狗能计时复位程序,避免程序陷入长时间的罢工状态,保证系…...

ks8 本地化部署 F5-TTS
huggingface上有一个demo可以打开就能玩 https://huggingface.co/spaces/mrfakename/E2-F5-TTS 上传了一段懂王的演讲片段,然后在 generate text框内填了点古诗词,生成后这语气这效果,离真懂王就差一个手风琴了。 F5-TTS 项目地址…...

Web组态大屏可视化编辑器
1、零代码、一键构建、一键下载 用户只需通过拖拉拽操作,即可在画布上添加、调整和排列各种设备组件、图表和控件。零代码拖拽方式让用户能够实时预览界面效果,直观地观察布局、样式和数据的变化。 2、实时展示,自动化连接数据,用…...

【comfyui教程】让模特换衣服,comfyui一键搞定!
前言 一键穿上别人的衣服?揭秘ComfyUI模特换装工作流! 你有没有想过,某天早晨你起床后,只需轻轻一点,就能穿上明星昨晚在红毯上的华丽礼服?这种听起来像是科幻电影的情节,如今通过ComfyUI模特…...
数据湖与数据仓库的区别
数据湖与数据仓库是两种不同的数据存储和管理方式,它们在多个方面存在显著的区别。以下是对数据湖与数据仓库区别的详细阐述: 一、数据存储方式 数据仓库 通常采用预定义的模式和结构来存储数据。数据在存储前通常经过清洗、转换和整合等处理࿰…...
golang分布式缓存项目 Day6 防止缓存击穿
该项目原作者:https://github.com/geektutu/7days-golang。本文旨在记录本人做该项目时的一些疑惑解答以及部分的测试样例以便于本人复习。 1 缓存雪崩、缓存击穿与缓存穿透 概念解析: 缓存雪崩:缓存在同一时刻全部失效,造成瞬…...

Redis高可用-主从复制
这里写目录标题 Redis主从复制主从复制过程环境搭建从节点配置常见问题主从模式缺点 Redis主从复制 虽然 Redis 可以实现单机的数据持久化,但无论是 RDB 也好或者 AOF 也好,都解决不了单点宕机问题,即一旦 redis 服务器本身出现系统故障、硬…...
Angular框架:构建现代Web应用的全面指南
文章目录 前言一、Angular简介二、Angular的核心特性三、Angular的应用场景四、Angular的发展趋势五、如何开始使用Angular结语 前言 在当今高度竞争的互联网环境中,构建高效、响应迅速且易于维护的Web应用成为企业成功的关键。Angular框架以其强大的功能、灵活的架…...

Golang | Leetcode Golang题解之第563题二叉树的坡度
题目: 题解: func findTilt(root *TreeNode) (ans int) {var dfs func(*TreeNode) intdfs func(node *TreeNode) int {if node nil {return 0}sumLeft : dfs(node.Left)sumRight : dfs(node.Right)ans abs(sumLeft - sumRight)return sumLeft sumRi…...

gdb编译教程(支持linux下X86和ARM架构)
1、下载源码 http://ftp.gnu.org/gnu/gdb/ 我下载的8.2版本。 2、下载完后拷贝到linux的x86系统。 3、解压,然后进入到目录下,打开当前目录的命令行窗口。 4、创建一个生成目录。 5、我们先开始x86版本,这个比较简单,不需要配置…...

Android 开发指南:初学者入门
Android 是全球最受欢迎的移动操作系统之一,为开发者提供了丰富的工具和资源来创建各种类型的应用程序。本文将为你提供一个全面的入门指南,帮助你从零开始学习 Android 开发。 目录 1. 了解 Android 平台[1]2. 设置开发环境[2]3. 学习基础知识[3]4. 创…...

镭速大文件传输软件向金融银行的文档管理提供高效的解决方案
随着数字化浪潮的推进,金融机构对文档处理和大文件传输的需求日益增长。无论是中央机构还是地方分行,他们都急需一套强大的文档管理系统来应对日益庞大的数据量和日益复杂的业务需求。如何有效地管理海量文档,成为了金融机构面临的一大挑战。…...

D64【python 接口自动化学习】- python基础之数据库
day64 SQL-DQL-基础查询 学习日期:20241110 学习目标:MySQL数据库-- 133 SQL-DQL-基础查询 学习笔记: 基础数据查询 基础数据查询-过滤 总结 基础查询的语法:select 字段列表|* from 表过滤查询的语法:select 字段…...
HTTP 客户端怎么向 Spring Cloud Sleuth 传输跟踪 ID
在 Spring Cloud Sleuth 的请求链路追踪中,X-B3-TraceId 是第二个 ID,X-B3-SpanId 是第三个 ID。以下是 Sleuth 中各个追踪标识的含义: X-B3-TraceId:表示整个请求链路的全局唯一 ID,用于跟踪请求在多个服务间的流转。…...
为什么hbase在大数据领域渐渐消失
HBase 曾是大数据存储领域的标杆之一,凭借其强大的分布式、列式存储和高扩展性,广泛应用于电商、社交网络、金融等需要海量数据管理的场景。然而,近年来 HBase 的使用确实在减少,这主要是因为数据技术栈的演变和用户需求的变化。以下是一些主要原因: 1. 复杂的运维和管理…...

css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...
API网关Kong的鉴权与限流:高并发场景下的核心实践
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 引言 在微服务架构中,API网关承担着流量调度、安全防护和协议转换的核心职责。作为云原生时代的代表性网关,Kong凭借其插件化架构…...
k8s从入门到放弃之HPA控制器
k8s从入门到放弃之HPA控制器 Kubernetes中的Horizontal Pod Autoscaler (HPA)控制器是一种用于自动扩展部署、副本集或复制控制器中Pod数量的机制。它可以根据观察到的CPU利用率(或其他自定义指标)来调整这些对象的规模,从而帮助应用程序在负…...

【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
Java详解LeetCode 热题 100(26):LeetCode 142. 环形链表 II(Linked List Cycle II)详解
文章目录 1. 题目描述1.1 链表节点定义 2. 理解题目2.1 问题可视化2.2 核心挑战 3. 解法一:HashSet 标记访问法3.1 算法思路3.2 Java代码实现3.3 详细执行过程演示3.4 执行结果示例3.5 复杂度分析3.6 优缺点分析 4. 解法二:Floyd 快慢指针法(…...
深入浅出WebGL:在浏览器中解锁3D世界的魔法钥匙
WebGL:在浏览器中解锁3D世界的魔法钥匙 引言:网页的边界正在消失 在数字化浪潮的推动下,网页早已不再是静态信息的展示窗口。如今,我们可以在浏览器中体验逼真的3D游戏、交互式数据可视化、虚拟实验室,甚至沉浸式的V…...
数据库正常,但后端收不到数据原因及解决
从代码和日志来看,后端SQL查询确实返回了数据,但最终user对象却为null。这表明查询结果没有正确映射到User对象上。 在前后端分离,并且ai辅助开发的时候,很容易出现前后端变量名不一致情况,还不报错,只是单…...

高效的后台管理系统——可进行二次开发
随着互联网技术的迅猛发展,企业的数字化管理变得愈加重要。后台管理系统作为数据存储与业务管理的核心,成为了现代企业不可或缺的一部分。今天我们要介绍的是一款名为 若依后台管理框架 的系统,它不仅支持跨平台应用,还能提供丰富…...