rust学习二十一.1、构建Rust web服务器和所有权简单小结
学习到这里我也很高兴!
这意味着,马上就要看完<<The rust programming Language>>。
基本上每天花费一些时间,前后大概耗费了半年左右的时间,速度实在不是很理想,年纪大了!
考虑还需要做不少的练习,大概编写了119个rs文件(当然大部分内容来自于书本),速度还将就。
但说实在,我心里还不是很有底,虽然也逐渐觉得对于rust越发熟悉,了解,但又不是真的非常了解!
现在是时候练习最后第二十一章节的内容了。
为了这个练习,我足足耗费也一周左右的空闲时间,所费前所未有!
我写的代码和书本上的差别不大,主体基本同书本上的,不同的地方在于:
1.添加了一个缓存静态文件的模块cache
2.添加一个处理系统信号的模块keyboard,用于接收ctrl+c,以便中止程序
由于模块有那么一些,所以构建了一个工作空间,如下图:

其中httpmain是主二进制工程,而threadpool就是来自于书本的线程池。
一、示例代码
本章节分别列出四个工程的代码:
- httpmain
- threadpool
- keyboard
- cache
以及工作空间的代码。
1.1、工作空间文件
Cargo.toml
[workspace]
resolver = "2"
members = [ "cache","httpmain", "keyboard", "threadpool"]
[workspace.package]
version = "0.1.0"
edition = "2021"
[workspace.dependencies]
rand = "0.9.0-beta.1"
1.2、httpmain
Cargo.toml
[package]
name = "httpmain"
version.workspace = true
edition.workspace = true
[dependencies]
chrono = "0.4"
threadpool={path="../threadpool"}
cache={path="../cache"}
keyboard={path="../keyboard"}
main.rs
use cache::{init_cache, ResourceCache};
/**
* 这里仅仅实现了一个单线程的HTTP服务器
* 及其简单,只能处理GET请求,并且只处理HTTP/1.1 GET / 和favicon.ico的请求
*
* 要完成这样的一个程序并不容易。除了要熟悉基本的内容,还要
* 1.熟悉rust的常见的api ,可以参考 https://doc.rust-lang.org/中的api资料
* 2.记住各种奇奇怪怪的unwrap()
* 3.理解for循环和迭代器,以及模式匹配所造成的一些困扰
*/
use chrono::Local;
use keyboard::Keyboard;
use std::{
fs,
io::{prelude::*, BufReader},
net::{TcpListener, TcpStream},
path::Path,
sync::{Arc, Mutex, OnceLock},
thread,
time::Duration,
};
use threadpool::ThreadPool;
//计算请求次数,作为一个练习,不用担心这个值会溢出
static COUNTER: OnceLock<Arc<Mutex<u32>>> = OnceLock::new();
/**
* 从这里读取客户端的请求头,然后根据请求头的某些信息来做不同的处理
*/
fn get_agent(headers: &Vec<String>) -> String {
let mut user_agent = String::new();
for line in headers {
if line.starts_with("User-Agent") {
user_agent = line.to_string();
//判断user_agent是否包含FireFox
if user_agent.contains("Firefox") {
user_agent = "Firefox".to_string();
} else if user_agent.contains("Edg") {
user_agent = "Edge".to_string();
} else if user_agent.contains("OPR") {
user_agent = "OPR".to_string();
}
else if user_agent.contains("Chrome") {
user_agent = "Chrome".to_string();
}
else{
user_agent = "Other".to_string();
}
break;
}
}
user_agent
}
/**
* 读取请求头的所有内容,并以字符串数组的形式返回。
*/
fn read_header(stream: &mut TcpStream) -> Vec<String> {
let buf_reader = BufReader::new(stream);
let http_request: Vec<String> = buf_reader
.lines()
.map(|result| result.unwrap())
.take_while(|line| !line.is_empty())
.collect();
return http_request;
}
fn main() {
COUNTER.get_or_init(|| Arc::new(Mutex::new(0)));
init_cache();
//初始化键盘
let kb = Keyboard::new();
let pool = Arc::new(ThreadPool::new(10));
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
println!("服务器在 127.0.0.1:7878 启动 ❤🌼");
println!("按下 Ctrl+C 以停止服务器");
while !kb.is_ctrl_c_pressed() {
match listener.accept() {
Ok((mut stream, _)) => {
pool.execute(move || {
response_ifhttpreq(&mut stream);
});
}
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
thread::sleep(Duration::from_millis(100));
continue;
}
Err(e) => {
eprintln!("Error accepting connection: {}", e);
break;
}
}
}
println!("\n\rServer shutting down gracefully...");
}
fn inc_counter() -> u32 {
let cc = COUNTER.get().unwrap().clone();
let mut currnet_counter = cc.lock().unwrap();
*currnet_counter += 1;
*currnet_counter
}
/**
* 极其简单的响应
* 当请求为HTTP/1.1 GET / 时才返回hello.html的内容
* 当请求为GET /favicon.ico HTTP/1.1 时返回favicon.ico的内容
*/
fn response_ifhttpreq(stream: &mut TcpStream) {
//这里故意每次处理都等待1秒钟
//以便放大单线程下的阻塞问题,便于观察效果
println!("正在接收数据...");
//打印当前时间,精确到毫秒
let current_counter = inc_counter();
let human_readable_time = Local::now().format("%Y-%m-%d %H:%M:%S%.3f").to_string();
let headers: Vec<String> = read_header(stream);
if headers.len() == 0 {
println!("第{}次接收时间: {}", current_counter, human_readable_time);
handle_404(stream);
return;
} else {
let user_agent = get_agent(&headers);
println!("第{}次接收时间: {} {}\n\r", current_counter, human_readable_time,user_agent);
let param = headers[0].as_str();
if param == "GET / HTTP/1.1" {
//显示首页
handle_root(stream);
} else if param == "GET /favicon.ico HTTP/1.1" {
//显示favicon.ico图标
handle_favicon(stream);
} else {
// 其它请求都返回404错误
handle_404(stream);
}
}
}
/**
* 处理根目录请求
*/
fn handle_root(stream: &mut TcpStream) {
let status_line = "HTTP/1.1 200 OK";
let cache_html = ResourceCache::get_cache_html("hello.html");
match cache_html {
Some(html) => {
println!("从缓存中获取hello.html");
let response = format!("{status_line}\r\n\r\n{html}");
stream.write_all(response.as_bytes()).unwrap();
return;
}
None => {}
}
println!("从文件系统读取hello.html");
let contents = fs::read_to_string("hello.html").unwrap();
let response = format!("{status_line}\r\n\r\n{contents}");
stream.write_all(response.as_bytes()).unwrap();
println!("html写入缓存中...");
ResourceCache::set_cache_html("hello.html", contents);
}
/**
* 处理404错误
*/
fn handle_404(stream: &mut TcpStream) {
let response = "HTTP/1.1 404 NOT FOUND\r\n\
Content-Length: 0\r\n\
\r\n";
stream.write_all(response.as_bytes()).unwrap();
}
/**
* 处理favicon.ico请求
*/
fn handle_favicon(stream: &mut TcpStream) {
let image = ResourceCache::get_cache_image("favicon.ico");
match image {
Some(bytes) => {
println!("从缓存中获取favicon.ico");
let mut response = format!(
"HTTP/1.1 200 OK\r\n\
Content-Type: image/x-icon\r\n\
Content-Length: {}\r\n\
\r\n",
bytes.len()
)
.into_bytes();
response.extend(&bytes);
stream.write_all(&response).unwrap();
stream.flush().unwrap();
return;
}
None => {}
}
// 读取 favicon.ico 文件
println!("从文件系统读取favicon.ico");
let favicon_path = Path::new("favicon.ico");
match fs::read(favicon_path) {
Ok(favicon_data) => {
// 构造并发送完整HTTP响应
let mut response = format!(
"HTTP/1.1 200 OK\r\n\
Content-Type: image/x-icon\r\n\
Content-Length: {}\r\n\
\r\n",
favicon_data.len()
)
.into_bytes();
response.extend(&favicon_data);
stream.write_all(&response).unwrap();
stream.flush().unwrap();
println!("favicon写入缓存中...");
ResourceCache::set_cache_image("favicon.ico", favicon_data);
}
Err(_) => {
// 如果图标文件不存在,返回 404
let response = "HTTP/1.1 404 NOT FOUND\r\n\
Content-Length: 0\r\n\
\r\n";
stream.write_all(response.as_bytes()).unwrap();
stream.flush().unwrap();
}
}
}
1.3、threadpool
Cargo.toml
[package]
name = "threadpool"
version.workspace = true
edition.workspace = true
[dependencies]
lib.rs
use std::{
sync::{mpsc, Arc, Mutex},
thread
};
type Job = Box<dyn FnOnce() + Send + 'static>;
struct Worker {
id: usize,
thread: Option<thread::JoinHandle<()>>
}
impl Worker {
//所有的worker其实是共享一个receiver,所以需要Arc+Mutex.以便满足mpsc的要求
//这种设计也只能满足于比较简单的场景,如果要支持复杂的场景,就需要用到更复杂的设计
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
//创建一个不断循环的线程,不断接收消息并执行任务
//者是一个死循环,如何会退出了?
//spawn返回一个JoinHandle,可以用来等待线程结束
let thread = thread::spawn(move || loop {
let message = receiver.lock().unwrap().recv();
match message {
Ok(job) => {
println!("Worker {id} got a job; executing.");
job();
}
Err(_) => {
println!("Worker {id} disconnected; shutting down.");
break;
}
}
});
Worker {
id,
thread: Some(thread),
}
}
}
pub struct ThreadPool {
workers: Vec<Worker>,
sender: Option<Arc<mpsc::Sender<Job>>>,
}
impl ThreadPool {
pub fn new(size: usize) -> ThreadPool {
assert!(size > 0);
let (sender, receiver) = mpsc::channel();
let receiver = Arc::new(Mutex::new(receiver));
let mut workers = Vec::with_capacity(size);
for id in 0..size {
workers.push(Worker::new(id, Arc::clone(&receiver)));
}
ThreadPool {
workers,
sender: Some(Arc::new(sender)),
}
}
pub fn get_sender(&self) -> Option<Arc<mpsc::Sender<Job>>> {
// as_ref() 返回一个Option的引用,形如 Opotion<&T>
// Option的map接受一个匿名函数/闭包,对其中的T执行闭包,并返回一个新的Option
self.sender.as_ref().map(Arc::clone)
}
pub fn execute<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
let job = Box::new(f);
self.sender.as_ref().unwrap().send(job).unwrap();
}
}
impl Drop for ThreadPool {
fn drop(&mut self) {
//删除sender,会导致receiver收到一个错误,导致所有worker退出循环
//take()会返回Option中的T,并把Option设置为None
drop(self.sender.take());
for worker in &mut self.workers {
println!("Shutting down worker {}", worker.id);
if let Some(thread) = worker.thread.take() {
thread.join().unwrap();
}
}
}
}
1.4、keyboard
Cargo.toml
[package]
name = "keyboard"
version.workspace = true
edition.workspace = true
[dependencies]
signal-hook = "0.3"
lib.rs
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use signal_hook::consts::signal::SIGINT;
use signal_hook::flag::register;
/**
* 系统信号处理器,捕获Ctrl+C(SIGINT)信号
*/
pub struct Keyboard {
ctrl_c_pressed: Arc<AtomicBool>,
}
impl Keyboard {
pub fn new() -> Keyboard {
let ctrl_c_pressed = Arc::new(AtomicBool::new(false));
let pressed_ref = ctrl_c_pressed.clone();
// 注册SIGINT(Ctrl+C)信号处理器
register(SIGINT, pressed_ref).expect("无法注册信号处理器");
Keyboard { ctrl_c_pressed }
}
/// 检查是否收到Ctrl+C信号
pub fn is_ctrl_c_pressed(&self) -> bool {
self.ctrl_c_pressed.load(Ordering::Relaxed)
}
/// 重置Ctrl+C状态
pub fn reset_ctrl_c_state(&self) {
self.ctrl_c_pressed.store(false, Ordering::Relaxed);
}
}
1.5、cache
Cargo.toml
[package]
name = "cache"
version.workspace = true
edition.workspace = true
[dependencies]
lib.rs
use std::collections::HashMap;
use std::sync::{Arc, Mutex, OnceLock};
static CACHE: OnceLock<Arc<Mutex<ResourceCache>>> = OnceLock::new();
pub enum Container {
Text(String),
Image(Vec<u8>),
}
pub struct ResourceCache {
data: HashMap<String, Container>,
}
impl ResourceCache {
pub fn new() -> Self {
ResourceCache {
data: HashMap::new(),
}
}
pub fn get(&self, key: &str) -> Option<&Container> {
self.data.get(key)
}
pub fn set(&mut self, key: String, value: Container) {
self.data.insert(key, value);
}
pub fn remove(&mut self, key: &str) -> Option<Container> {
self.data.remove(key)
}
pub fn has(&self, key: &str) -> bool {
self.data.contains_key(key)
}
pub fn set_cache_html(path: &str, text: String) {
//OnceLock::get()方法返回一个Option<&T>
//unwrap()方法将Option<&T>转换为&T,如果Option为None则panic
//通过get().unwrap()方法获取到Arc<Mutex<ResourceCache>>的引用,然后通过clone()方法添加一个引用计数,
//这样就可以在多个地方共享这个缓存。
let arc_cache = CACHE.get().unwrap().clone();
//通过lock()方法获取到ResourceCache的互斥锁,然后使用unwrap()方法将其转换为&mut ResourceCache的引用。
let cache = &mut *arc_cache.lock().unwrap();
if let None = cache.get(path) {
cache.set(path.to_string(), Container::Text(text));
}
}
pub fn get_cache_html(path: &str) -> Option<String> {
let arc_cache = CACHE.get().unwrap().clone();
let cache = &*arc_cache.lock().unwrap();
if let Some(txt) = cache.get(path) {
match txt {
Container::Text(text) => {
return Some(text.clone());
}
_ => {
return None;
}
}
} else {
return None;
}
}
pub fn set_cache_image(path: &str, bytes: Vec<u8>) {
let arc_cache = CACHE.get().unwrap().clone();
let cache = &mut *arc_cache.lock().unwrap();
if let None = cache.get(path) {
cache.set(path.to_string(), Container::Image(bytes));
}
}
pub fn get_cache_image(path: &str) -> Option<Vec<u8>> {
let arc_cache = CACHE.get().unwrap().clone();
let cache = &*arc_cache.lock().unwrap();
if let Some(image) = cache.get(path) {
match image {
Container::Image(bytes) => {
return Some(bytes.clone());
}
_ => {
return None;
}
}
} else {
return None;
}
}
}
pub fn init_cache() {
CACHE.get_or_init(|| Arc::new(Mutex::new(ResourceCache::new())));
}
测试结果

