异步编程简单实例
过程宏(自定义注解)
- 加载项目,关于Cargo.lock, Cargo.toml 文件
Cargo.toml 文件中保存的是配置比如,Cargo.lock 是首次构建项目的时候自动为您生成的文件,里面包含了首次项目中库的精确版本,如果项目是一个可执行文件,建议将Cargo.lock 文件上传到git上,让其他开发者与你保持同样的开发环境,如果是一个普通的库不需要将Cargo.lock 文件上传
- 宏定义
println!()
问题一: 有了函数 为什么还需要宏???- 宏是一种为写其他代码而写代码方式,比如关键字 println! ,
元编程 #[derive] 属性
能够减少大量编写和维护代码功能十分有效,比如println! 宏中内部实现调用底层io::std::out 将这些使用固定格式定义出来后,统一成模板 抽象出来。 - 宏能够接收更多参数,并且相比函数而言在编译器翻译代码前展开,但是函数只能是在运行时候被调用
- 宏缺点在于比函数定义更加复杂,更加难阅读和理解加维护
- 宏函数最大区别: 文件调用宏之前必须要定义,函数可以在任何地方调用和定义
- rust代码中使用
macro_rules!
来定义宏 比如以vec! 为实例e
- 宏是一种为写其他代码而写代码方式,比如关键字 println! ,
// 声明宏
#[macro_export] // 使用这个注解说明宏是可用的,没有此注解说明宏无法被引用
macro_rules! vec { // macro_rules!, 宏开始宏定义,所定义的宏并不带感叹号
// vec! 宏结构与 match 表达式结构类似 单边模式($($x:expr), *),更加复杂有多个单边模式
($($x:expr ), *) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x);
)* // 上边单边模式匹配的expr ,内部不断push
temp_vec
}
};
}
// 单向模式结构: 一对扩韩包含整个模式, 接下来是$, 后跟一对括号,捕获符合括号内模式 $()内 $x:expr ,其匹配Rust任意表达式并将其记录是 $x, h后面的* 表示匹配一个或者多个 其实说白了单向模式结构是一个正则表达式,内部结构中有其相关的表达式:
{
let mut temp_vec = Vec::new();
temp_vec.push(1);
temp_vec.push(2);
temp_vec.push(3);
temp_vec
}
- 类属性宏:
#[route(GET, "/")]
创建一个route的属性用于注解web应用程序框架 - 类函数宏 sql!
- 自定义的派生宏
是例子: 写一个自定义派生宏,类似于接口让用户自己写具体实现
// 传统的方式 使用 trait结合代码 实现
pub trait HelloMacro {
fn hello_macro(); // 有默认实现,用户使用自定义宏实现具体内容
}
struct Pancakes;
impl HelloMacro for Pancakes{
fn hello_macro() {
println!("Hello, Macro! My name is Pancakes!");
}
}
fn main(){
Pancakes::hello_macro();
}
如上所示代码中我们借助trait 接口方式,有个缺点任何使用 hello_macro方法 我都要写其具体实现,有没有一种方式类似于@GettMaiing一样 我只需要进行注解配置 就可以让代码进行使用,此种过程宏类似于JAVA中注解
rust 中没有反射能力, 需要代码在编译时候生成代码的宏
写过程宏中注意点:
1. 过程宏必须要要是属于自己的包 在当前项目中 新建一个包cargo new hello_marcro_derive --lib
2. 什么刚才创建的包是过程宏包,对Cargo.tom配置如下
[lib]
proc-macro = true
[dependencies]
syn = "1.0"
quote = "1.0"
3. 添加宏定义的的同样代码,这些同样代码对自定义宏的设定 表示自定义是宏 类似JAVA中自定义注解一些配置
// 添加大多数宏的所需要代码
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn;
#[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)
}
proc_macro包: 是编译器读取和操作我们Rust代码的API
syn: 将字符串中rust代码解析成一个我们可以操作的数据结构
quote: 将解析的数据结构转回Rust代码
当用户在一个类型指定#[derive(HelloMacro)] 时候,hello_macro_derive 函数将会被调用。原因在于我们已经使用 proc_macro_derive 及其指定名称对 hello_macro_derive 函数进行了注解:HelloMacro ,其匹配到 trait 名,这是大多数过程宏遵循的习惯。
首次进行引入代码如下的二进制结构
pancakes: 包路径下面
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/hello_macro_derive" }
-- main.rs
use hello_macro::HelloMacro;
use hello_macro_derive::HelloMacro;
#[derive(HelloMacro)]
struct Pancakes;
fn main() {
Pancakes::hello_macro();
}
- 宏定义过程:
hello_macro: trait的定义
hello_macro_derive: 对 trait 进行过程宏定义, hello_macro_derive在hello_macro 目录中
hello_macro/lib.rs
pub trait HelloMacro {
fn hello_macro();
}
hello_macro_derive/lib.rs 过程宏定义 具体类似JAVA中自定义的注解
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn;
#[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()
}
[package]
name = "hello_macro_derive"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
[dependencies]
syn = "1.0"
quote = "1.0"
客户端和服务器端例子
rust 使用Result 类型处理 成功和失败时候返回值类型
enum Result<T, E> {
Ok(T),
Err(E),
}
在错误传播过程中 rust 使用?
进行对错误机制的处理
如下所示?运算符会将OK中值返回给变量f, 如果出现了错误,?运算符会提早返回整个函数并将Err值传播给调用, ?之后直接使用调用链形式进一步缩短代码
let mut f = File::open("hello.txt")?;
? 功能与使用match 一样 是对match的简写
服务器代码:
服务运行不会停止,一直运行 加入loop 循环
use std::net::{TcpListener, TcpStream};
use std::io::{Read, Write};
use std::thread;
use std::time;
fn handle_client(mut stream: TcpStream, wait_time: u64) -> std::io::Result<()> {
let mut buf = [0; 512];
loop {
let bytes_read = stream.read(&mut buf)?;
if bytes_read == 0 {
return Ok(());
}
thread::sleep(time::Duration::from_secs(wait_time));
stream.write(&buf[..bytes_read])?;
stream.write(&("\n".as_bytes()))?;
}
}
fn main() -> std::io::Result<()> {
let listener = TcpListener::bind("127.0.0.1:8080")?;
for stream in listener.incoming() {
handle_client(stream?, 3)?;
}
Ok(())
}
客户端:
客户端内使用 线程池 join 链接不同服务器
// 客户端
use std::net::TcpStream;
use std::io::{prelude::*, BufReader, Write};
use std::str;
// use std::thread;
use futures::join;
use futures::executor;
fn use_server(server: &str, port: u16, content: &str) -> std::io::Result<()> {
let mut stream = TcpStream::connect((server, port))?;
let _ = stream.write(content.as_bytes())?;
// 读取服务器中数据
let mut reader = BufReader::new(&stream);
let mut buffer: Vec<u8> = Vec::new();
reader.read_until(b'\n', &mut buffer)?;
println!("recv from server: {} ", str::from_utf8(&buffer).unwrap());
Ok(())
}
async fn async_use_server(server: &str, port: u16, content: &str) {
use_server(server, port, content).unwrap();
}
async fn use_all_server() {
let f1 = async_use_server("127.0.0.1", 8080, "use server1 download 127.0.0.1:8080");
// let f2 = async_use_server("127.0.0.1", 8081, "use server2 download 127.0.0.1:8081");
join!(f1);
}
fn main() -> std::io::Result<()> {
// 开启异步服务
let f = use_all_server();
executor::block_on(f);
Ok(())
}
线程
- rust中不同于Java的线程之间的通行,和Go语言一样,线程间的通信主要通过消息的传递,
不要通过共享内存来通讯,通过通讯来共享内存
,因此rust实现消息传递并发工具 channel. - 编程中通道分为两部分: transmitter(发送者), 接受者(receiver), 使用场景利用通道实现聊天系统,利用很多线程进行分布式计算并将部分计算结果发送一个线程进行聚合
use std::sync::mpsc;
use std::thread;
fn main() {
// mpsc 是多个生产者,单个消费者
let (tx, rx) = mpsc::channel();
// 子线程发布消息主线程接收 ,move关键字 强制闭包获取 父参数的所有权
thread::spawn(move || {
let val = String::from("Hi");
tx.send(val).unwrap();
// 在线程发送完毕后不能使用val 所有权已经消失了
});
// recv() 阻塞方法,try_recv()费阻塞
let received = rx.recv().unwrap();
println!("Got: {}", received);
}
// 多个发送者 一个消费者
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
// --snip--
let (tx, rx) = mpsc::channel();
let tx1 = tx.clone();
thread::spawn(move || {
let vals = vec![
String::from("hi"),
String::from("from"),
String::from("the"),
String::from("thread"),
];
for val in vals {
tx1.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
thread::spawn(move || {
let vals = vec![
String::from("more"),
String::from("messages"),
String::from("for"),
String::from("you"),
];
for val in vals {
tx.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
for received in rx {
println!("Got: {}", received);
}
// --snip--
}
共享状态并发
在某种程度上,任何编程语言的通道d都类似有所有权的概念,一个值传送到通道后, 将无法在使用这个值
共享内存拥有多所有权,多个内存同时访问内存地址,因此保证有消息 必须使用互斥器mutex 锁的概念,比如Java中就是使用mutex防止数据竞争(尝试使用锁,使用完成后记得释放)。 rust得意于类型系统所有权,不需要类似Java 自动释放锁
Mutex 的API
use std::sync::Mutex;
fn main() {
let m = Mutex::new(5);
{
let mut num = m.lock().unwrap();
*num = 6;// rust 所有权的概念 自动释放锁
}
println!("m = {:?}", m);
}
- 在线程间共享内存: 启动十个线程,内个线程对计数器加1
use std::sync::Mutex;
use std::thread;
fn main() {
let counter = Mutex::new(0);
let mut handles = vec![];
for _ in 0..10 {
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}
cargo run
Compiling shared-state v0.1.0 (file:///projects/shared-state)
error[E0382]: use of moved value: `counter`
--> src/main.rs:9:36
|
5 | let counter = Mutex::new(0);
| ------- move occurs because `counter` has type `Mutex<i32>`, which does not implement the `Copy` trait
...
9 | let handle = thread::spawn(move || {
| ^^^^^^^ value moved into closure here, in previous iteration of loop
10 | let mut num = counter.lock().unwrap();
| ------- use occurs due to use in closure
明显编译不通过: 不能用Counter所有权移动到多个线程中
- 变动一: 使用智能指针 Rc, Mutex 封装进Rc 并且在所有权移入线程前Copy Rc
use std::rc::Rc;
use std::sync::Mutex;
use std::thread;
fn main() {
let counter = Rc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Rc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}
编译还是出现错误,Rc<T> 无法保证修改技术操作时候不被其他线程打断
- 原子引用技术Arc
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}