可以看到缓存生效了。其次也发现不是每一个请求都会发送有意义的内容,这大概是浏览器所导致的!
浏览器输出(Opera)

顺便说下,通过测试发现不同的浏览器的请求存在细微差别,有的浏览器刷新一次页面会有许多次请求,例如opera,有的不会,例如firefox,edge。
程序还不够完善! 按照crtl+c并不会停止程序,而是必须再刷新下浏览器才会停止,需要改进...

二、一些想法
2.1、关于所有权和生命周期
毫无疑问,这依然是掌握最不好的部分。对于熟悉了其它语言的人而言,以往的经验往往会变成一种桎梏!
到现在,对于所有权的主要印象依然是"三原则",对生命周期的印象主要是奇怪的'a之类的符号.
2.2.1、所有权问题
所有权问题包括所有权和借用等一些内容。
核心有:
- 所有权和借用的定义
- 什么情况下所有权会转移,什么情况下所有权不会转移
- 如何在不转移的情况下使用值(除了标准的借用)
下图表达了这些内容(注意和后文的不是完全一致)

注意:上图中关于Fn的描述是不对的,正确的应该是:Fn会可以执行多次,其它的多余描述都是不恰当的。
具体包括:
a、三原则
- 一个值(不是变量)一定要有所有者
- 而且任意时刻,一个值只能有一个所有者
- 值所有者离开作用域后,持有的值会被立刻释放
注意:对于第二条存在疑问,例如Rc,Arc是什么意思?
b、函数调用参数对所有权的影响
- 堆变量直接传递-所有权转移
- 利用&进行借用传递
- 例如&mut进行可变借用传递
例外情况:如果实现了Copy特质,则不会被剥夺所有权。
c、作为复合类型成员对所有权的影响
- 如果直接作为复合类型成员,会转移所有权
例外情况:如果实现了Copy特质,则不会被剥夺所有权。
d、匿名函数/闭包对所有权的影响
记住FnOnce,FnMut,Fn对所有权转移的影响(这三个描述源于rust编程语言的中文翻译)
FnOnce适用于只能被调用一次的闭包。所有闭包至少都实现了这个 trait,因为所有闭包都能被调用。一个会将捕获的值从闭包体中移出的闭包只会实现FnOncetrait,而不会实现其他Fn相关的 trait,因为它只能被调用一次。FnMut适用于不会将捕获的值移出闭包体,但可能会修改捕获值的闭包。这类闭包可以被调用多次。 简而言之,FnMut会修改被捕获的变量,FnMut可以执行多次。Fn适用于既不将捕获的值移出闭包体,也不修改捕获值的闭包,同时也包括不从环境中捕获任何值的闭包。这类闭包可以被多次调用而不会改变其环境,这在会多次并发调用闭包的场景中十分重要
注:关于Fn的中文翻译是有问题的,正确的意思是:Fn不会修改捕获的变量,也不会获得被捕获变量的所有权,且Fn可以被执行多次。它和FnOnce的唯一区别在于,Fn可以执行多次.
有关内容参见:rust学习十三.1、RUST匿名函数(闭包)
e、引用计数指针对所有权的影响
应该说的是Arc指针,允许一个值有多个所有者,通过Rc::clone增加引用计数。
按照rust的说法,clone并不会花费多少时间!
因为这个特性,Arc在多线程编程中是大量存在。
只不过这样,和jvm之类的引用计数又有什么区别了?
f、多线程对所有权的影响
Send特质 --表明实现了 Send 的类型值的所有权可以在线程间传送。几乎所有的 Rust 类型都是Send 的,不过有一些例外,包括 Rc<T>
Sync特质-表明一个实现了 Sync 的类型可以安全的在多个线程中拥有其值的引用
* 1.Send 标记 trait 表明实现了 Send 的类型值的所有权可以在线程间传送。几乎所有的 Rust 类型都是Send 的,
* 不过有一些例外,包括 Rc<T>
* 2.任何完全由 Send 的类型组成的类型也会自动被标记为 Send。
* 几乎所有基本类型都是 Send 的,除了第二十章将会讨论的裸指针(raw pointer)
* 3.Sync 标记 trait 表明一个实现了 Sync 的类型可以安全的在多个线程中拥有其值的引用
* 4.类似于 Send 的情况,基本类型是 Sync 的,完全由 Sync 的类型组成的类型也是 Sync 的
* 5.通常并不需要手动实现 Send 和 Sync trait,因为由 Send 和 Sync 的类型组成的类型,
* 自动就是 Send 和 Sync 的。因为它们是标记 trait,甚至都不需要实现任何方法。它们只是用来加强并发相关的不可变性的
* 6.手动实现这些标记 trait 涉及到编写不安全的 Rust 代码
g、Copy特质对所有权的影响
如果一个类型实现了Copy特质,那么作为参数传递,或者赋值的时候,并不会丢失所有权,因为rust会默认先复制一份值。
所以这个会让代码变得更加复杂。
参见示例:rust学习五、Rust所有权和函数传参
h、静态编译对变量定义的要求
rustc必须在在编译的时候知道某个变量的大小,如果不能知道,那么会报错。
因为这个比较苛刻要求,所以很多变量必须定义为为引用类型。
2.1.2、生命周期问题
所有权、借用已经把问题搞复杂了,还要来个生命周期。
为什么要有生命周期,这是因为rust的变量(值)有值的作用范围,但是在某些情况下rustc并不知道使用的多个变量的作用范围是否一致,
为了不在运行时候出现错误,rustc强制我们必须给参数做标记,标记这些参数的作用返回是可相容(通常是一样的)。
rustc这样的做的最主要目的就是为了把可能的异常消除在编译时,而不是保留到运行时。
这样的副作用,就是让代码丑陋难看,但又有什么办法了?
这无疑是一个糟糕的设计,至少工程上是,也许将来的某天可以消除这个。
对于我们而言,主要记住几点:
1.如果是函数/方法,那么如果没有返回的情况下,通常不需要标记生命周期
2.如果参数是&self,&mut self那么通常也不需要标注
3.生命周期标记通常都是引用导致的
2.2、关于匿名函数和闭包
在这个东西大行其道的时候,掌握它无疑很重要的。
掌握这个东西,其实主要掌握FnOnce,FnMut,Fn三个东西,掌握外部变量的所有权是否会转移,是否会被修改,是否会转出。
除了这些基本的东西,还需要考虑匿名函数嵌套问题。
匿名嵌套式没有问题的,至少一般情况下。
其它问题,以后再补充...
2.3、关于Option和枚举
Option和Resut这两个东西,在rust中平常得不能再平常了。
因为它能解决几个问题:
1.异常
2.一个类型存储多种值,虽然这在其它语言很容易,但是在rust中就基本只能这么干了。
3.能够和rust喜欢的match一起工作
关于match,我们有必要知道各种匹配方式。
例如这个项目中,定义了一个类型用于存储各种缓存数据:
pub enum Container {
Text(String),
Image(Vec<u8>),
}
pub struct ResourceCache {
data: HashMap<String, Container>,
}
2.4、关于无处不在的unwrap
这和Option、Result滥用是相关的。
Option和Resut都有方法unwrap方法.
例如Option的unwrap()的说明:
Returns the contained Some value, consuming the self value.
Because this function may panic, its use is generally discouraged.
Panics are meant for unrecoverable errors, and may abort the entire program.
Instead, prefer to use pattern matching and handle the None case explicitly,
or call unwrap_or, unwrap_or_else, or unwrap_or_default.
In functions returning Option, you can use the ? (try) operator.
简单而言,unwrap()得到其中的值,但可能会异常.
rust不建议使用这个,因为可能会引发错误:
let some_number = Some(5); // some_number的类型是Option<i32>
let score=some_number.unwrap()*20;
println!("some_number is {}", score);
let message:Option<String>=None;
message.unwrap();
这一段代码最终会执行错误,因为最后一行代码有问题。
郑重提示:务必认证研究Option的每个方法!!! 这是rust的提示,也是我的体会.
参考:rust枚举说明
2.5、关于多线程
复杂的多线程编程不是三言两语可以说清楚点的。 但是主要内容书本上已经列出了:
- 线程
- 并发(信道和共享内存)
- Arc指针和Mutex互斥变量
- Send和Sync特质--基本上是自动实现,这是主要好处
在本例中:
采用的是信道进行多线程的通信
使用OnceLock<Arc<Mutex<ResourceCache>>>来存储缓存数据
使用OnceLock<Arc<Mutex<u32>>>存储请求计数
采用信道还是采用共享内存,看个人习惯,因为Rust都支持!
2.6、关于智能指针
智能指针的重要性毋庸置疑。
这是因为大部分的应用都需要用到多线程,而多线程必须要考虑到各种复杂数据结构,这些复杂的数据结构基本都是智能指针。
智能指针主要关注几点:
1.Deref和Drop特质
2.Deref的隐式转换
主要是&Box之类会转为 &(*Box)方便了程序程序编写,由于这种隐式转换,其实也会产生一些问题。
有的人习惯隐式转换写法;程序中夹杂着有隐式和无隐式的写法,会然部分人不太适应。
参考:rust学习十五.3、智能指针相关的Deref和Drop特质
2.7、如何定义和传递参数
在大部分情况下,当我们为函数/方法传递参数的时候,应该还是比较简单的。
只需要注意几点:
1. 参数怎么定义基本上怎么传,这复合大部分情况
2.对于实现了Copy和Deref有一些特别的方式
a.如果实现了Copy,参数是T,那么实际传递的是T的副本(浅拷贝)
b.如果实现了Deref,那么参数要求&T,那么直接&T就可以自动解引用,当然如果要写成&(*T)依然还是支持的,就看个人习惯了
3.如果可以,那么定义一些中间变量,可以省掉直接在函数调用中书写&,&mut,mut之类的符号
示例:
#[derive(Debug, Clone,Copy)]
struct Bag {
price: f32,
no: u32,
}
/**
* 由于Bag实现了Copy trait,sell_bag函数可以拷贝传入参数
*/
fn sell_bag(bag:Bag)->f32{
println!("{:?} 已经售出..",bag);
bag.price
}
fn show_bag(bag:&Bag){
println!("{:?}",bag);
}
fn main() {
let bag=Bag{
price:100.0,
no:1000
};
println!("{:?}",bag);
//1.0 演示Copy特质对传参的影响
// 拷贝bag对象,传入sell_bag函数
let price=sell_bag(bag);
println!("{}",price);
// 再次打印bag对象,可以发现bag没有被修改
println!("{:?}",bag);
//2.0演示Deref特质对传参的影响
let mybag=Box::new(Bag{
price:100.0,
no:1000
});
show_bag(&mybag);
}
2.8、熟悉api文档
这是学习每一门语言的基本要求。
学习Jquery,要看jquey文档,以便了解jq支持什么方法
学习java,要查看javaDoc,了解诸如List,String支持什么方法或者接口等
学习Vc++,需要掌握winapi
自然,学些Rust,需要掌握以下这些重要类型的功能:
- Vec
- String
- enum(Option,Result)
- struct
- Box,Rc,Arc,RefCell,OnceLock
- Receiver,Sender
除了基本的功能,需要重点关于关于所有权之类的方法。
参考: rust标准库API lib.rs官网,docs.rs的官网
三、小结
学到这里,只能说熟悉了Rust的知识(略知一二),不敢说掌握,也不能说精通。距离掌握还是比较远!
后面的计划应该是这样:
1.针对api文档加深对rust基本知识了解,同时完善有关笔记/博客
2.到lib.rs和crates.io看看一些比较比较优秀和流行的内容,看看别人的代码,总结一些内容,记录成博客
资料太多,只能挑选一些看看,例如lib.rs大概有18万个单元包。到死也看不了几个。
3.试着编写几个个人小工具
4.找个机会加入rust的组织或者参与一些项目
rust大概是鄙人学过的十几种语言中,最难于掌握的一种,因为它的所有权和乱七八糟的符号!
本文来自博客园,作者:正在战斗中,转载请注明原文链接:https://www.cnblogs.com/lzfhope/p/18841537
浙公网安备 33010602011771号