这是初学者学习Rust时记录的一些学习摘要和部分Cookbook过时修正内容 由于 Rust Cookbook 内容很大部分过时,所以做了许多修正和提示,以符合现代 Rust 规范。 若有纰漏,劳烦指出

Rust Candy

  1. Option<T>is_none()实现,返回布尔值。

  2. 使用if let while let快速取指针包装值。

     if let Some(value) = pointer {
         println!("{:?}", value);
     }
  3. nums.iter().max().unwrap();获取一个Vector的最大值。

  4. vec![0; (max_val + 1) as usize]是格式化一个Vector的方法,可以快速填充。

     let vec = vec![0; a as usize];
  5. 对字符串String类型可以直接用chars().enumerate()化成可迭代切片。

     let s = String::from("arrrw");
    
     for (_, i) in s.chars().enumerate() {
         if i == 'r' {
             println!("{}", String::from("r!"));
         }
     }
  6. 使用rand库生成随机数时,依据最新版本来操作。用rand::rng()声明一个随机源,调用这个随机源即可生成需要的随机量。

     use rand::Rng;
    
     let mut rng = rand::rng();
     let v1: u8 = rng.random(); // Generate random in u8 range.
     let v2: u16 = rng.rangdom_range(0..=1000); // Generate u16 random in [0, 1000].
     let b1: bool = rng.random(); // Generate random true/false.
  7. 亦可借助rand库生成一个给定分布的随机数,例如正态分布。使用分布时,需要引入其父库rand_distr。此时,对一个分布使用sample(distr)即可生成此分布的一个样本。实验可知,此操作并不影响源。

     use rand::Rng;
     use rand_distr::Normal;
    
     fn main() {
         let mut rng = rand::rng();
    
         let normal = Normal::new(2.0, 2.0).unwrap();
    
         for _ in 0..=10 {
             println!("{}", rng.random::<u8>());
         }
    
         for _ in 0..=10 {
             println!("{}", rng.sample(normal));
         }
     }
  8. 可以使用rand_distr生成自定义类型的随机。

     use rand_distr::{Distribution, StandardUniform};
     use rand::Rng;
    
     struct Point {
         x: i64,
         y: i64,
     }
    
     impl Distribution<Point> for StandardUniform {
         fn sample<R: ?Rng + ?Sized>(&self, &mut rng) -> Point {
             let (px, py) = rng.random();
             Point {
                 x: px,
                 y: py,
             }
         }
     }
    
     fn main() {
         let mut rng = rand::rng();
         let p = rng.random::<Point>();
    
         println!("Point: ({}, {})", p.x, p.y);
     }
  9. 甚至可以用rand来创建有字母的密码。

     use rand::distr::Alphanumeric;
     use rand::Rng;
    
     fn main() {
         let pwd: String = rand::rng()
             .sample_iter(&Alphanumeric)
             .take(25)
             .map(char::from)
             .collect();
    
         println!("Password: {}", pwd);
     }

    注意:此处若使用变量rand::rng(),其经过sample_iter(&distr)后会被drop,若要继续使用这个源,请在操作前clone()

  10. 可以自定义一个字符串所取的字符来随机。

     use rand::Rng;
    
     fn main() {
         const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
             abcdefghijklmnopqrstuvwxyz\
             0123456789)(*&^%$#@!~";
         const PWD_LEN: usize = 25;
    
         let pwd: String = (0..PWD_LEN)
             .map(|_| {
                 let idx = rand::rng()
                     .random_range(0..CHARSET.len());
                 CHARSET[idx] as char
             })
             .collect();
    
         println!("Password: {}", pwd);
     }
  11. 对一个Vec<T>中重复元素进行清除而不用保留原来的顺序的情况,使用以下组合技。

     let mut vec = vec![1, 1, 4, 5, 1, 4, 1, 9, 1, 9, 8, 1, 0];
    
     vec.sort();
     vec.dedup();
    
     println!("{:?}", vec);
  12. 使用sort_unstable()来处理一些无顺序数可以减少内存消耗。

     let mut vec = vec![1, 1, 4, 5, 1, 4, 1, 9, 1, 9, 8, 1, 0];
     vec.sort_unstable();
    
     assert_eq!(vec, vec![0, 1, 1, 1, 1, 1, 1, 4, 4, 5, 8, 9, 9]);
  13. 利用sort_by()实现快速降序等离奇的排序操作。

     let mut vec = vec![1, 1, 4, 5, 1, 4, 1, 9, 1, 9, 8, 1, 0];
    
     vec.sort_by(|x, y| {
         y.cmp(x)    // 降序
     });
    
     println!("{:?}", vec);
    • 此操作对处理结构体等内部数据同样适用。需要注意,如果对结构体使用sort()需要对其声明一个实现。
     #[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]    // Must included for sort()
     struct Person {
         name: String,
         age: u16,
         gender: char,
     }
    
     fn main() {
         let rana = Person {
             name: String::from("Rana"),
             age: 16,
             gender: 'F',
         };
         let tomori = Person {
             name: String::from("Tomori"),
             age: 16,
             gender: 'F',
         };
         let anon = Person {
             name: String::from("Anon"),
             age: 16,
             gender: 'F',
         };
         let rikki = Person {
             name: String::from("Rikki"),
             age: 16,
             gender: 'F',
         };
         let soyo = Person {
             name: String::from("Soyo"),
             age: 16,
             gender: 'F',
         };
    
         let mut mygo = vec![rana, tomori, anon, rikki, soyo];
    
         mygo.sort();
    
         println!("Sorted by name: {:?}", mygo);
    
         // If not include derive[***], must impl as below.
         mygo.sort_by(|x, y| x.name.cmp(&y.name));
    
         println!("Sorted by name: {:?}", mygo);
     }
  14. 对于Vec<f64>Vec<f128>之类没有实现sort()cmp()的Vector,可以用sort_by()x.partical_cmp(y).unwrap()来实现排序。

     let  mut vec_f = vec![1.14, 5.14, 1.19, 8.10, 19.19, 350.234, 1.14, 14.51, 51.41];
    
     vec_f.sort_unstable_by(|x, y| {
         x.partial_cmp(y).unwrap()
     });
    
     println!("{:?}", vec_f);
  15. 对于Option<T>包装的类型,可以直接用unwrap()来处理获取内部值,如果无法确定是否为None,需要使用更安全的unwrap_or()unwrap_or_else()unwrap_or_default()这些实现来替代。

  16. 使用clap库简单创建参数支持。

     use clap::Parser;
    
     #[derive(Parser, Debug)]
     #[command(version, about, long_about = None)]
     struct Args {
         #[arg(short, long)]
         name: String,
    
         #[arg(short, long, default_value_t = 16)]
         age: u8,
     }
    
     fn main() {
         let args = Args::parse();
    
         println!("Hello {}, you are {} years old!", args.name, args.age);
     }
  17. 使用ansi_term库来对终端输出添加彩色文本、粗体文本、粗体彩色文本、闪烁文本、斜体文本等。

     use ansi_term::{ Colour, Style };
    
     fn main() {
         println!("Hello, {}! {} a {}.",
             Colour::Green.paint("world"),
             Style::new().bold().italic().dimmed().paint("I'm"),
             Colour::Red.bold().blink().paint("GAY"));
     }

    这样一来,可以简化ANSI符号的输入麻烦。

  18. 使用flate2tar库对Gzip压缩文件进行操作,可以进行解压和压缩。

    可通过flate2::read::GzDecoder::new()来解码一个.tar.gz.tgz这一类Gzip压缩包。 在此之前用std::fs::File::open(path)打开文件。 之后再通过tar::Archive::new()来打开这个压缩包。 之后,对打开的压缩包使用unpack(dst: path)实现来解压。 unpack()open()需要对其进行错误处理。

    同理,压缩按照下方压缩方式来进行。

     use std::fs::File;
     use flate2::read::GzDecoder;
     use tar::Archive;
    
     use flate2::write::GzEncoder;
     use flate2::Compression;
     use tar::Builder;
    
     fn main() {
         println!("Hello, world!");
    
         // ! Decompress
         let path = "Python-3.14.1.tgz";
         let tar_gz = File::open(path).unwrap();
         let tar = GzDecoder::new(tar_gz);
         let mut archive = Archive::new(tar);
         archive.unpack(".").unwrap();
    
         // ! Compress
         let tar_gz_cp = File::create("archive.tar.gz").unwrap();
         let enc = GzEncoder::new(tar_gz_cp, Compression::fast());
         let mut tar_cp = Builder::new(enc);
         tar_cp.append_dir_all(".", "cp_src").unwrap();
     }
  19. Rust 常用错误处理法

    Rust 提供多种安全的错误处理方法:

    1. expect() - 带自定义错误信息

       let tar_gz = File::open(path).expect("无法打开文件");
    2. ? 操作符 - 自动传播错误(推荐)

       let tar_gz = File::open(path)?;
    3. match - 精细控制

       let tar_gz = match File::open(path) {
           Ok(file) => file,
           Err(e) => {
               eprintln!("打开文件失败: {}", e);
               return Err(e.into());
           }
       };
    4. if let - 只处理成功或失败情况

       if let Ok(tar_gz) = File::open(path) {
           // 处理成功情况
       } else {
           // 处理失败情况
       }
    5. unwrap_or() / unwrap_or_else() - 提供默认值

       let tar_gz = File::open(path).unwrap_or_else(|_| File::open("default.tgz").unwrap());
  20. 使用creatcrossbeam来操作并行(Concurrency),这种操作只针对短时存在的多线程任务。

    此处使用分治来查找最大值,示例如下:

     use crossbeam;
    
     fn main() {
         let arr = &[1, 25, -4, 10, 0, -7, 3];
         let max = find_max(arr);
         println!("Max value: {:?}", max);
     }
    
     fn find_max(arr: &[i32]) -> Option<i32> {
         // Minimum split length
         const THRESHOLD: usize = 2;
    
         if arr.len() <= THRESHOLD {
             // Immediately quickcmp
             return arr.iter().cloned().max();
         }
    
         let mid = arr.len() / 2;
         let (left, right) = arr.split_at(mid);
    
         crossbeam::scope(|s| {
             // Created a thread for leftside
             let thread_l = s.spawn(|_| find_max(left));
             // Created a thread for rightside
             let thread_r = s.spawn(|_| find_max(right));
    
             // `join()` get the return value of the target thread <Option(i32)>, then `unwrap()` to get inner value <i32>
             let max_l = thread_l.join().unwrap();
             let max_r = thread_r.join().unwrap();
    
             max_l.max(max_r)
         }).unwrap()
     }

    crossbeam 用一个简单的方法保证了多线程的原子性。

    crossbeam 还有一个有界通道(bounded channel)功能,可以时间生产者-消费者模型,并通过多个 worker 线程对消息进行处理和转发。

    以下是示例:

     use crossbeam::channel::bounded;
    
     use std::thread;
     use std::time::Duration;
    
     fn main() {
         // tx1(生产者) -> rx1(worker)
         let (tx1, rx1) = bounded::<i32>(1);
         // tx2(worker) -> rx2(主线程)
         let (tx2, rx2) = bounded::<i32>(1);
         // `bounded(1)`表示通道容量为1,发送方在通道满时会阻塞等待,接收方在通道为空时会阻塞等待
    
         let n_msg = 5;
         let n_workers = 2;
    
         crossbeam::scope(|s| {
    
             // 生产者线程
             s.spawn(|_| {
                 for i in 0..n_msg {
                     // Circulately send integer 0..n.msg to tx1
                     println!("Sending {}", i);
                     tx1.send(i).unwrap();
                 }
    
                 // Close tx1 while finishing sending
                 drop(tx1);
             });
    
             // 多 worker 线程
             for _ in 0..n_workers {
                 // 注意此处是 rx1,表示 worker 接收生产者信息,从tx发送到主线程
                 let (tx, rx) = (tx2.clone(), rx1.clone());
    
                 // `move` refers to move tx&rx to new thread
                 s.spawn(move |_| {
                     // Simulate processing
                     thread::sleep(Duration::from_millis(500));
                     for msg in rx.iter() {
                         println!("Received {}", msg);
                         tx.send(msg * 2).unwrap();
                     }
                 });
             }
    
             // Close tx2 while finishing creating threads
             drop(tx2);
    
             // Then can recv msg processed by worker
             for msg in rx2.iter() {
                 println!("Final received {}", msg);
             }
    
         }).unwrap();
     }

    在两个线程之间也可传输数据:

     use std::{thread, time};
     use crossbeam::channel::unbounded;
    
     fn main() {
         let (tx, rx) = unbounded();
         let n_msgs = 5;
         crossbeam::scope(|s| {
             s.spawn(|_| {
                 for i in 0..n_msgs {
                     tx.send(i).unwrap();
                     thread::sleep(time::Duration::from_millis(100));
                 }
             });
         }).unwrap();
         for _ in 0..n_msgs {
             let msg = rx.recv().unwrap();
             println!("Received {}", msg);
         }
     }

    此处unbounded()意味着两个线程之间传输缓存是无上限的,也意味着发送方可以无等待发送数据。

  21. 安全的全局变量

    Rust 默认全局不可变(const定义),而lazy_static提供了可变的全局量,这个全局变量需要Mutex来定义以避免线程竞争引发错误。

     use lazy_static::lazy_static;
     use std::sync::Mutex;
    
     lazy_static! {
         static ref FRUIT: Mutex<Vec<String>> = Mutex::new(vec![]);
     }
    
     fn insert(fruit: &str) -> Result<(), Box <dyn std::error::Error>> {
         let mut db = FRUIT.lock().map_err(|_| {
             "Failed to acquire MutexGuard"
         })?;
         db.push(fruit.to_string());
         Ok(())
     }
    
     fn main() -> Result<(), Box<dyn std::error::Error>> {
         insert("Apple")?;
         insert("Banana")?;
         insert("Orange")?;
    
         {
    
             let db = FRUIT.lock().map_err(|_| {
                 "Failed to acquire MutexGuard"
             })?;
             db.iter().enumerate().for_each(|(i, fruit)| {
                 println!("Fruit {}: {}", i + 1, fruit);
             });
    
         }
    
         insert("Grape")?;
    
         Ok(())
     }

    不难发现,lazy_static!里声明了用Mutex<T>包裹的全局变量,声明方式:

     lazy_static! {
         static ref CONSTANT_NAME: Mutex<T> = Mutex::new(T::new());
     }

    使用全局变量时,用lock()方法获得锁并放在一个临时变量内安全使用,此临时变量即为锁的生命周期,生命周期范围内其他线程方法内不允许获得锁。

     {
         let mut data = CONSTANT_NAME.lock().map_err(|_| {
             "Could not get locker!"
         })?;
         /* 锁生命周期开始…… */
         println!("{:?}", data.unwrap());
     }
     /* 锁生命周期结束,此时其他线程可以获取锁 */

    同时要注意,map_err(|_| {})处理锁错误极为重要,若直接unwrap()或其他不当操作会引发panic!()

  22. 实例:对所有iso文件的 SHA256 值并发求和

     use num_cpus;
     use walkdir::WalkDir;
     use std::fs::File;
     use std::io::{BufReader, Error, Read};
     use std::path::Path;
     use threadpool::ThreadPool;
     use std::sync::mpsc::channel;
     use ring::digest::{Context, Digest, SHA256};
    
     // Verify the iso extension
     fn is_iso(entry: &Path) -> bool {
         match entry.extension() {
             Some(e) if e.to_string_lossy().to_lowercase() == "iso" => true,
             _ => false,
         }
     }
    
     fn compute_digest<P: AsRef<Path>>(filepath: P) -> Result<(Digest, P), Error> {
         let mut buf_reader = BufReader::new(File::open(&filepath)?);
         let mut context = Context::new(&SHA256);
         let mut buffer = [0; 1024];
    
         loop {
             let count = buf_reader.read(&mut buffer)?;
             if count == 0 {
                 break;
             }
             context.update(&buffer[..count]);
         }
    
         Ok((context.finish(), filepath))
     }
    
     fn main() -> Result<(), Box<dyn std::error::Error>> {
         let pool = ThreadPool::new(num_cpus::get());
         let (tx, rx) = channel();
    
         for entry in WalkDir::new(".")
             .follow_links(true)
             .into_iter()
             .filter_map(|e| {
                 e.ok()
             })
             .filter(|e| !e.path().is_dir() && is_iso(e.path())) {
                 let path = entry.path().to_owned();
                 let tx = tx.clone();
                 pool.execute(move || {
                     let digest = compute_digest(path);
                     tx.send(digest).expect("Couldn't send data!");
                 });
             }
    
         drop(tx);
    
         for t in rx.iter() {
             let (sha, path) = t?;
             println!("{:?}  {:?}", sha, path);
         }
    
         Ok(())
    
     }
    1. 使用的依赖库:

      • num_cpus:获取当前机器的 CPU 核心数,用来决定线程池大小。

      • walkdir:递归遍历目录,找到所有文件。

      • threadpool:创建固定大小的线程池,避免频繁创建/销毁线程。

      • std::sync::mpsc::channel:主线程与工作线程之间的消息通道。

      • ring::digest:提供 SHA‑256 哈希算法。

    2. 判断ISO文件的函数:

      • 检查文件扩展名是否为 "iso"(不区分大小写)。

      • 用于过滤遍历结果,只处理 ISO 文件。

    3. 计算 SHA‑256 摘要:

      • 打开文件并用 BufReader 包装,逐块读取(每次 1024 字节)。

      • 每读到一块数据,就更新 SHA‑256 的计算上下文。

      • 文件读完后,调用 context.finish() 得到最终的 Digest

      • 返回 (Digest, filepath),即哈希值和文件路径。

    4. 遍历目录并分派任务:

      • 遍历当前目录(包括子目录),跟随符号链接。

      • 过滤掉目录,只保留扩展名为 .iso 的文件。

      • 对每个文件:

        • 克隆一个 tx(因为多个线程要发送结果)。

        • 把任务提交给线程池:计算文件的 SHA‑256 摘要,并通过通道发送结果。

    重点内容:Threadpool 的使用方法

    Threadpool 用于创建固定大小的线程池,避免重复地增删线程。

    新建线程池:

    // Get the count of CPUs
    let count = num_cpus::get();
    // Create pool via `count`
    let pool = threadpool::Threadpool::new(count);

    使用线程池:

    // Create a channel to recv res
    let (tx, rx) = std::sync::mpsc::channel();
    
    for i in 0..count {
        let tx = tx.clone();
        pool.execute(move || {
            tx.send("Context...").expect("Fital error!");
        });
    }
    

    接收线程数据:

    loop {
        // Avoid `unwrap()` while releasing!
        let context = rx.recv().unwrap();
        println!("Received: {}", context);
    }
  23. 实例:将绘制分形的线程分派到线程池

     use std::sync::mpsc::channel;
     use threadpool::ThreadPool;
     use num::complex::Complex;
     use image::{ImageBuffer, Rgb};
    
     // 将强度值转换为 RGB 值的函数
     // 基于 http://www.efg2.com/Lab/ScienceAndEngineering/Spectra.htm
     fn wavelength_to_rgb(wavelength: u32) -> Rgb<u8> {
         let wave = wavelength as f32;
    
         let (r, g, b) = match wavelength {
             380..=439 => ((440. - wave) / (440. - 380.), 0.0, 1.0),
             440..=489 => (0.0, (wave - 440.) / (490. - 440.), 1.0),
             490..=509 => (0.0, 1.0, (510. - wave) / (510. - 490.)),
             510..=579 => ((wave - 510.) / (580. - 510.), 1.0, 0.0),
             580..=644 => (1.0, (645. - wave) / (645. - 580.), 0.0),
             645..=780 => (1.0, 0.0, 0.0),
             _ => (0.0, 0.0, 0.0),
         };
    
         let factor = match wavelength {
             380..=419 => 0.3 + 0.7 * (wave - 380.) / (420. - 380.),
             701..=780 => 0.3 + 0.7 * (780. - wave) / (780. - 700.),
             _ => 1.0,
         };
    
         let (r, g, b) = (normalize(r, factor), normalize(g, factor), normalize(b, factor));
         // 新标准不再使用`from_channels()`函数构建Rgb,直接投入slice即可
         Rgb([r, g, b])
     }
    
     // 将茱莉亚集距离映射为强度值
     fn julia(c: Complex<f32>, x: u32, y: u32, width: u32, height: u32, max_iter: u32) -> u32 {
         let width = width as f32;
         let height = height as f32;
    
         let mut z = Complex {
             // scale and translate the point to image coordinates
             re: 3.0 * (x as f32 - 0.5 * width) / width,
             im: 2.0 * (y as f32 - 0.5 * height) / height,
         };
    
         let mut i = 0;
         for t in 0..max_iter {
             if z.norm() >= 2.0 {
                 break;
             }
             z = z * z + c;
             i = t;
         }
         i
     }
    
     // 规格 RGB 颜色值范围内的强度值
     fn normalize(color: f32, factor: f32) -> u8 {
         ((color * factor).powf(0.8) * 255.) as u8
     }
    
     fn main() -> Result<(), Box<dyn std::error::Error>> {
         let (width, height) = (1920, 1080);
         let mut img = ImageBuffer::new(width, height);
         let iterations = 300;
    
         let c = Complex::new(-0.8, 0.156);
    
         let pool = ThreadPool::new(num_cpus::get());
         let (tx, rx) = channel();
    
         for y in 0..height {
             let tx = tx.clone();
             pool.execute(move || for x in 0..width {
                 let i = julia(c, x, y, width, height, iterations);
                 let wavelength = 380 + (i * 400 / iterations);
                 let pixel = wavelength_to_rgb(wavelength);
                 tx.send((x, y, pixel)).expect("Could not send data!");  
             });
         }
    
         for _ in 0..height {
             for _ in 0..width {
                 let (x, y, pixel) = rx.recv().unwrap();
                 img.put_pixel(x, y, pixel);
             }
         }
    
         img.save("julia.png")?;
         Ok(())
     }

    此实例解析了茱莉亚集的分型图像,操作了图像处理的方法。

  24. 使用rayon库并行改变数组中的元素

     use rayon::prelude::*;
    
     fn main() {
         let mut arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
         arr.par_iter_mut().for_each(|x| *x *= 2);
         println!("{:?}", arr);
     }

    此过程借用rayon::prelude::par_iter_mut()方法为任意可迭代数据类型创建并行可变迭代器,可以对数据进行并行的迭代计算。

    同样的,可以使用rayon::prelude::par_iter()方法创建并行不可变迭代器,其可以体现迭代作用:

     arr.par_iter().for_each(|x| println!("{}", x));

    输出类似于:

     2
     18
     12
     4
     8
     14
     16
     10
     6
     20
  25. 寻找任意符合匹配的元素

    使用rayon::prelude::par_iter().find_any()方法可以在并行迭代中寻找任意符合条件的元素,示例如下:

     use rayon::prelude::*;
    
     fn main() {
         let v = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    
         for _ in 0..10 {
             let f1 = v.par_iter().find_any(|&&x| x % 3 == 0);
             println!("Found any 3t number: {}", f1.unwrap_or(&-1));
         }
     }

    这个程序会在每次迭代中找到一个首先匹配的3的倍数并输出。输出应该类似于:

     Found any 3t number: 3
     Found any 3t number: 6
     Found any 3t number: 6
     Found any 3t number: 3
     Found any 3t number: 3
     Found any 3t number: 6
     Found any 3t number: 3
     Found any 3t number: 6
     Found any 3t number: 3
     Found any 3t number: 3

    注意:此操作是并行的,故每次运行结果可能不同,也不是所有符合条件的元素都会被找到。同样也不是找到向量或切片的第一个符合条件的元素。

  26. 对字符串的并行排序

    使用rayon::prelude::par_sort_unstable()方法可以对字符串进行并行排序,示例如下:

     use rayon::prelude::*;
     use rand::{Rng, rng};
     use rand_distr::Alphanumeric;
    
     fn main() {
         let mut vec = vec![String::new(); 100_000];
         vec.par_iter_mut().for_each(|s| {
             // Create a random source
             // References: No.6 Rust Candy
             let mut rng = rng();
             // Create a random string with 10 characters
             // Note: `as char` is necessary
             // &Alphanumeric gives latin letters and numbers
             *s = (0..10).map(|_| rng.sample(&Alphanumeric) as char).collect();
         });
         // Before sorting (Sample 10 strings)
         println!("{:?}", &vec[..10]);
         // Parallel sort
         vec.par_sort_unstable();
         // After sorting (Sample 10 strings)
         println!("{:?}", &vec[..10]);
     }

    此程序首先创建一个包含100000个随机字符串的向量,然后对其进行并行排序。输出类似于:

     ["MB0xhNbUVS", "qazgyPzQY3", "4bb64VNtJM", "9kzbrmKRhy", "pUD9jsRaUo", "vVz52gYs5I", "M6kvAyaHsa", "G1DFfw7HlS", "fLXSfc6F9E", "PT0Da7DSFL"]
     ["000lyuHNbg", "003ZFXzVMv", "004omFlhZD", "00Ai1GEbQK", "00CXDh30dX", "00DCg9fXxl", "00IDlIu6B9", "00K1anSifr", "00MbufM2cj", "00SYb6E6tL"]

    可以使用rayon::prelude::par_sort()替代,但是在不需要区分键值对等排序时,rayon::prelude::par_sort_unstable()会更快且节省内存。

  27. f32::EPSILONf64::EPSILON分别表示32位和64位浮点数的最小正数差值,可以用来判断两个浮点数是否近似相等。

     fn main() {
         let a = 0.1_f32 + 0.2_f32;
         let b = 0.3_f32;
    
         if (a - b).abs() < f32::EPSILON {
             println!("a and b are approximately equal");
         } else {
             println!("a and b are not equal");
         }
     }

    输出:

     a and b are approximately equal
  28. 并行生成 JPG 图像的缩略图

    使用rayon库可以并行处理图像文件以生成缩略图,示例如下:

     use std::error::Error;
     use std::path::Path;
     use std::fs::create_dir_all;
    
     use glob::{glob_with, MatchOptions};
     use rayon::prelude::*;
     use image::imageops::FilterType;
    
     fn make_thumbnail<PA, PB>(original: PA, thumb_dir: PB, longest_edge: u32) -> Result<(), Box<dyn Error + Send + Sync>>
     where
         PA: AsRef<Path>,
         PB: AsRef<Path>,
     {
         // 打开原始图像
         let img = image::open(original.as_ref())?;
         // 构建缩略图文件路径标准类型`PathBuf`
         let file_path = thumb_dir.as_ref().join(original.as_ref().file_name().unwrap());
    
         // 调整图像大小并保存缩略图
         img.resize(longest_edge, longest_edge, FilterType::Nearest)
             .save(file_path)?;
         Ok(())
     }
    
     fn main() -> Result<(), Box<dyn Error>> {
         // 匹配方式:默认(区分大小写)
         let options: MatchOptions = Default::default();
         // 获取当前目录下所有jpg文件
         let files: Vec<_> = glob_with("*.jpg", options)?
             // 丢弃不可读的文件项
             .filter_map(|x| x.ok())
             .collect();
             // 返回Vec
    <PathBuf>
    
         if files.len() == 0 {
             return Err("No jpg files found in the current directory.".into());
         }
    
         let thumb_dir = "thumbnails";
         // 确保缩略图目录存在
         create_dir_all(thumb_dir)?;
    
         println!("Processing {} files...", files.len());
    
         // 对files创建并行迭代器,生成缩略图
         let image_failures: Vec<_> = files.par_iter()
             // 匹配每个文件路径
             .map(|path| {
                 // 为每个路径生成缩略图
                 make_thumbnail(path, thumb_dir, 300)
                     .map_err(|e| format!("{}: {}", path.display(), e))
             })
             // 收集错误信息
             .filter_map(|x| x.err())
             .collect();
    
         // 输出失败的文件信息
         image_failures.iter().for_each(|e| {
             println!("Failed to process image: {}", e);
         });
    
         Ok(())
     }

    以下依赖的作用:

    • glob: 文件模式匹配,用glob::glob_with("*.jpg", options)获取当前目录下所有.jpg文件的路径。用于批量的文件查找,搭配过滤不可读项。
    • rayon: 并行处理库,使用par_iter()对文件路径进行并行迭代处理。提高处理大量图像文件时的效率。
    • image: 图像处理库,使用image::open()打开图像文件,DynamicImage::resize()调整图像大小,DynamicImage::save()保存缩略图。实现图像的读取、处理和保存功能。总体实现逻辑简略过程:

        let path = "example.jpg";
        // The path the thumbnail will be saved to
        let thumb_dir = "thumbnails";
        // image open
        // `as_ref()` to convert AsRef<Path> to &Path
        let image = image::open(path).as_ref()?;
        let file_path = thumb_dir.as_ref().join(path.as_ref().file_name().unwrap());
      
        image.resize(300, 300, FilterType::Nearest)
            .save(file_path)?;

      AsRef<T> trait 用于将类型转换为引用类型,这里用于将路径参数转换为&Path类型,方便图像处理函数使用。其他支持的数据类型(如:String``[u8])被其包装后也被引用为&Path类型。

    • std::fs::create_dir_all: 创建目录函数,确保缩略图保存目录存在。
  29. 28的错误收集方法的解释

     let image_failures: Vec<_> = file
         .par_iter()
         .map(|path| -> Return<(), String>) {
             make_thumbnail(path, thumb_dir, 300).map_err(|e| format!("Failed to process image {}: {}", path.display(), e))
         }
         .filter_map(|x| x.err())
         .collect();

    此处filter_map用于过滤错误,过滤对象是迭代器map后返回的Result<T, E>(此处为Map<Self, F>),将错误信息收集到Vec<String>中。

  30. 28定义方法make_thumbnail<PA, PB>()的解释。

     fn make_thumbnail<PA, PB>(
         original: PA,
         thumb_dir: PB,
         longest_edge: u32,
     ) -> Result<(), Box<dyn Error + Send + Sync>>
     where
         PA: AsRef<Path>,
         PB: AsRef<Path>,
     {
         let img = image::open(original.as_ref())?;
         let file_path = thumb_dir
             .as_ref()
             .join(original
                 .as_ref()
                 .file_name()
                 .unwrap()
                 );
    
         img.resize(
             longest_edge, 
             longest_edge, 
             FilterType::Nearest
             )
             .save(file_path)?;
         Ok(())
     }

    此处定义了泛型函数make_thumbnail,定义时将使用的泛型放入<_>内包装,在参数中选择泛型使用,再在where关键词内定义泛型作用范围为“实现了某种方法的类型”,如:

     where
         PA: AsRef<Path>,
         PB: AsRef<Path>,

    此处是指定泛型PA, PB为实现了AsRef<Path> trait 的类型(如:&strPathBufString等)。

    什么是 trait

    • trait 是 Rust 中定义行为(行为契约、接口)的语言构件。它声明一组方法签名(可以包含默认实现),任何实现了该 trait 的类型都承诺提供这些行为。
    • 可以把 trait 理解为其他语言里的“接口”(Java 的 interface、Go 的interface),但在 Rust 中它与泛型、类型系统结合得更紧密。
  31. String类型的几个有用的方法:

    • push_str(&str):在字符串末尾追加一个字符串切片。

        let mut s = String::from("Hello");
        s.push_str(", world!");
        println!("{}", s); // 输出 "Hello, world!"
    • push(char):在字符串末尾追加一个字符。

        let mut s = String::from("Hello");
        s.push('!');
        println!("{}", s); // 输出 "Hello!"
    • replace(&str, &str) -> String:返回一个新字符串,其中所有匹配的子串都被替换为另一个字符串。

        let s = String::from("Hello, world!");
        let new_s = s.replace("world", "Rust");
        println!("{}", new_s); // 输出 "Hello, Rust!"
    • split_whitespace() -> SplitWhitespace:返回一个迭代器,按空白字符分割字符串。

        let s = String::from("Hello,   world! This is Rust.");
        for word in s.split_whitespace() {
            println!("{}", word);
        }
    • trim_ascii() -> &str:返回一个字符串切片,去除字符串开头和结尾的 ASCII 空白字符。

        let s = String::from("   Hello, world!   ");
        let trimmed = s.trim_ascii();
        println!("'{}'", trimmed); // 输出 "'Hello, world!'"
    • split_off(usize) -> String:从指定索引处分割字符串,返回分割出的部分,并将原字符串截断。

        let mut s = String::from("Hello, world!");
        let tail = s.split_off(7);
        println!("{}", s);    // 输出 "Hello, "
        println!("{}", tail); // 输出 "world!"
  32. 计算文件 SHA-256 摘要

     use data_encoding::HEXUPPER;
     use ring::digest::{Context, Digest, SHA256};
     use std::fs::File;
     use std::io::{BufReader, Read, Write};
    
     fn sha256_digest<R: Read>(mut reader: R) -> Result<Digest, Box<dyn std::error::Error>> {
         // Create a SHA256 context session
         let mut context = Context::new(&SHA256);
         // Set a buffer to read file
         let mut buffer = [0; 1024];
    
         loop {
             // Read file in chunks of 1024 bytes
             let count = reader.read(&mut buffer)?;
             if count == 0 {
                 break;   
             }
             // Update context with read bytes
             context.update(&buffer[..count]);
         }
    
         Ok(context.finish())
     }
    
     fn main() -> Result<(), Box<dyn std::error::Error>> {
         let path = "./example.txt";
    
         // Create and write to the file
         let mut output = File::create(path)?;
         write!(output, "Hello, world?")?;
    
         // Read the file and compute its SHA-256 digest
         let input = File::open(path)?;
         let reader = BufReader::new(input);
         let digest: Digest = sha256_digest(reader)?;
    
         println!("SHA-256 Digest: {}", HEXUPPER.encode(digest.as_ref()));
    
         Ok(())
     }

    此操作在当前目录下创建一个example.txt文件,写入字符串Hello, world?,然后计算该文件的 SHA-256 摘要并以十六进制大写格式输出。

  33. 使用 HMAC 摘要对消息进行签名和认证

     use ring::{hmac, rand};
     use ring::rand::SecureRandom;
     use ring::error::Unspecified;
    
     fn main() -> Result<(), Unspecified> {
         let mut key_value = [0u8; 48];
         // Create a secure random number generator
         let rng = rand::SystemRandom::new();
         // Fill the key_value with random bytes
         rng.fill(&mut key_value)?;
         // Create HMAC key
         let key = hmac::Key::new(hmac::HMAC_SHA256, &key_value);
    
         let message = "Legitimate and important message";
         // Sign the message
         let signature: hmac::Tag = hmac::sign(&key, message.as_bytes());
         // Verify the signature
         hmac::verify(&key, message.as_bytes(), signature.as_ref())?;
    
         println!("Message: {}", message);
         println!("HMAC: {:?}", signature);
         println!("Key: {:?}", key_value);
         println!("HMAC verification succeeded.");
    
         Ok(())
     }
  34. 使用 PBKDF2 对密码进行加密和哈希运算

     use data_encoding::HEXUPPER;
     use ring::error::Unspecified;
     use ring::rand::SecureRandom;
     use ring::{digest, pbkdf2, rand};
     use std::num::NonZeroU32;
    
     fn main() -> Result<(), Unspecified> {
         // Define SHA512 Credential Length
         const CREDENTIAL_LEN: usize = digest::SHA512_OUTPUT_LEN;
         // Create a non-zero iteration count
         let n_iter = NonZeroU32::new(100_000).unwrap();
         // Create a secure random number generator
         let rng = rand::SystemRandom::new();
    
         // Define salt, length = CREDENTIAL_LEN
         let mut salt = [0u8; CREDENTIAL_LEN];
         // Fill salt with secure random bytes
         rng.fill(&mut salt)?;
    
         let password = "super_secret_password";
         let mut pbkdf2_hash = [0u8; CREDENTIAL_LEN];
         // `derive()` will fill `pbkdf2_hash` with the derived key
         // parameters: algorithm, iteration count, salt, password, output buffer
         pbkdf2::derive(
             pbkdf2::PBKDF2_HMAC_SHA512, 
             n_iter, 
             &salt, 
             password.as_bytes(), 
             &mut pbkdf2_hash
         );
    
         println!("Salt: {}", HEXUPPER.encode(&salt));
         println!("PBKDF2 Hash: {}", HEXUPPER.encode(&pbkdf2_hash));
    
         // Verify the password
         // Input the real password to compare
         let should_succeed = pbkdf2::verify(
             pbkdf2::PBKDF2_HMAC_SHA512, 
             n_iter, 
             &salt, 
             password.as_bytes(), 
             &pbkdf2_hash
         );
         let wrong_password = "wrong_password";
         // Input a wrong password to compare
         let should_fail = pbkdf2::verify(
             pbkdf2::PBKDF2_HMAC_SHA512, 
             n_iter, 
             &salt, 
             wrong_password.as_bytes(), 
             &pbkdf2_hash
         );
    
         assert!(should_succeed.is_ok());
         assert!(should_fail.is_err());
    
         Ok(())
     }

    对某些库的说明:

    • std::num::NonZeroU32:用于创建一个非零的 u32 类型值,确保迭代次数不能为零。NonZeroU32类型规定了其值必须大于零,这在密码学中很重要,因为迭代次数为零会导致安全性问题。在新建NonZeroU32时不要使用new_unchecked(),而应使用new()方法以确保值不为零,否则UB。

      为什么迭代次数不能为零?

      • 迭代次数为零意味着密码哈希函数不会进行任何计算,这会导致生成的哈希值与原始密码相同,从而极大地降低了密码存储的安全性。

      NonZeroU32Option<T>包装后的内存占用比较:

      • u32 的大小:4 字节。
      • std::num::NonZeroU32 的大小:4 字节(底层就是 u32)。
      • Option:4 字节(利用了 NonZeroU32 的“niche(值为 0)”来表示 None)。
      • Option:通常在 64 位目标上是 8 字节(因为 u32 没有可用的 niche,枚举需要额外空间);在 32 位目标上常为 4 字节。

        fn main() {
            use std::mem::size_of;
            println!("u32: {}", size_of::<u32>());
            println!("NonZeroU32: {}", size_of::<std::num::NonZeroU32>());
            println!("Option<u32>: {}", size_of::<Option<u32>>());
            println!("Option<NonZeroU32>: {}", size_of::<Option<std::num::NonZeroU32>>());
        }

        输出(在 64 位系统上):

        u32: 4
        NonZeroU32: 4
        Option<u32>: 8
        Option<NonZeroU32>: 4

        可见,Option<NonZeroU32> 的内存占用更小,因为它利用了 NonZeroU32 的“niche”来表示 None,而不需要额外的空间来存储枚举标签。此举可以优化内存使用,尤其是在需要大量存储可选非零整数的场景中。

    • ring::error::Unspecified:表示可能发生的错误类型,用于处理密码学操作中的错误。此错误类型与Box<dyn std::error::Error>类似,但更具体地用于ring库中的密码学操作错误处理。详细地说,Unspecified类型表示密码学操作失败时的错误,但不提供具体的错误信息。这种设计是为了避免泄露敏感信息,同时仍然允许调用者知道操作失败了。

    • ring::rand::SecureRandom:用于生成安全的随机数,确保密码学操作中的随机性。

      如何确保安全的随机性呢?

      • SecureRandom trait 定义了生成安全随机数的方法,确保生成的随机数具有足够的熵和不可预测性。
      • SystemRandom 结构体实现了 SecureRandom trait,利用操作系统提供的随机数生成器(如 /dev/urandom 或 Windows 的 CryptGenRandom)来生成高质量的随机数。
    • ring::pbkdf2:提供 PBKDF2 密码哈希算法的实现,用于安全地存储密码。

      什么是 PBKDF2 密码哈希算法?

      PBKDF2(Password-Based Key Derivation Function 2)是一种基于密码的密钥派生函数,用于将密码转换为加密密钥。

      它通过多次迭代哈希函数(如 HMAC-SHA256 或 HMAC-SHA512)来增加密码破解的难度,从而提高密码存储的安全性。

    • ring::digest:提供哈希函数的实现,如 SHA-256 和 SHA-512,用于密码学操作中的数据完整性验证。

  35. 定义并操作位域风格的类型(实际上就是对比特位的操作)

     use bitflags::bitflags;
     use std::fmt;
    
     bitflags! {
         // 定义派生了 Clone, Copy, Debug, PartialEq, Eq trait 的 MyFlags 结构体,以便可以复用
         #[derive(Clone, Copy, Debug, PartialEq, Eq)]
         struct MyFlags: u32 {
             const FLAG_A = 0b001;
             const FLAG_B = 0b010;
             const FLAG_C = 0b100;
             const FLAG_ABC = Self::FLAG_A.bits() | Self::FLAG_B.bits() | Self::FLAG_C.bits();
         }
     }
    
     impl MyFlags {
         // 对 MyFlags 结构体添加一个清除所有标志的方法
         pub fn clear(&mut self) {
             *self = MyFlags::empty();
         }
     }
    
     impl fmt::Display for MyFlags {
         // 定义了 MyFlags 结构体的显示格式化方法,定义了这个`std::fmt::Display` trait 后,可以使用`{}`格式化输出 MyFlags 实例
         fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
             write!(f, "{:032b}", self.bits())
         }
     }
    
     fn main() {
         // e1 = FLAG_A | FLAG_C = 0b101
         let e1 = MyFlags::FLAG_A | MyFlags::FLAG_C;
         // e2 = FLAG_B | FLAG_C = 0b110
         let e2 = MyFlags::FLAG_B | MyFlags::FLAG_C;
    
         // e1 | e2 = FLAG_ABC = 0b111
         assert_eq!(e1 | e2, MyFlags::FLAG_ABC);
         // e1 & e2 = FLAG_C = 0b100
         assert_eq!(e1 & e2, MyFlags::FLAG_C);
    
         let mut flags = MyFlags::FLAG_ABC;
         // 输出:00000000000000000000000000000111
         println!("{}", flags);
         flags.clear();
         // 输出:00000000000000000000000000000000
         println!("{}", flags);
         // 输出:MyFlags(FLAG_B),即 Debug 格式。
         // 这样输出可以清晰地看到当前启用的标志而非一堆二进制开关。
         println!("{:?}", MyFlags::FLAG_B);
     }

    应用场景:

    • 配置选项:比如一个网络库的 socket,可以有 NONBLOCKINGREUSEADDRKEEPALIVE 等选项,用位域组合。
    • 权限控制:文件系统权限(读/写/执行)就是典型的位域。
    • 硬件寄存器操作:在嵌入式开发里,寄存器的每一位都有特殊意义,用 bitflags! 可以更安全地操作。
  36. 创建 SQLite 数据库

     use rusqlite::{Connection, Result};
    
     fn main() -> Result<(), Box<dyn std::error::Error>> {
         let conn = Connection::open("cats.db")?;
    
         conn.execute(
             "create table if not exists cat_colors (
                 id integer primary key,
                 name text not null unique
             )",
             [],
         )?;
    
         conn.execute(
             "create table if not exists cats (
                 id integer primary key,
                 name text not null,
                 color_id integer not null references cat_colors(id)
             )", 
             [],
         )?;
    
         Ok(())
     }

    实现过程非常简单,主要是使用rusqlite库的Connection::open()方法创建或打开数据库文件,然后使用execute()方法执行 SQL 语句创建表格。

  37. 数据的插入和查询

     use rusqlite::{Connection, Result};
     use std::collections::HashMap;
    
     // 派生 Debug trait 以便打印输出
     #[derive(Debug)]
     #[allow(dead_code)]
     struct Cat {
         name: String,
         color: String,
     }
    
     fn main() -> Result<(), Box<dyn std::error::Error>> {
         // 新建或打开数据库文件
         let conn = Connection::open("cats.db")?;
         // 建立数据库表格
         conn.execute(
             "CREATE TABLE IF NOT EXISTS cat_colors (
                 id INTEGER PRIMARY KEY,
                 name TEXT NOT NULL UNIQUE
             )",
             [],
         )?;
         conn.execute(
             "CREATE TABLE IF NOT EXISTS cats (
                 id INTEGER PRIMARY KEY,
                 name TEXT NOT NULL,
                 color_id INTEGER NOT NULL REFERENCES cat_colors(id)
             )",
             [],
         )?;
    
         // 新建 HashMap 存储颜色和猫名的对应关系
         let mut cat_colors = HashMap::new();
         cat_colors.insert(String::from("Blue"), vec!["Anon", "Soyo"]);
         cat_colors.insert(String::from("Black"), vec!["Tomori", "Rikki"]);
         cat_colors.insert(String::from("White"), vec!["Rana"]);
    
         // 插入数据到表格中
         for (color, catnames) in &cat_colors {
             // 插入颜色键值对
             conn.execute(
                 "INSERT INTO cat_colors (name) values (?1)", 
                 &[&color.to_string()],
             )?;
             // 获取插入的最后一个id值
             let last_id = conn.last_insert_rowid().to_string();
    
             // 插入猫名和颜色id的对应关系(指定最后插入颜色的猫名)
             for cat in catnames {
                 conn.execute(
                     "INSERT INTO cats (name, color_id) values (?1, ?2)",
                     &[&cat.to_string(), &last_id],
                 )?;
             }
         }
    
         // 查询并输出结果
         let mut stmt = conn.prepare(
             "SELECT c.name, cc.name from cats c
             INNER JOIN cat_colors cc
             ON cc.id = c.color_id;"
         )?;
    
         let cats = stmt.query_map([], |row| {
             Ok(Cat {
                 name: row.get(0)?,
                 color: row.get(1)?,
             })
         })?;
    
         for cat in cats {
             println!("Found cat: {:?}", cat?);
         }
    
         Ok(())
     }

    注意:数据库的操作必须严格遵守数据库操作原则,否则会引发程序错误退出。

    此处程序实现了对cats.db数据库的插入和查询操作,首先定义了一个Cat结构体用于存储查询结果,然后创建或打开数据库文件,创建表格,插入数据,最后执行查询并输出结果。若不创建表格,程序将发生错误。创建了再写入似乎也会出错,不是很了解SQL方面的技术。

    对 SQLite 数据库的指令解释:

    • 创建一个名为cat_colors的表格(如果不存在的话),包含两个字段:id(整数类型,主键)和name(文本类型,不能为空且唯一)。

      CREATE TABLE IF NOT EXISTS cat_colors (
          id INTEGER PRIMARY KEY,
          name TEXT NOT NULL UNIQUE
      )
    • 创建一个名为cats的表格(如果不存在的话),包含三个字段:id(整数类型,主键)、name(文本类型,不能为空)和color_id(整数类型,不能为空,引用cat_colors表格的id字段)。

      CREATE TABLE IF NOT EXISTS cats (
          id INTEGER PRIMARY KEY,
          name TEXT NOT NULL,
          color_id INTEGER NOT NULL REFERENCES cat_colors(id)
      )
    • cat_colors表格中插入一条记录,?1是一个占位符,表示要插入的颜色名称。

      INSERT INTO cat_colors (name) values (?1)
    • cats表格中插入一条记录,?1?2是占位符,分别表示要插入的猫名和颜色ID。

      INSERT INTO cats (name, color_id) values (?1, ?2)
    • 执行一个查询,选择cats表格中的猫名(c.name)和cat_colors表格中的颜色名称(cc.name),通过内连接(INNER JOIN)将两个表格连接起来,连接条件是cat_colors表格的id字段等于cats表格的color_id字段。

      SELECT c.name, cc.name from cats c
      INNER JOIN cat_colors cc
      ON cc.id = c.color_id;

    为何用ccc作为表格别名?

    • ccc是对表格catscat_colors的简短别名,方便在查询中引用字段,避免重复书写完整表格名称,提高代码可读性。
    • 这种别名的使用在 SQL 查询中很常见,尤其是在涉及多个表格连接时,可以使查询语句更简洁明了。
    • 若有重复别名,会引发 SQL 语法错误,需确保别名唯一。
  38. 事务处理

     use rusqlite::{Connection, Result};
    
     fn main() -> Result<(), Box<dyn std::error::Error>> {
         let mut conn = Connection::open("cats.db")?;
    
         successful_tx(&mut conn)?;
    
         let count_before: i32 = conn.query_row("SELECT COUNT(*) FROM cat_colors", [], |r| r.get(0))?;
         println!("Count before rolled back transaction: {}", count_before);
    
         let res = rolled_back_tx(&mut conn);
         println!("Transaction result: {:?}", res);
    
         let count_after: i32 = conn.query_row("SELECT COUNT(*) FROM cat_colors", [], |r| r.get(0))?;
         println!("Count after rolled back transaction: {}", count_after);
    
         Ok(())
     }
    
     fn successful_tx(conn: &mut Connection) -> Result<(), Box<dyn std::error::Error>> {
         let tx = conn.transaction()?;
    
         // 先删除引用表中(cats)的数据,或者确保没有关联数据
         tx.execute("DELETE FROM cats", [])?;
         tx.execute("DELETE FROM cat_colors", [])?;
    
         tx.execute(
             "INSERT INTO cat_colors (name) VALUES (?1)", 
             ["lavender"]
         )?;
         tx.execute(
             "INSERT INTO cat_colors (name) VALUES (?1)", 
             ["purple"]
         )?;
    
         tx.commit()?;
    
         println!("Successful transaction committed.");
         Ok(())
     }
    
     fn rolled_back_tx(conn: &mut Connection) -> Result<(), Box<dyn std::error::Error>> {
         let tx = conn.transaction()?;
         // 此处操作由于事务回滚也无作用,最后表没有`mint`
         tx.execute(
             "INSERT INTO cat_colors (name) VALUES (?1)", 
             ["mint"]
         )?;
         // 这里故意插入重复的 "lavender" 以触发唯一性约束失败并回滚
         tx.execute(
             "INSERT INTO cat_colors (name) VALUES (?1)", 
             ["lavender"]
         )?;
    
         tx.commit()?;
    
         Ok(())
     }

    对于表cat_colorsname字段被定义为唯一(UNIQUE),因此尝试插入重复的颜色名称会违反唯一性约束,导致事务失败并回滚。

    对事务回滚的解释:

    • rolled_back_tx函数中,尝试插入重复的颜色名称"lavender",这违反了cat_colors表格中name字段的唯一性约束。
    • 当执行到插入重复记录的语句时,数据库会抛出错误,导致事务无法成功提交。
    • 由于事务未能成功提交,所有在该事务中执行的操作都会被回滚,数据库状态恢复到事务开始之前的状态。
  39. 测量运行时间

     use std::time::Instant;
     use std::time::Duration;
     use std::thread::sleep;
    
     fn main() {
         let start = Instant::now();
    
         // 模拟一些工作负载
         sleep(Duration::from_secs(2));
    
         let duration = start.elapsed();
         println!("代码块运行时间: {:?}", duration);
     }

    此代码使用了elapsed()方法来测量从Instant::now()调用到当前时间点之间经过的时间。输出类似于:

     代码块运行时间: 2.0004369s
  40. 执行日期检查和时间计算

     use chrono::{DateTime, Duration, TimeDelta, Utc};
    
     fn day_earlier(date_time: DateTime<Utc>) -> Option<DateTime<Utc>> {
         date_time.checked_sub_signed(Duration::days(1))
     }
    
     fn main() {
         let now = Utc::now();
         println!("{}", now);
    
         // 计算三周后减一天的时间
         let almods_three_weeks_from_now = now.checked_add_signed(Duration::weeks(2))
             .and_then(|in_2weeks| {
                 in_2weeks.checked_add_signed(Duration::weeks(1))
             })
             .and_then(day_earlier);
    
         // 处理可能的溢出
         match almods_three_weeks_from_now {
             Some(dt) => println!("三周后减一天的时间是: {}", dt),
             None => println!("计算时间时发生溢出"),
         }
    
         // 故意制造一个溢出
         match now.checked_add_signed(TimeDelta::MAX) {
             Some(x) => println!("计算结果: {}", x),
             None => println!("出现了问题!"),
         }
     }
  41. 时间时区的转换

     use chrono::{FixedOffset, Local, Utc, TimeZone};
    
     fn main() {
         let local_time = Local::now();
         let utc_time = &Utc.from_utc_datetime(&local_time.naive_utc());
         let china_time = FixedOffset::east_opt(8 * 3600);
         let rio_time = FixedOffset::west_opt(2 * 3600);
    
         println!("本地时间: {}", local_time);
         println!("UTC时间: {}", utc_time);
         if let Some(china_tz) = china_time {
             let china_dt = local_time.with_timezone(&china_tz);
             println!("中国时间: {}", china_dt);
         }
         if let Some(rio_tz) = rio_time {
             let rio_dt = local_time.with_timezone(&rio_tz);
             println!("里约热内卢时间: {}", rio_dt);
         }
     }

    以上代码展示了如何使用chrono库进行时间的时区转换。首先获取本地时间,然后将其转换为UTC时间。接着,定义了中国和里约热内卢的时区偏移,并将本地时间转换为这两个时区的时间。最后打印出各个时区的时间。

  42. 检查日期和时间

     use chrono::{Datelike, Timelike, Utc};
    
     fn main() {
         let now = Utc::now();
    
         let (is_pm, hour) = now.hour12();
         println!(
             "Current time: {:02}:{:02}:{:02} {}",
             hour,
             now.minute(),
             now.second(),
             if is_pm { "PM" } else { "AM" }
         );
         println!(
             "And there have been {} secs since the midnight!",
             now.num_seconds_from_midnight()
         );
    
         let (is_common_era, year) = now.year_ce();
         println!(
             "Current date: {:02}-{:02}-{} {}",
             now.day(),
             now.month(),
             year,
             if is_common_era { "CE" } else { "BCE" }
         );
         println!(
             "And the Common Era began {} days ago!",
             now.num_days_from_ce()
         )
     }

    Timelike trait 提供了对时间组件(如小时、分钟、秒)的访问方法,而 Datelike trait 则提供了对日期组件(如年、月、日)的访问方法。

  43. 日期与 Unix 时间戳的转换

     use chrono::{NaiveDate, DateTime};
    
     fn main() {
         let date_time = NaiveDate::from_ymd_opt(2026, 1, 9)
             .unwrap()
             .and_hms_opt(11, 45, 14)
             .unwrap();
    
         println!("Constructed date and time: {}", date_time);
    
         let date_time_after_a_billion_seconds = DateTime::from_timestamp(1_000_000_000, 0).unwrap();
    
         println!(
             "Date and time after a billion seconds since the Unix epoch: {}",
             date_time_after_a_billion_seconds
         );
     }
  44. 日期与时间的格式化表示

     use chrono::Utc;
    
     fn main() {
         let now = Utc::now();
    
         println!("UTC now is: {}", now);
         println!("UTC now in RFC 2822 format: {}", now.to_rfc2822());
         println!("UTC now in RFC 3339 format: {}", now.to_rfc3339());
         println!("UTC now in a custom format: {}", now.format("%Y-%m-%d %H:%M:%S"));
         println!("UTC now in a custom format: {}", now.format("%a %b %e %T %Y"));
     }

    输出类似于:

     UTC now is: 2026-01-09 03:01:07.562204200 UTC
     UTC now in RFC 2822 format: Fri, 9 Jan 2026 03:01:07 +0000
     UTC now in RFC 3339 format: 2026-01-09T03:01:07.562204200+00:00
     UTC now in a custom format: 2026-01-09 03:01:07
     UTC now in a custom format: Fri Jan  9 03:01:07 2026
  45. 将字符串解析为 DateTime 结构体

     use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime};
     use chrono::format::ParseError;
    
     fn main() -> Result<(), ParseError> {
         let rfc2822 = DateTime::parse_from_rfc2822("Tue, 1 Jul 2003 10:52:37 +0200")?;
         println!("{}", rfc2822);
    
         let rfc3339 = DateTime::parse_from_rfc3339("1996-12-19T16:39:57-08:00")?;
         println!("{}", rfc3339);
    
         let custom = DateTime::parse_from_str("5.8.1994 8:00 am +0000", "%d.%m.%Y %H:%M %P %z")?;
         println!("{}", custom);
    
         let time_only = NaiveTime::parse_from_str("23:56:04", "%H:%M:%S")?;
         println!("{}", time_only);
    
         let date_only = NaiveDate::parse_from_str("2015-09-05", "%Y-%m-%d")?;
         println!("{}", date_only);
    
         let no_timezone = NaiveDateTime::parse_from_str("2015-09-05 23:56:04", "%Y-%m-%d %H:%M:%S")?;
         println!("{}", no_timezone);
    
         Ok(())
     }
  46. 日志信息记录到控制台

     fn main() {
         log::debug!("This is a debug message");
         env_logger::init();
         log::info!("This is an info message");
         log::warn!("This is a warning message");
         log::debug!("This is another debug message");
         log::error!("This is an error message");
     }

    设置环境变量:

     $env:RUST_LOG="debug"

    输出类似于:

     [2026-01-09T03:15:42Z INFO  log] This is an info message
     [2026-01-09T03:15:42Z WARN  log] This is a warning message
     [2026-01-09T03:15:42Z DEBUG log] This is another debug message
     [2026-01-09T03:15:42Z ERROR log] This is an error message

    默认情况下,日志级别为error,因此debug级别的日志不会被输出。通过设置环境变量RUST_LOG="debug",可以启用debug级别的日志输出。

  47. 记录错误日志到控制台

     fn exe_query(query: &str) -> Result<(), &'static str> {
         println!("Executing query: {}", query);
         Err("Query execution failed")
     }
    
     fn main() {
         env_logger::init();
    
         if let Err(e) = exe_query("SELECT * FROM users") {
             log::error!("Error executing query: {}", e);
         }
     }

    返回错误发生时可以记录错误日志,输出类似于:

     [2026-01-09T08:35:01Z ERROR log] Error executing query: Query execution failed
  48. 用标准输出替换标准错误

     use env_logger::{Builder, Target};
    
     fn main() {
         Builder::new()
             .target(Target::Stdout)
             .init();
    
         log::error!("This is an error message printed to stdout");
     }

    此处调用env_logger::Builder::target(Target::Stdout)方法将日志输出目标设置为标准输出(stdout),从而使错误日志也被打印到标准输出流中。

    标准输出和标准错误的区别

    项目 标准输出 (stdout) 标准错误 (stderr)
    用途 输出正常结果,如命令执行结果 输出错误信息或警告
    文件描述符 1 2
    默认显示位置 终端屏幕 终端屏幕
    重定向方式 >1> 2>
    常见场景 ls显示目录内容 ls /不存在目录 显示错误信息
  49. 使用自定义日志记录器记录信息

     use log::{Record, Level, Metadata, LevelFilter, SetLoggerError};
    
     static CONSOLE_LOGGER: ConsoleLogger = ConsoleLogger;
    
     struct ConsoleLogger;
    
     // 定义日志记录器实现
     impl log::Log for ConsoleLogger {
         // 设置日志级别过滤
         fn enabled(&self, metadata: &Metadata) -> bool {
             metadata.level() <= Level::Info
         }
    
         // 处理日志记录
         fn log(&self, record: &Record) {
             if self.enabled(record.metadata()) {
                 println!("{} - {}", record.level(), record.args());
             }
         }
    
         // 刷新日志(此处为空实现)
         fn flush(&self) {
    
         }
     }
    
     fn main() -> Result<(), SetLoggerError> {
         log::set_logger(&CONSOLE_LOGGER)?;
    
         log::set_max_level(LevelFilter::Info);
    
         log::error!("This is an error message");
         log::warn!("This is a warning message");
         log::info!("This is an info message");
         log::debug!("This is a debug message that will not be shown");
    
         Ok(())
     }

    本实例通过新建一个空结构体作为日志记录器,并实现log::Log trait 来定义日志的处理方式。enabled方法用于过滤日志级别,log方法负责实际的日志输出,而flush方法在此处为空实现。最后,在main函数中设置了自定义日志记录器并指定了最大日志级别为Info,从而只输出Info及以上级别的日志信息。

  50. 记录到 Unix 系统日志

     #[cfg(target_os = "linux")]
     #[cfg(target_os = "linux")]
     use syslog::{Facility, Error};
    
     #[cfg(target_os = "linux")]
     fn main() -> Result<(), Error> {
         syslog::init(Facility::LOG_USER,
                     log::LevelFilter::Debug,
                     Some("My app name"))?;
         log::debug!("this is a debug {}", "message");
         log::error!("this is an error!");
         Ok(())
     }
    
     #[cfg(not(target_os = "linux"))]
     fn main() {
         println!("So far, only Linux systems are supported.");
     }

    由于使用Windows测试,本实例未进行测试验证

  51. 启用每个模块的日志级别

     mod foo {
         mod bar {
             pub fn run() {
                 log::info!("Running foo::bar::run");
                 log::debug!("Debug info in foo::bar::run");
                 log::warn!("Warning in foo::bar::run");
             }
         }
    
         pub fn run() {
             log::info!("Running foo::run");
             log::debug!("Debug info in foo::run");
             log::warn!("Warning in foo::run");
             bar::run();
         }
     }
    
     fn main() {
         env_logger::init();
         log::warn!("[root] warn");
         log::info!("[root] info");
         log::debug!("[root] debug");
         foo::run();
     }

    设置环境变量:

     $env:RUST_LOG="warn,config_log::foo=info,config_log::foo::bar=debug"
     cargo run

    如此,可以得到以下输出:

     [2026-01-09T09:12:02Z WARN  config_log] [root] warn
     [2026-01-09T09:12:02Z INFO  config_log::foo] Running foo::run
     [2026-01-09T09:12:02Z WARN  config_log::foo] Warning in foo::run
     [2026-01-09T09:12:02Z INFO  config_log::foo::bar] Running foo::bar::run
     [2026-01-09T09:12:02Z DEBUG config_log::foo::bar] Debug info in foo::bar::run
     [2026-01-09T09:12:02Z WARN  config_log::foo::bar] Warning in foo::bar::run

    可以看到,根模块的日志级别为warnfoo模块的日志级别为info,而foo::bar模块的日志级别为debug。通过这种方式,可以灵活地控制不同模块的日志输出级别。

  52. 使用自定义环境变量定义日志级别

     use env_logger::Builder;
    
     fn main() {
         Builder::new()
             .parse_env("MYGO")
             .init();
    
         log::info!("informational message");
         log::warn!("warning message");
         log::error!("this is an error {}", "message");
     }

    设置环境变量:

     $env:MYGO="info"
     cargo run

    输出类似于:

     [2026-01-09T09:41:33Z INFO  config_log] informational message
     [2026-01-09T09:41:33Z WARN  config_log] warning message
     [2026-01-09T09:41:33Z ERROR config_log] this is an error message

    环境变量设置方法与RUST_LOG类似,但这里使用了自定义的环境变量MYGO来定义日志级别。

  53. 在日志中包含时间戳

     use std::io::Write;
     use chrono::Local;
     use env_logger::Builder;
     use log::LevelFilter;
    
     fn main() {
         Builder::new()
             .format(|buf, record| {
                 writeln!(buf,
                     "{} [{}] - {}",
                     Local::now().format("%Y-%m-%d %H:%M:%S"),
                     record.level(),
                     record.args()
                 )
             })
             .filter(None, LevelFilter::Info)
             .init();
    
         log::info!("informational message");
         log::warn!("warning message");
         log::error!("this is an error {}", "message");
     }

    输出类似于:

     2026-01-09 17:45:47 [INFO] - informational message
     2026-01-09 17:45:47 [WARN] - warning message
     2026-01-09 17:45:47 [ERROR] - this is an error message
  54. 将信息记录到自定义位置

     use log::LevelFilter;
     use log4rs::config::{Config, Appender, Root};
     use log4rs::append::file::FileAppender;
     use log4rs::encode::pattern::PatternEncoder;
    
     fn main() -> Result<(), Box<dyn std::error::Error>> {
         let logfile = FileAppender::builder()
             .encoder(Box::new(PatternEncoder::new("{d} [{l}] - {m}\n")))
             .build("log/output.log")?;
    
         let config = Config::builder()
             .appender(Appender::builder().build("logfile", Box::new(logfile)))
             .build(
                 Root::builder()
                     .appender("logfile")
                     .build(LevelFilter::Info),
             )?;
    
         log4rs::init_config(config)?;
    
         log::info!("informational message");
         log::warn!("warning message");
         log::error!("this is an error {}", "message");
    
         Ok(())
     }

    这个代码示例展示了如何使用log4rs库将日志信息记录到自定义文件中。首先,创建一个FileAppender,指定日志文件路径和日志格式。然后,构建日志配置,将文件追加器添加到配置中,并设置根日志级别为Info。最后,初始化日志配置并记录日志信息。

    这个程序会将日志信息写入到log/output.log文件中,日志格式包括时间戳、日志级别和消息内容。

  55. 解析并递增版本字符串

     use semver::{BuildMetadata, Error, Prerelease, Version};
    
     fn main() -> Result<(), Error> {
         let mut parsed_version = Version::parse("1.1.4")?;
    
         assert_eq!(
             parsed_version, 
             Version {
                 major: 1,
                 minor: 1,
                 patch: 4,
                 pre: Prerelease::EMPTY,
                 build: BuildMetadata::EMPTY,
             }
         );
    
         parsed_version.patch += 1;
         println!("Updated version: {}", parsed_version);
    
         parsed_version.minor += 1;
         println!("Updated version: {}", parsed_version);
    
         parsed_version.major += 1;
         println!("Updated version: {}", parsed_version);
    
         Ok(())
     }

    注意:由于新版本控制库中删除了increment_patch()等方法,故此处直接对patchminormajor字段进行递增操作。

  56. 解析复杂版本字符串

     use semver::Version;
     use semver::Error;
    
     fn main() -> Result<(), Error> {
         let version_str = "2.0.0-alpha.1+build.123";
         let parsed_version = Version::parse(version_str)?;
    
         assert_eq!(
             parsed_version,
             Version {
                 major: 2,
                 minor: 0,
                 patch: 0,
                 pre: semver::Prerelease::new("alpha.1").unwrap(),
                 build: semver::BuildMetadata::new("build.123").unwrap(),
             }
         );
         assert_eq!(
             parsed_version.build,
             semver::BuildMetadata::new("build.123").unwrap(),
         );
    
         let serialized_version = parsed_version.to_string();
         assert_eq!(serialized_version, version_str);
    
         println!("Parsed and serialized version match: {}", serialized_version);
    
         Ok(())
     }
  57. 检查给定版本是否为预发布版本

     use semver::{Version, Error};
    
     fn main() -> Result<(), Error> {
    
         let ver1 = Version::parse("1.0.64-alpha")?;
         let ver2 = Version::parse("1.1.4")?;
    
         assert!(ver1.pre.is_empty());
         assert!(!ver2.pre.is_empty());
    
         Ok(())
    
     }

    注意:is_prerelease()方法已被移除,故此处使用pre.is_empty()来检查版本是否为预发布版本。

  58. 查询适配范围的最新版本

     use semver::{Error, Version, VersionReq};
    
     fn find_max_matching_version<'a, I>(ver: &str, iter: I) -> Result<Option<Version>, Error>
     where
         I: IntoIterator<Item = &'a str>,
     {
         let vreq = VersionReq::parse(ver)?;
    
         Ok(
             iter
                 .into_iter()
                 .filter_map(|s| {
                     Version::parse(s).ok()
                 })
                 .filter(|s| {
                     vreq.matches(s)
                 })
                 .max(),
         )
     }
    
     fn main() -> Result<(), Error> {
    
         assert_eq!(
             find_max_matching_version("< 1.5.0", vec!["1.0.0", "1.1.4", "1.5.0", "2.0.0"])?,
             Some(Version::parse("1.1.4")?),
         );
    
         assert_eq!(
             find_max_matching_version(">= 2.0.0", vec!["1.0.0", "1.1.4", "1.5.0", "2.0.0"])?,
             Some(Version::parse("2.0.0")?),
         );
    
         Ok(())
     }
  59. 检查外部命令的版本兼容性

     use std::process::Command;
     use semver::{Version, VersionReq};
    
     fn main() -> Result<(), Box<dyn std::error::Error>> {
         let ver_constraint = ">= 1.0.0";
    
         let ver_test = VersionReq::parse(ver_constraint)?;
         let output = Command::new("git").arg("--version").output()?;
    
         if !output.status.success() {
             println!("Failed to execute git command");
             return Ok(());
         }
    
         let stdout = String::from_utf8(output.stdout)?;
    
         let ver = stdout.trim().split_whitespace().last().ok_or_else(|| { "Invalid command output!" })?;
    
         // git on windows can be 2.51.0.windows.1, which is not semver-compliant.
         // We attempt to parse only the first three parts.
         let ver = ver.split('.')
             .take(3)
             .collect::<Vec<_>>()
             .join(".");
    
         let parsed_version = Version::parse(&ver)?;
         if !ver_test.matches(&parsed_version) {
             println!("Version {} does not satisfy the constraint {}", parsed_version, ver_constraint);
         } else {
             println!("Version {} satisfies the constraint {}", parsed_version, ver_constraint);
         }
    
         Ok(())
    
     }

    由于 Windows 上的 Git 版本字符串可能包含非标准的部分(如 windows.1),因此在解析版本时,我们只取前三个数字部分进行拼接,以确保符合 SemVer 标准。

  60. 编译并静态链接到绑定的 C 语言库

    在使用 Rust 调用 C 语言库时,通常需要确保 C 库已经被正确编译并链接。以下是一个示例,展示如何使用 cc crate 来编译一个简单的 C 语言库,并在 Rust 代码中调用它。

    • 首先,在 build.rs 文件中添加以下代码,用于编译 C 语言源文件:

        fn main() {
            cc::Build::new()
                .file("src/side.c")
                .compile("side");
        }
    • 然后,在 src/side.c 文件中编写一个简单的 C 函数:

        #include <stdio.h>
      
        void hello() {
            printf("Hello from C!\n");
        }
      
        void greet(const char* name) {
            printf("Hello, %s!\n", name);
        }
    • 接下来,在 Rust 代码中声明外部函数并调用它们:

        use std::ffi::CString;
        use std::os::raw::c_char;
      
        fn prompt(s: &str) -> Result<String, Box<dyn std::error::Error>> {
            use std::io::Write;
            print!("{}", s);
            std::io::stdout().flush()?;
            let mut input = String::new();
            std::io::stdin().read_line(&mut input)?;
            Ok(input.trim().to_string())
        }
      
        unsafe extern "C" {
            fn hello();
            fn greet(name: *const c_char);
        }
      
        fn main() -> Result<(), Box<dyn std::error::Error>> {
      
            unsafe {
                hello();
            }
            let name = prompt("What's your name?")?;
            let c_name = CString::new(name)?;
            unsafe {
                greet(c_name.as_ptr());
            }
      
            Ok(())
        }

    注意:一切在 Rust 上 extern "C" 块中声明的函数都必须是unsafe的,因为调用外部代码可能会引发未定义行为。在 Rust 1.94.0-nightly 版本中,extern 块必须声明关键字 unsafe。并且在调用这些函数时也需要使用 unsafe 块,在 unsafe 块中调用。

    C++ 的处理方法与 C 类似,不再过多赘述。

  61. 编译 C 语言库时自定义设置

    更改 build.rs 文件内容如下:

     fn main() {
         cc::Build::new()
             .define("APP_NAME", "\\"test\\"")
             .define("VERSION", "\\"1.1.4\\"")
             .define("WELCOME", None)
             .file("src/side.c")
             .compile("side");
     }

    define() 方法用于在编译 C 语言代码时定义预处理宏。它接受两个参数:宏名称和宏值。如果宏没有值,可以传递 None。、

    之后,在 C 语言代码中可以使用这些宏:

     #include <stdio.h>
    
     void hello() {
         printf("Hello from C!\n");
     }
    
     void greet(const char* name) {
         printf("Hello, %s!\n", name);
     #ifdef VERSION
         printf("Version: %s\n", VERSION);
     #endif
     }

    注意:编译前可能需要清理之前的构建缓存。

  62. 将字符串进行百分比编码(URL 编码)

     use percent_encoding::{utf8_percent_encode, percent_decode, AsciiSet, CONTROLS};
     use std::str::Utf8Error;
    
     /// https://url.spec.whatwg.org/#fragment-percent-encode-set
     const FRAGMENT: &AsciiSet = &CONTROLS
         .add(b' ')
         .add(b'"')
         .add(b'<')
         .add(b'>')
         .add(b'`');
    
     fn main() -> Result<(), Utf8Error> {
         let input = "confident, productive developers <3";
         println!("Input string: {}", input);
    
         let iter = utf8_percent_encode(input, FRAGMENT);
         println!("Percent-encoded: {:?}", iter);
    
         let encoded = iter.to_string();
         println!("Encoded string: {}", encoded);
    
         let iter = percent_decode(encoded.as_bytes());
         println!("Percent-decoded: {:?}", iter);
    
         let decoded = iter.decode_utf8()?;
         println!("Decoded string: {}", decoded);
    
         Ok(())
     }

    此程序将字符串进行百分比编码和解码,使用了 percent_encoding crate。首先定义了一个字符集 FRAGMENT,用于指定哪些字符需要进行编码。然后,程序对输入字符串进行编码,输出编码后的结果,并随后对编码后的字符串进行解码,输出解码后的结果。

    输出:

     Input string: confident, productive developers <3
     Percent-encoded: PercentEncode { bytes: [99, 111, 110, 102, 105, 100, 101, 110, 116, 44, 32, 112, 114, 111, 100, 117, 99, 116, 105, 118, 101, 32, 100, 101, 118, 101, 108, 111, 112, 101, 114, 115, 32, 60, 51], ascii_set: AsciiSet { mask: [4294967295, 1342177285, 0, 2147483649] } }
     Encoded string: confident,%20productive%20developers%20%3C3
     Percent-decoded: PercentDecode { bytes: Iter([99, 111, 110, 102, 105, 100, 101, 110, 116, 44, 37, 50, 48, 112, 114, 111, 100, 117, 99, 116, 105, 118, 101, 37, 50, 48, 100, 101, 118, 101, 108, 111, 112, 101, 114, 115, 37, 50, 48, 37, 51, 67, 51]) }
     Decoded string: confident, productive developers <3

    其中:被编码的空格变成了%20,小于号<变成了%3C。详细参见源码 URL。

  63. 将字符串编码为 application/x-www-form-urlencoded 格式

    什么是 application/x-www-form-urlencoded 格式? application/x-www-form-urlencoded 是一种常用于 HTTP 请求中传递表单数据的编码格式。在这种格式中,键值对以 key=value 的形式表示,多个键值对之间使用 & 符号分隔。特殊字符会被百分比编码(URL 编码),例如空格会被编码为 +%20

     use url::form_urlencoded::{byte_serialize, parse};
    
     fn main() {
         let url_encoded: String = byte_serialize("What is 😱?".as_bytes()).collect();
    
         println!("URL-encoded: {}", url_encoded);
    
         let decoded: String = parse(url_encoded.as_bytes())
             .map(|(k, v)| {
                 [k, v].concat()
             })
             .collect();
    
         println!("Decoded: {}", decoded);
    
     }

    输出:

     URL-encoded: What+is+%F0%9F%98%B1%3F
     Decoded: What is 😱?
  64. 编解码 base64

     use std::str;
     use base64::{engine::general_purpose, Engine as _};
    
     fn main() -> Result<(), Box<dyn std::error::Error>> {
         let input = b"Hello, Base64 encoding!";
         let encoded = general_purpose::STANDARD.encode(input);
         println!("Base64-encoded: {}", encoded);
    
         let decoded_bytes = general_purpose::STANDARD.decode(&encoded)?;
         let decoded_str = str::from_utf8(&decoded_bytes)?;
         println!("Decoded string: {}", decoded_str);
    
         Ok(())
     }

    注意:base64::encodebase64::decode 方法已被弃用,改为使用 base64::engine::general_purpose 模块中的 STANDARD 引擎来进行编码和解码。

    输出:

     Base64-encoded: SGVsbG8sIEJhc2U2NCBlbmNvZGluZyE=
     Decoded string: Hello, Base64 encoding!
  65. 编解码十六进制

     use data_encoding::{HEXUPPER, DecodeError};
    
     fn main() -> Result<(), DecodeError> {
         let input = b"The quick brown fox jumps over the lazy dog.";
         let encoded = HEXUPPER.encode(input);
         println!("Hex-encoded: {}", encoded);
    
         let decoded = HEXUPPER.decode(encoded.as_bytes())?;
         let decoded_str = String::from_utf8(decoded).expect("Invalid UTF-8");
         println!("Decoded string: {}", decoded_str);
    
         Ok(())
     }

    输出:

     Hex-encoded: 54686520717569636B2062726F776E20666F78206A756D7073206F76657220746865206C617A7920646F672E
     Decoded string: The quick brown fox jumps over the lazy dog.
  66. 读取 CSV 记录

    使用 csv crate 读取 CSV 格式的数据,将标准 CSV 读入 csv:StringRecord,是一种弱类型的方式来处理 CSV 数据。

     fn main() -> Result<(), csv::Error> {
         let csv = "year,make,model,description,price
     1997,Ford,E350,\"ac, abs, moon\",3000.00
     1999,Chevy,\\"Venture \\"\\"Extended Edition\\"\\"\\",\\"\\",4900.00
     1996,Jeep,Grand Cherokee,\\"MUST SELL! air, moon roof, loaded\\",4799.00";
    
         let mut reader = csv::Reader::from_reader(csv.as_bytes());
    
         for rec in reader.records() {
             let record = rec?;
             println!(
                 "In {}, the {} {} {} costs ${}",
                 &record[0], &record[1], &record[2], &record[3], &record[4]
             );
         }
    
         Ok(())
     }

    格式化输出:

     In 1997, the Ford E350 ac, abs, moon costs $3000.00
     In 1999, the Chevy Venture "Extended Edition"  costs $4900.00
     In 1996, the Jeep Grand Cherokee MUST SELL! air, moon roof, loaded costs $4799.00

    另一种是使用 serde crate 将 CSV 读入强类型的结构体:

     use serde::Deserialize;
    
     #[derive(Deserialize)]
     struct Car {
         year: u16,
         make: String,
         model: String,
         description: String,
         price: f32,
     }
    
     fn main() -> Result<(), csv::Error> {
         let csv = "year,make,model,description,price
     1997,Ford,E350,\\"ac, abs, moon\\",3000.00
     1999,Chevy,\\"Venture \\"\\"Extended Edition\\"\\"\\",\\"\\",4900.00
     1996,Jeep,Grand Cherokee,\\"MUST SELL! air, moon roof, loaded\\",4799.00";
    
         let mut reader = csv::Reader::from_reader(csv.as_bytes());
    
         for rec in reader.deserialize() {
             let record: Car = rec?;
             println!(
                 "In {}, the {} {} {} costs ${}",
                 record.year, record.make, record.model, record.description, record.price
             );
         }
    
         Ok(())
     }

    注意:导入 serde crate 必须启用 derive 功能标志。

  67. 读取有不同分隔符的 CSV 记录

     use serde::Deserialize;
    
     #[derive(Deserialize, Debug)]
     struct Rec {
         name: String,
         place: String,
         // Serde 自定义反序列化函数处理字段 `id`
         // `invalid_option` 函数会将空字符串解析为 `None`
         // 用在此处以固定格式解析此字段 `id` -> `Some(u64)` 或 `None`
         #[serde(deserialize_with = "csv::invalid_option")]
         id: Option<u64>,
     }
    
     use csv::ReaderBuilder;
    
     fn main() -> Result<(), csv::Error> {
         let data = "name\\tplace\\tid
     Mark\\tMelbourne\\t46
     Ashley\\tZurich\\t92";
    
         let mut reader = ReaderBuilder::new()
             .delimiter(b'\\t')
             .from_reader(data.as_bytes());
    
         for result in reader.deserialize::<Rec>() {
             let result = result?;
             println!("{}, {}, {}", result.name, result.place, result.id.unwrap_or(0));
         }
    
         Ok(())
     }

    此代码内 CSV 使用 \t 作为分隔符,通过 ReaderBuilder::delimiter() 方法指定分隔符为制表符(Tab)。

  68. 筛选匹配的 CSV 记录

     use std::io;
    
     fn main() -> Result<(), csv::Error> {
         let query = "CN";
         let data = 
     "City,Country/Region,Population,Latitude,Longitude
     Tokyo,JP,37435191,35.6897,139.6922
     Delhi,IN,29399141,28.7041,77.1025
     Shanghai,CN,26317104,31.2304,121.4737
     Sao Paulo,BR,21846507,-23.5505,-46.6333
     Mumbai,IN,21810000,19.076,72.8777
     Beijing,CN,20035455,39.9042,116.4074
     Mexico City,MX,21671908,19.4326,-99.1332
     Cairo,EG,20007600,30.0444,31.2357
     Lagos,NG,13900000,6.5244,3.3792
     Taipei,TW,2381675,25.033,121.5654
     Hong Kong,HK,7451000,22.3193,114.1694
     New York,US,18804000,40.7128,-74.006
     London,GB,9304016,51.5074,-0.1278
     Pyongyang,KP,2555000,39.0392,125.7625
     Bangkok,TH,8281000,13.7563,100.5018
     Chendu,CN,16330000,30.5728,104.0668
     Harbin,CN,10600000,45.8038,126.5349
     Wuhan,CN,10850000,30.5928,114.3055
     Lahore,PK,11126285,31.5497,74.3436
     Fukuoka,JP,5101556,33.5904,130.4017
     Kuala Lumpur,MY,8000000,3.139,101.6869
     Macau,MO,682800,22.1987,113.5439";
    
         // 调用 CSV 读取器
         let mut reader = csv::Reader::from_reader(data.as_bytes());
         // 调用 CSV 写入器,输出到标准输出,即输出到控制台
         let mut writer = csv::Writer::from_writer(io::stdout());
    
         for result in reader.records() {
             // 开盒
             let record = result?;
             // 匹配查询条件
             // `get(i)` 方法用于获取指定索引的字段值,返回 `Option<&str>`
             // 此处不建议拆包为变量再比较,因为 CSV 记录可能包含空字段
             if record.get(1) == Some(query) {
                 writer.write_record(&record)?;
             }
         }
    
         // 刷新写入器缓冲区,确保所有数据都被写出
         writer.flush()?;
         Ok(())
     }

    输出:

     Shanghai,CN,26317104,31.2304,121.4737
     Beijing,CN,20035455,39.9042,116.4074
     Chendu,CN,16330000,30.5728,104.0668
     Harbin,CN,10600000,45.8038,126.5349
     Wuhan,CN,10850000,30.5928,114.3055
  69. 将记录序列化为 CSV

    可使用 csv crate 将实现了 serde::Serialize 特征的元组或结构体等序列化为 CSV 格式的数据。

     use std::io;
    
     #[derive(serde::Serialize)]
     struct Person {
         name: String,
         place: String,
         id: u64,
     }
    
     fn main() -> Result<(), csv::Error> {
         // 定义 CSV 写入器,输出到标准输出
         let mut wtr = csv::Writer::from_writer(io::stdout());
    
         wtr.write_record(&["name", "place", "id"])?;
    
         // 格式化写入多条记录,参数为实现了 Serialize 特征的元组或结构体等
         wtr.serialize(("Soyo", "Tokyo", 114514))?;
         wtr.serialize(Person {
             name: "Rana".to_string(),
             place: "Osaka".to_string(),
             id: 1919810,
         })?;
         wtr.serialize(["Miko", "Kyoto", 8101919.to_string().as_ref()])?;
    
         wtr.flush()?;
         Ok(())
     }

    输出:

     name,place,id
     Soyo,Tokyo,114514
     Rana,Osaka,1919810
     Miko,Kyoto,8101919
  70. 自定义 CSV 反序列化方案

     use csv::{Reader, Writer};
     use serde::{de, Deserialize, Deserializer};
     use std::str::FromStr;
    
     #[derive(Debug)]
     struct HexColor {
         red: u8,
         green: u8,
         blue: u8,
     }
    
     #[derive(Debug, Deserialize)]
     struct Row {
         color_name: String,
         color: HexColor,
     }
    
     impl FromStr for HexColor {
         type Err = String;
    
         // 转换 #RRGGBB 格式的字符串为 HexColor 结构体
         // 返回 `HexColor` 实例或错误信息字符串,包装于 `Result` 中
         fn from_str(hex_color: &str) -> std::result::Result<Self, Self::Err> {
             let trimmed = hex_color.trim_matches('#');
             if trimmed.len() != 6 {
                 Err("Invalid hex color length".to_string())
             } else {
                 Ok(HexColor {
                     // 格式化解析十六进制字符串为 u8 数值
                     red: u8::from_str_radix(&trimmed[..2], 16).map_err(|e| e.to_string())?,
                     green: u8::from_str_radix(&trimmed[2..4], 16).map_err(|e| e.to_string())?,
                     blue: u8::from_str_radix(&trimmed[4..6], 16).map_err(|e| e.to_string())?,
                 })
             }
         }
     }
    
     // 对 HexColor 实现 Deserialize 特征
     // 实现此特征需要声明生命周期参数 `'de`
     impl<'de> Deserialize<'de> for HexColor {
         fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
         where
             D: Deserializer<'de>,
         {
             let s = String::deserialize(deserializer)?;
             FromStr::from_str(&s).map_err(de::Error::custom)
         }
     }
    
     fn main() -> Result<(), Box<dyn std::error::Error>> {
         let data = 
     "color_name,color
     miku,#39C5BB
     rin,#FFCC11
     len,#FFEE11
     luka,#FFBACC
     kaito,#3366CC
     meiko,#CC0000"
             .to_owned();
         let mut out = Writer::from_writer(vec![]);
         let mut reader = Reader::from_reader(data.as_bytes());
         for result in reader.deserialize::<Row>() {
             let res = result?;
             out.serialize((
                 res.color_name,
                 res.color.red,
                 res.color.green,
                 res.color.blue,
             ))?;
         }
         let written = String::from_utf8(out.into_inner()?)?;
    
         println!("{}", written);
         Ok(())
     }

    对生命周期的解释 在 Rust 中,生命周期(lifetime)是一个非常重要的概念,用于描述引用的有效范围。生命周期参数通常以 ' 开头,例如 'de。在实现 Deserialize 特征时,生命周期参数用于确保在反序列化过程中引用的数据在整个过程中都是有效的。 在上面的代码中,impl<'de> Deserialize<'de> for HexColor 表示我们为 HexColor 结构体实现了 Deserialize 特征,并且这个实现依赖于一个生命周期参数 'de。这个生命周期参数确保在反序列化过程中,引用的数据在整个过程中都是有效的。 生命周期参数的使用有助于 Rust 编译器在编译时检查引用的有效性,防止悬垂引用和数据竞争等问题,从而提高代码的安全性和可靠性。 生命周期的定义方法 生命周期参数通常在 impl 块或函数签名中定义。例如,在上面的代码中,impl<'de> Deserialize<'de> for HexColor 定义了一个生命周期参数 'de,并将其应用于 Deserialize 特征的实现中。 生命周期定义没有特别限制,生命周期标签可以是任何合法的标识符,只要它以单引号 ' 开头即可。常见的生命周期标签包括 'a'b'c 等,但在实际使用中,可以根据需要选择任何合适的名称。

  71. 对非结构化的 JSON 序列化和反序列化

     use serde_json::{Error, Value, json};
    
     fn main() -> Result<(), Error> {
         let j = r#"
             {
                 "userid": 114514,
                 "verified": true,
                 "access_privileges": [
                     "user",
                     "admin"
                 ]
             }
         "#;
    
         // 直接调用 `serde_json::from_str()` 方法反序列化 JSON 字符串
         let parsed: Value = serde_json::from_str(j)?;
    
         let exp = json!({
             "userid": 114514,
             "verified": true,
             "access_privileges": [
                 "user",
                 "admin"
             ]
         });
    
         assert_eq!(parsed, exp);
    
         Ok(())
     }

    serde_json::Value 是一个非常强大的枚举类型,可以表示 JSON 数据中的各种类型,包括对象、数组、字符串、数字、布尔值和空值。通过使用 serde_json::from_str() 方法,可以将 JSON 字符串直接反序列化为 Value 类型,而无需定义具体的结构体。这种方式非常适合处理动态或未知结构的 JSON 数据。serde_json::Value 定义如下:

     #[cfg(feature = "raw_value")]
     #[cfg_attr(docsrs, doc(cfg(feature = "raw_value")))]
     pub use crate::raw::{to_raw_value, RawValue};
    
     /// Represents any valid JSON value.
     ///
     /// See the [`serde_json::value` module documentation](self) for usage examples.
     #[derive(Clone, Eq, PartialEq, Hash)]
     pub enum Value {
         /// Represents a JSON null value.
         ///
         /// ```
         /// # use serde_json::json;
         /// #
         /// let v = json!(null);
         /// ```
         Null,
    
         /// Represents a JSON boolean.
         ///
         /// ```
         /// # use serde_json::json;
         /// #
         /// let v = json!(true);
         /// ```
         Bool(bool),
    
         /// Represents a JSON number, whether integer or floating point.
         ///
         /// ```
         /// # use serde_json::json;
         /// #
         /// let v = json!(12.5);
         /// ```
         Number(Number),
    
         /// Represents a JSON string.
         ///
         /// ```
         /// # use serde_json::json;
         /// #
         /// let v = json!("a string");
         /// ```
         String(String),
    
         /// Represents a JSON array.
         ///
         /// ```
         /// # use serde_json::json;
         /// #
         /// let v = json!(["an", "array"]);
         /// ```
         Array(Vec<Value>),
    
         /// Represents a JSON object.
         ///
         /// By default the map is backed by a BTreeMap. Enable the `preserve_order`
         /// feature of serde_json to use IndexMap instead, which preserves
         /// entries in the order they are inserted into the map. In particular, this
         /// allows JSON data to be deserialized into a Value and serialized to a
         /// string while retaining the order of map keys in the input.
         ///
         /// ```
         /// # use serde_json::json;
         /// #
         /// let v = json!({ "an": "object" });
         /// ```
         Object(Map<String, Value>),
     }

    r#"{}"# 的解释 在 Rust 中,r#"{}"# 是一种原始字符串字面量(raw string literal)的表示方式。原始字符串字面量允许你在字符串中包含特殊字符而不需要使用转义字符,例如反斜杠 \</code> 或双引号 "。这种表示方式对于包含大量特殊字符的字符串非常有用,例如 JSON、正则表达式等。 语法规则:

    • 基本形式:r"内容"
    • 带井号的形式:r#"内容"#r##"内容"## 等。
      • 开头和结尾的 # 数量必须相等。
      • 如果你需要定义的字符串内容本身就包含 "#,你可以通过增加 # 的数量来避免歧义(例如使用 r##" ... "# ... "##)。
  72. 反序列化 TOML 配置文件

     use toml::{Value, de::Error};
    
     fn main() -> Result<(), Error> {
         let toml_content = r#"
             [package]
             name = "example"
             version = "1.1.4"
             edition = "2024"
             authors = ["Shizuku <shizukuaqua@outlook.com>"]
    
             [dependencies]
             serde = { version = "1.0", features = ["derive"] }
             serde_json = "1.0.149"
             toml = "0.9.11"
         "#;
    
         let package_info: Value = toml::from_str(toml_content)?;
         let dependences: Value = package_info
             .get("dependencies")
             .cloned()
             .unwrap_or(Value::Table(Default::default()));
    
         println!("Package Information: {:#?}", package_info);
         println!("Dependencies: {:#?}", dependences);
         Ok(())
     }

    toml::Value 是一个非常强大的枚举类型,可以表示 TOML 数据中的各种类型,包括表、数组、字符串、整数、浮点数、布尔值和日期时间。通过使用 toml::from_str() 方法,可以将 TOML 字符串直接反序列化为 Value 类型,而无需定义具体的结构体。这种方式非常适合处理动态或未知结构的 TOML 数据。toml::Value 定义如下:

     /// Representation of a TOML value.
     #[derive(PartialEq, Clone, Debug)]
     pub enum Value {
         /// Represents a TOML string
         String(String),
         /// Represents a TOML integer
         Integer(i64),
         /// Represents a TOML float
         Float(f64),
         /// Represents a TOML boolean
         Boolean(bool),
         /// Represents a TOML datetime
         Datetime(Datetime),
         /// Represents a TOML array
         Array(Array),
         /// Represents a TOML table
         Table(Table),
     }

    toml::Value 的实现有很多实用的方法,可以方便地操作和查询 TOML 数据。例如,可以使用 get() 方法获取表中的字段值,使用 as_str() 方法将值转换为字符串等。toml::Value::get() 方法定义如下:

     /// Index into a TOML array or map. A string index can be used to access a
     /// value in a map, and a usize index can be used to access an element of an
     /// array.
     ///
     /// Returns `None` if the type of `self` does not match the type of the
     /// index, for example if the index is a string and `self` is an array or a
     /// number. Also returns `None` if the given key does not exist in the map
     /// or the given index is not within the bounds of the array.
     pub fn get<I: Index>(&self, index: I) -> Option<&Self> {
         index.index(self)
     }

    此处也可以使用 serde crate 将 TOML 读入强类型的结构体:

     use toml::de::Error;
     use toml::Value;
     use serde::Deserialize;
     use std::collections::HashMap;
    
     #[derive(Deserialize, Debug)]
     struct Config {
         package: Package,
         /// **注意:**此处使用 `toml::Value` 类型来表示依赖项,
         /// 因为依赖项的值可能是字符串或 Map 等不同类型
         dependencies: HashMap<String, Value>,
     }
    
     #[derive(Deserialize, Debug)]
     struct Package {
         name: String,
         version: String,
         edition: String,
         authors: Vec<String>,
     }
    
     fn main() -> Result<(), Error> {
         let toml_content = r#"
             [package]
             name = "example"
             version = "1.1.4"
             edition = "2024"
             authors = ["Shizuku <shizukuaqua@outlook.com>"]
    
             [dependencies]
             serde = { version = "1.0", features = ["derive"] }
             serde_json = "1.0.149"
             toml = "0.9.11"
         "#;
    
         // 修改目标类型为 `Config`
         let package_info: Config = toml::from_str(toml_content)?;
    
         println!("Package Information: {:#?}", package_info);
    
         println!("Name: {}", package_info.package.name);
         println!("Version: {}", package_info.package.version);
         println!("Edition: {}", package_info.package.edition);
         println!("dependencies: {:#?}", package_info.dependencies);
         println!("Authors: {}", package_info.package.authors.join(", "));
    
         Ok(())
     }
  73. 以小端序读写整数

     use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
     use std::io::Error;
    
     #[derive(Default, PartialEq, Debug)]
     struct Payload {
         kind: u8,
         value: u16,
     }
    
     fn encode(payload: &Payload) -> Result<Vec<u8>, Error> {
         let mut buf = Vec::new();
         buf.write_u8(payload.kind)?;
         // 以小端序写入 u16 整数,需要给 `write_u16()` 方法指定字节序类型枚举
         buf.write_u16::<LittleEndian>(payload.value)?;
         Ok(buf)
     }
    
     fn decode(mut bytes: &[u8]) -> Result<Payload, Error> {
         let kind = bytes.read_u8()?;
         // 同 `write_u16()` 方法类似,读取 u16 整数时也需要指定字节序类型枚举
         let value = bytes.read_u16::<LittleEndian>()?;
         Ok(Payload { kind, value })
     }
    
     fn main() -> Result<(), Error> {
         let original_payload = Payload {
             kind: 114,
             value: 514,
         };
         let encoded_bytes = encode(&original_payload)?;
    
         /*
         ? <-< {key: 114, value: 514} >->
         ? value = 514 = 0x0202 = 0b0000_0010_0000_0010
         ? divide into two bytes:
         ? low byte: 0b0000_0010 = 2
         ? high byte: 0b0000_0010 = 2
         ? => [114, 2, 2]
         */
    
         let decoded_payload = decode(&encoded_bytes)?;
    
         println!("Original: {:?}", original_payload);
         println!("Encoded bytes: {:?}", encoded_bytes);
         println!("Decoded: {:?}", decoded_payload);
    
         assert_eq!(original_payload, decoded_payload);
         Ok(())
     }

    小端序(LittleEndian)和大端序(BigEndian)的解释 在计算机科学中,字节序(Endianness)指的是多字节数据类型(如整数、浮点数等)在内存中存储的顺序。主要有两种字节序:小端序(Little Endian)和大端序(Big Endian)。

    • 小端序(Little Endian):在小端序中,数据的最低有效字节(Least Significant Byte, LSB)存储在内存的最低地址处,而最高有效字节(Most Significant Byte, MSB)存储在内存的最高地址处。换句话说,数据的字节顺序是从低到高。例如,整数 0x12345678 在小端序中存储为 78 56 34 12

    • 大端序(Big Endian):在大端序中,数据的最高有效字节存储在内存的最低地址处,而最低有效字节存储在内存的最高地址处。换句话说,数据的字节顺序是从高到低。例如,整数 0x12345678 在大端序中存储为 12 34 56 78。 字节序的选择通常取决于计算机体系结构和应用程序的需求。不同的处理器架构可能采用不同的字节序,例如 x86 架构通常使用小端序,而某些网络协议则使用大端序。在进行数据传输或存储时,了解和正确处理字节序是非常重要的,以确保数据的正确解释和使用。 例如:

    • 在嵌入式 Bluetooth 128位UUID 规范中,采用小端序存储 UUID 数据。

      static const ble_uuid128_t service_uuid = BLE_UUID128_INIT(0xA1, 0xC6, 0xE6, 0xD4, 0x11, 0x45, 0x19, 0x19, 0x19, 0x19, 0x11, 0x45, 0x14, 0x19, 0x81, 0x00);
    • 在客户端 Bluetooth 128位UUID 显示时,通常需要将小端序转换为大端序进行显示。

      let uuid_bytes: [u8; 16] = [0x00, 0x81, 0x19, 0x14, 0x45, 0x11, 0x19, 0x19, 0x19, 0x19, 0x45, 0x11, 0xD4, 0xE6, 0xC6, 0xA1];
  74. main() 中适当处理错误

     use std::fs::File;
     use std::io::Read;
     use anyhow::{Context, Result};
    
     fn read_uptime() -> Result<u64> {
         let mut uptime = String::new();
         File::open("uptime")
             .context("无法打开 uptime 文件")?
             .read_to_string(&mut uptime)
             .context("读取 uptime 内容失败")?;
    
         Ok(uptime
             .split('.')
             .next()
             .context("无法解析 uptime 数据格式")?
             .parse()
             .context("无法将 uptime 转换为数字")?)
     }
    
     fn main() -> Result<()> {
         let uptime = read_uptime().context("程序运行出错")?;
         println!("Uptime: {} seconds", uptime);
         Ok(())
     }

    在上述错误处理示例中,使用了 anyhow crate 来简化错误处理流程。anyhow::Result 是一个通用的错误处理类型,可以用于返回任何实现了 std::error::Error 特征的错误类型。通过使用 context() 方法,可以为错误添加上下文信息,使得错误信息更加清晰易懂。

    可以看到 anyhow::Context 实现:

     impl<T> Context<T, Infallible> for Option<T> {
         fn context<C>(self, context: C) -> Result<T, Error>
         where
             C: Display + Send + Sync + 'static,
         {
             // Not using ok_or_else to save 2 useless frames off the captured
             // backtrace.
             match self {
                 Some(ok) => Ok(ok),
                 None => Err(Error::construct_from_display(context, backtrace!())),
             }
         }
    
         fn with_context<C, F>(self, context: F) -> Result<T, Error>
         where
             C: Display + Send + Sync + 'static,
             F: FnOnce() -> C,
         {
             match self {
                 Some(ok) => Ok(ok),
                 None => Err(Error::construct_from_display(context(), backtrace!())),
             }
         }
     }

    实现方法 Context::context(context) 接受一个上下文信息参数 context,此参数可以是任何实现了 Display + Send + Sync + 'static 其中任意一种的类型,当 OptionNone 时,返回一个包含上下文信息的错误 Err(Error) 并调用 backtrace!() 宏回溯发现栈调用情况输出。如果 OptionSome(ok),则返回包含值的 Ok(ok),将上一个返回值向下传递。

    接下来发现 Context::with_context(context_fn) 方法接受一个返回上下文信息的闭包 context_fn,当 OptionNone 时,调用闭包获取上下文信息并返回包含该信息的错误 Err(Error) 并调用 backtrace!() 宏回溯发现栈调用情况输出。如果 OptionSome(ok),则返回包含值的 Ok(ok),将上一个返回值向下传递。意味着可以这么写:

         .with_context(|| "无法解析 uptime 数据格式")?

    FnOnce() 闭包表示该闭包只能被调用一次,适用于需要消耗捕获变量的场景。

    如果找不到文件,会有类似的如下输出:1

     Error: 程序运行出错
    
     Caused by:
         0: 无法打开 uptime 文件
         1: 系统找不到指定的文件。 (os error 2)
    
     Stack backtrace:
     0: std::backtrace_rs::backtrace::win64::trace
                 at /rustc/a3f2d5abe45a9acfaccbf09266b33e1fd7ab193e/library\std\src\..\..\backtrace\src\backtrace\win64.rs:85
     1: std::backtrace_rs::backtrace::trace_unsynchronized
                 at /rustc/a3f2d5abe45a9acfaccbf09266b33e1fd7ab193e/library\std\src\..\..\backtrace\src\backtrace\mod.rs:66
     2: std::backtrace::Backtrace::create
                 at /rustc/a3f2d5abe45a9acfaccbf09266b33e1fd7ab193e/library\std\src\backtrace.rs:331
     3: std::backtrace::Backtrace::capture
                 at /rustc/a3f2d5abe45a9acfaccbf09266b33e1fd7ab193e/library\std\src\backtrace.rs:296
     4: anyhow::context::ext::impl$0::ext_context<std::io::error::Error,ref$<str$> >
                 at C:\Users\ASUS\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\anyhow-1.0.100\src\backtrace.rs:27
     5: anyhow::context::impl$0::context<std::fs::File,std::io::error::Error,ref$<str$> >
                 at C:\Users\ASUS\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\anyhow-1.0.100\src\context.rs:54
     6: handle::read_uptime
                 at .\src\main.rs:8
     7: handle::main
                 at .\src\main.rs:21
     8: core::ops::function::FnOnce::call_once<enum2$<core::result::Result<tuple$<>,anyhow::Error> > (*)(),tuple$<> >
                 at C:\Users\ASUS\.rustup\toolchains\nightly-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\ops\function.rs:250
     9: std::sys::backtrace::__rust_begin_short_backtrace<enum2$<core::result::Result<tuple$<>,anyhow::Error> > (*)(),enum2$<core::result::Result<tuple$<>,anyhow::Error> > >
                 at C:\Users\ASUS\.rustup\toolchains\nightly-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\std\src\sys\backtrace.rs:166
     10: std::rt::lang_start::closure$0<enum2$<core::result::Result<tuple$<>,anyhow::Error> > >
                 at C:\Users\ASUS\.rustup\toolchains\nightly-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\std\src\rt.rs:206
     11: std::rt::lang_start_internal::closure$0
                 at /rustc/a3f2d5abe45a9acfaccbf09266b33e1fd7ab193e/library\std\src\rt.rs:175
     12: std::panicking::catch_unwind::do_call
                 at /rustc/a3f2d5abe45a9acfaccbf09266b33e1fd7ab193e/library\std\src\panicking.rs:581
     13: std::panicking::catch_unwind
                 at /rustc/a3f2d5abe45a9acfaccbf09266b33e1fd7ab193e/library\std\src\panicking.rs:544
     14: std::panic::catch_unwind
                 at /rustc/a3f2d5abe45a9acfaccbf09266b33e1fd7ab193e/library\std\src\panic.rs:359
     15: std::rt::lang_start_internal
                 at /rustc/a3f2d5abe45a9acfaccbf09266b33e1fd7ab193e/library\std\src\rt.rs:171
     16: std::rt::lang_start<enum2$<core::result::Result<tuple$<>,anyhow::Error> > > 
                 at C:\Users\ASUS\.rustup\toolchains\nightly-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\std\src\rt.rs:205
     17: main
     18: invoke_main
                 at D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:78
     19: __scrt_common_main_seh
                 at D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288
     20: BaseThreadInitThunk
     21: RtlUserThreadStart
     error: process didn't exit successfully: `target\debug\handle.exe` (exit code: 1)

    如果没用 anyhow,而是直接使用标准库的错误处理,会有类似如下输出:

     系统找不到指定的文件。 (os error 2)

    由此可见,使用 anyhow 可以提供更丰富的错误信息,帮助开发者更好地定位和解决问题。

    应该注意的是,此处 Context::context()

    注意:使用 anyhow 替代 error-chain,因为 error-chain 已过时不再维护。

    对 Linux 下 uptime 的简单解释 在 Linux 操作系统中,uptime 是一个命令,用于显示系统已经运行的时间长度。它提供了有关系统运行状态的有用信息,包括系统启动以来的总运行时间、当前时间、系统负载等。uptime 命令的输出通常包含以下几个部分:

    • 当前时间:显示系统当前的时间。
    • 系统运行时间:显示系统自上次启动以来已经运行的时间,通常以天、小时和分钟的形式表示。
    • 当前登录用户数:显示当前登录到系统的用户数量。
    shizuku@AquaShizuku:/proc$ uptime
    23:35:50 up  3:22,  1 user,  load average: 0.00, 0.00, 0.00

    在上面的输出中:

    • 23:35:50 是当前时间。
    • up 3:22 表示系统已经运行了 3 小时 22 分钟。
    • 1 user 表示当前有 1 个用户登录到系统。
    • load average: 0.00, 0.00, 0.00 显示了系统在过去 1 分钟、5 分钟和 15 分钟内的平均负载情况。

    对于 /proc/uptime 文件,它包含了系统的运行时间信息,格式如下:

    11350.21 45286.80
    • 第一个数字 11350.21 表示系统自启动以来的总运行时间,单位为秒。
    • 第二个数字 45286.80 表示系统空闲时间,单位为秒。

    使用 anyhow 不需要注意错误遗漏问题,回溯也无需关心(backtrace!() 宏已经做了)。

     75. 读写文件字符串行
     use std::fs::File;
     use std::io::{Write, BufReader, BufRead, Error};
    
     fn main() -> Result<(), Error> {
         let path = "example.txt";
    
         let mut output = File::create(path)?;
    
         writeln!(output, "Hello, world!")?;
         writeln!(output, "This is a test file.")?;
         writeln!(output, "我们党领导人民进行社会主义建设")?;
         writeln!(output, "有改革开放前和改革开放后两个历史时期")?;
         writeln!(output, "这是两个相互联系又有重大区别的时期")?;
         writeln!(output, "但本质上都是我们党领导人民进行社会主义建设的实践")?;
         writeln!(output, "不能用改革开放后的历史时期否定改革开放前的历史时期")?;
         writeln!(output, "也不能用改革开放前的历史时期否定改革开放后的历史时期")?;
    
         let input = File::open(path)?;
         let buffer = BufReader::new(input);
    
         for line in buffer.lines() {
             println!("{}", line?);
         }
    
         Ok(())
     }

    writeln!()write!() 用于向文件写入字符串。

    参数:

    • 第一个参数是实现了 std::io::Write 特征的输出目标,此处为文件句柄 output
    • 第二个参数是要写入的字符串内容,可以包含格式化占位符。
  75. 避免读写同一文件

     use std::fs::File;
     use std::io::{BufReader, BufRead, Error, ErrorKind};
     use std::path::Path;
    
     use same_file::Handle;
    
     fn main() -> Result<(), Error> {
         let reading = Path::new("example.txt");
    
         let stdout_handle = Handle::stdout()?;
         let handle = Handle::from_path(reading)?;
    
         println!("stdout handle: {:?}", stdout_handle);
         println!("Target handle: {:?}", handle);
    
         if stdout_handle == handle {
             return Err(Error::new(ErrorKind::Other, "Refusing to read from stdout"));
         } else {
             let file = File::open(&reading);
             let file = BufReader::new(file?);
    
             for (num, line) in file.lines().enumerate() {
                 println!("{}: {}", num + 1, line?);
             }
         }
    
         Ok(())
     }

    本示例使用了 same_file crate 来比较两个文件句柄是否指向同一个文件,从而避免读写同一文件导致的数据损坏或丢失问题。此处比较的是标准输出句柄和目标文件句柄,虽然两者基本不会相同,但在某些特殊情况下(例如重定向输出到文件),可能会出现相同的情况。或者处理在有些时候对同一文件进行读写导致循环等待等问题。

    注意: Powershell 和 CMD 下的标准输出在 Rust 中是不同的文件句柄,在 Powershell 中输出到对应文件时是一个管道,而在 CMD 中输出到对应文件时是一个实际的文件句柄,因此在不同的终端下运行时可能会有不同的行为。

     cargo run >> example.txt

    结果可能获取到:

     stdout handle: Handle(Handle { kind: Borrowed(HandleRef(HandleRefInner(Some(File { handle: 0x250 })))), key: Some(Key { volume: 0, index: 176681 }) })
     Target handle: Handle(Handle { kind: Owned(Handle(File { handle: 0xbc, path: "\\\\?\\C:\\Users\\ASUS\\Documents\\Rust_start\\rust_cookbook\\file_struct\\read-write\\example.txt" })), key: Some(Key { volume: 2751900681, index: 10696049115558411 }) })

    发现,虽然读取了同一文件,但是 Powershell 下的标准输出句柄和目标文件句柄并不相同,因此不会触发错误。

     cargo run >> example.txt

    结果可能获取到:

     stdout handle: Handle(Handle { kind: Borrowed(HandleRef(HandleRefInner(Some(File { handle: 0x2b0, path: "\\\\?\\C:\\Users\\ASUS\\Documents\\Rust_start\\rust_cookbook\\file_struct\\read-write\\example.txt" })))), key: Some(Key { volume: 2751900681, index: 10696049115558411 }) })
     Target handle: Handle(Handle { kind: Owned(Handle(File { handle: 0xb8, path: "\\\\?\\C:\\Users\\ASUS\\Documents\\Rust_start\\rust_cookbook\\file_struct\\read-write\\example.txt" })), key: Some(Key { volume: 2751900681, index: 10696049115558411 }) })

    发现,CMD 下的标准输出句柄和目标文件句柄是相同的,因此会触发错误,避免了读写同一文件的问题。

  76. 使用内存映射随机访问文件

     use memmap2::Mmap;
     use std::fs::File;
     use std::io::Error;
    
     fn main() -> Result<(), Error> {
         let file = File::open("example.txt")?;
         let mmap = unsafe { Mmap::map(&file)? };
    
         let rand_ind = [1, 1, 4, 5, 1, 4, 1, 9, 1, 9, 8, 10, 11, 45, 14, 191, 98, 100];
    
         println!("Mapped file length: {}", mmap.len());
    
         let rand_bytes: Vec<u8> = rand_ind
             .iter()
             .map(|&idx| mmap[idx])
             .collect();
    
         println!("Random bytes: {:?}", rand_bytes);
    
         for i in rand_bytes {
             print!("{}", i as char);
         }
    
         Ok(())
     }

    注意:memmap crate 已不再维护,建议使用 memmap2 crate 作为替代。

    本示例使用了 memmap2 crate 来创建文件的内存映射,从而实现对文件内容的随机访问。通过内存映射,可以将文件内容直接映射到内存中,避免了频繁的磁盘 I/O 操作,提高了访问效率。示例中定义了一个随机索引数组 rand_ind,通过这些索引从内存映射中读取对应的字节,并将其转换为字符进行输出。

    输出:

     Mapped file length: 817
     Random bytes: [101, 101, 111, 44, 101, 111, 101, 114, 101, 114, 111, 108, 100, 162, 84, 230, 141, 146]
     eeo,eoererold¢Tæ

    示例含有中文字符,故呈现出现错误字符

  77. 筛选近期修改的文件

     use std::{env, fs};
     use anyhow::Result;
    
     fn main() -> Result<()> {
         let cur_dir = env::current_dir()?;
    
         println!("Current directory: {}", cur_dir.display());
    
         for ent in fs::read_dir(cur_dir)? {
             let ent = ent?;
             let path = ent.path();
    
             let metadata = fs::metadata(&path)?;
             let last_mod = metadata.modified()?.elapsed()?.as_secs();
    
             println!("File: {}, Last modified (secs ago): {}", path.display(), last_mod);
    
             if last_mod < 86400 {
                 println!("Recently modified: {}", path.display());
             }
         }
         Ok(())
     }

    本示例使用了 std::fs 模块来读取当前目录下的文件,并通过文件的元数据获取最后修改时间。通过比较当前时间和文件的最后修改时间,可以筛选出在过去 24 小时内修改过的文件。metadata.modified() 方法返回一个 SystemTime 类型,表示文件的最后修改时间,elapsed() 方法计算从该时间点到当前时间的持续时间,最后使用 as_secs() 方法将其转换为秒数进行比较。

  78. 递归查找重名文件

     use std::collections::HashMap;
     use walkdir::WalkDir;
    
     fn main() {
         let mut filenames = HashMap::new();
    
         for ent in WalkDir::new(".")
             .into_iter()
             .filter_map(Result::ok)
             .filter(|e| !e.file_type().is_dir()) {
                 let f_name = String::from(ent.file_name().to_string_lossy());
                 let count = filenames.entry(f_name.clone()).or_insert(0);
                 *count += 1;
    
                 if *count > 1 {
                     println!("Duplicate file name: {}", f_name);
                 }
             }
     }
  79. 递归查找所有符合条件的文件

     use walkdir::WalkDir;
     use anyhow::Result;
    
     fn main() -> Result<()> {
         for ent in WalkDir::new(".")
             .follow_links(true)
             .into_iter()
             .filter_map(|e| e.ok()) 
         {
             let f_name = ent.file_name().to_string_lossy();
             let sec = ent.metadata()?.modified()?.elapsed()?.as_secs();
    
             if f_name.ends_with(".json") && sec < 86400 {
                 println!("Recently modified JSON file: {}", f_name);
             }
         }
    
         Ok(())
     }

    对一些方法的解释:

    • metadata():获取文件或目录的元数据,返回一个 Metadata 结构体,包含文件的各种属性信息,如大小、权限、修改时间等。
    • modified():获取文件或目录的最后修改时间,返回一个 SystemTime 类型,表示文件的最后修改时间点。
    • elapsed():计算从指定时间点到当前时间的持续时间,返回一个 Duration 类型,表示时间间隔。
    • ends_with():检查字符串是否以指定的子字符串结尾,返回一个布尔值。
    • file_name():获取文件或目录的名称,返回一个 OsStr 类型,表示文件名。
    • to_string_lossy():将 OsStr 类型转换为字符串,如果包含无效的 UTF-8 字符,则使用替代字符替换,返回一个 `Cow ` 类型。
    • follow_links(true):在递归遍历目录时,跟随符号链接(软链接),以确保访问链接指向的实际文件或目录。
  80. 递归计算一定深度范围内文件总大小

     use walkdir::WalkDir;
    
     fn main() {
         let total_size = WalkDir::new(".")
             .min_depth(1)
             .max_depth(3)
             .into_iter()
             .filter_map(|e| e.ok())
             .filter_map(|e| e.metadata().ok())
             .filter(|meta| meta.is_file())
             .fold(0, |acc, m| acc + m.len());
    
         println!("Total size of files (1-3 depth): {} bytes", total_size);
     }

    指定了 min_depth(1)max_depth(3),表示只计算当前目录下的子目录及其子目录中的文件大小,忽略更深层次的文件。min_depth(1) 确保至少进入一级子目录,而 max_depth(3) 限制了递归的最大深度为三级子目录。

    对一些过滤器方法的解释:

    • filter_map(|e| e.ok()):过滤掉遍历过程中可能出现的错误,只保留成功的目录项。
    • filter_map(|e| e.metadata().ok()):获取每个目录项的元数据,并过滤掉获取元数据时可能出现的错误。
    • filter(|meta| meta.is_file()):只保留文件类型的元数据,忽略目录和其他类型的文件系统对象。

    闭包传入的参数 emeta 分别表示目录项和其对应的元数据。e.metadata() 方法用于获取目录项的元数据,返回一个 `Result

    ` 类型,使用 `ok()` 方法将其转换为 `Option`,从而过滤掉可能的错误。`meta.is_file()` 方法用于检查元数据是否表示一个文件类型。 > **闭包传入参数从哪里来?** > 在 Rust 中,闭包(closure)是一种匿名函数,可以捕获其环境中的变量。闭包的参数是由调用闭包时传入的值决定的。在上述代码中,闭包传入的参数 `e` 和 `meta` 分别表示目录项和其对应的元数据,这些参数是由 `WalkDir` 迭代器在遍历目录时生成的。 当调用 `into_iter()` 方法时,`WalkDir` 会返回一个迭代器,该迭代器会依次产生目录中的每个条目(`DirEntry`)。在 `filter_map` 和 `filter` 方法中,闭包会接收这些条目作为参数,从而对其进行处理和过滤。 具体来说: > - 在 `filter_map(|e| e.ok())` 中,闭包接收一个 `Result` 类型的参数 `e`,并使用 `ok()` 方法将其转换为 `Option`,从而过滤掉可能的错误。 > - 在 `filter_map(|e| e.metadata().ok())` 中,闭包接收一个 `DirEntry` 类型的参数 `e`,并调用其 `metadata()` 方法获取元数据,返回一个 `Result` 类型,然后使用 `ok()` 方法将其转换为 `Option`,从而过滤掉可能的错误。 > > 一句话说,闭包参数在此来自于 `Iterator`,中的 `T` 类型。 `ok()` 函数作用于 `Result`,将其转换为 `Option`,如果 `Result` 是 `Ok(value)`,则返回 `Some(value)`,如果是 `Err(_)`,则返回 `None`。这种转换在处理可能失败的操作时非常有用,可以简化错误处理逻辑。
  81. 递归查找指定后缀文件

     use glob::glob;
     use anyhow::Result;
    
     fn main() -> Result<()> {
         for entry in glob("**/*.rs")? {
             match entry {
                 Ok(path) => println!("Found Rust file: {}", path.display()),
                 Err(e) => println!("Error reading path: {}", e),
             }
         }
    
         Ok(())
     }

    glob() 方法传入的是 &str,是一个路径,返回了 Result<Paths, PatternError>,其中 Paths 是一个迭代器,迭代器的每个元素是 Result<PathBuf, GlobError>。因此在遍历 glob("**/*.rs")? 返回的迭代器时,每个 entry 都是一个 Result<PathBuf, GlobError>

  82. 忽略大小写,使用给定模式查找所有文件

     use glob::{glob_with, MatchOptions};
     use anyhow::Result;
    
     fn main() -> Result<()> {
         let opt = MatchOptions {
             case_sensitive: false,
             ..Default::default()
         };
    
         for ent in glob_with("cargo*", opt)? {
             println!("Entry: {}", ent?.display());
         }
    
         Ok(())
     }

    输出可能为:

     Entry: Cargo.lock
     Entry: Cargo.toml

    可以发现,尽管文件名中有大写字母 C,但由于设置了 case_sensitive: false,因此仍然能够匹配到这些文件。而且 glob_with() 方法允许我们自定义匹配选项,从而实现更灵活的文件查找功能。

  83. 获取逻辑 CPU 内核数

     use num_cpus::get;
    
     fn main() {
         println!("Processor count: {}", get());
     }

    输出类似:

     Processor count: 16

    注意:逻辑处理器数量通常大于或等于物理处理器数量,因为现代 CPU 通常支持超线程技术(Hyper-Threading),允许每个物理核心同时处理多个线程,从而提高整体性能。

  84. 常量

     use lazy_static::lazy_static;
     use std::collections::HashMap;
    
     lazy_static! {
         static ref PRIVILEGES: HashMap<&'static str, Vec<&'static str>> = {
             let mut map = HashMap::new();
             map.insert("Rana", vec!["leader", "admin"]);
             map.insert("Rikki", vec!["leader"]);
             map
         };
     }
    
     fn show_acc(name: &str) {
         let access = PRIVILEGES.get(name);
         println!("{:?}", access.unwrap());
     }
    
     fn main() {
         let acc = PRIVILEGES.get("Rana");
         println!("Rana: {:?}", acc);
    
         show_acc("Rikki");
     }

    对比 conststatic

    • const 用于定义常量值,必须在编译时确定,并且不可变。const 常量没有固定的内存地址,每次使用时都会被内联展开。
    • static 用于定义具有固定内存地址的全局变量,可以是可变的(使用 static mut),但需要注意线程安全问题。static 变量在程序运行期间只会初始化一次。

    lazy_static! 在此处发挥的作用

    lazy_static! 宏用于定义在第一次访问时才初始化的静态变量。它允许我们创建复杂的静态数据结构,如 HashMap,而不需要在编译时就确定其值。通过使用 lazy_static!,我们可以确保静态变量在首次使用时才被初始化,从而避免了在程序启动时进行昂贵的计算或资源分配。

  85. 监听 TCP/IP 端口

     use std::net::{SocketAddrV4, Ipv4Addr, TcpListener};
     use std::io::{Read, Error};
    
     fn main() -> Result<(), Error> {
         let loopback = Ipv4Addr::new(127, 0, 0, 1);
         let socket_addr = SocketAddrV4::new(loopback, 9987);
         let listener = TcpListener::bind(socket_addr)?;
         let port = listener.local_addr()?;
    
         println!("Listening on {}", port);
    
         let (mut tcp_stream, addr) = listener.accept()?;
    
         println!("Connection established with {}", addr);
    
         let mut input = String::new();
         let _ = tcp_stream.read_to_string(&mut input)?;
    
         println!("Received data: {} from {}", input, addr);
         Ok(())
     }

    在另一个 PowerShell 中输入指令:

     Get-Content .\Cargo.lock | ncat -C 127.0.0.1 9987

    这个指令将本目录下 Cargo.lock 文件的内容通过 TCP 连接发送到本地的 9987 端口。

    得到如下结果:

     Listening on 127.0.0.1:9987
     Connection established with 127.0.0.1:46397
     Received data: # This file is automatically @generated by Cargo.
     # It is not intended for manual editing.
     version = 4
    
     [[package]]
     name = "server"
     version = "0.1.0"
     from 127.0.0.1:46397

    可以发现,程序成功监听了本地的 9987 端口,并接收到了通过该端口发送的数据。

    需要注意的是:这个 read_to_string() 方法会一直阻塞,直到连接关闭或发生错误。因此,在实际应用中,可能需要考虑使用非阻塞 I/O 或多线程来处理多个连接。

  86. 运行外部命令并处理 stdout

     use std::{process::Command};
     use regex::Regex;
     use anyhow::Result;
    
     // 定义一个 Commit 结构体来存储提交信息
     #[derive(PartialEq, Default, Clone, Debug)]
     struct Commit {
         hash: String,
         message: String,
     }
    
     fn main() -> Result<()> {
         // 定义命令及参数
         let output = Command::new("git")
             .arg("log")
             .arg("--oneline")
             .output()?;
    
         // 检查命令是否成功执行
         if !output.status.success() {
             anyhow::bail!("Failed to execute git log");
         }
    
         // 定义正则表达式模式
         let pattern = Regex::new(
     r"(?x)
     ([0-9a-fA-F]+)
     (.*)")?;
    
         // 从 output.stdout 处理命令为 UTF8 输出
         String::from_utf8(output.stdout)?
             // 按行分割输出
             .lines()
             // 过滤符合正则表达式的行
             .filter_map(|line| pattern.captures(line))
             // 映射到 Commit 结构体,向下传递
             .map(|cap| {
                 Commit {
                     hash: cap[1].to_string(),
                     message: cap[2].trim().to_string(),
                 }
             })
             // 只取前 5 个结果
             .take(5)
             // 每个打印出来
             .for_each(|x| println!("{:?}", x));
    
         Ok(())
     }

    输出:

     Commit { hash: "c064ce7", message: "Ohno妈咪何意味" }
     Commit { hash: "54b4705", message: "a" }

    captures() 方法用于从输入字符串中提取与正则表达式模式匹配的子字符串。它返回一个 `Option

    `,其中 `Captures` 是一个包含所有捕获组的结构体。如果输入字符串与模式匹配,则返回 `Some(Captures)`,否则返回 `None`。
  87. 运行传递 stdin 的外部命令并检查错误代码

     use std::collections::HashSet;
     use std::io::Write;
     use std::process::{Command, Stdio};
     use anyhow::Result;
    
     fn main() -> Result<()> {
         let mut child = Command::new("python")
             .stdin(Stdio::piped())
             .stderr(Stdio::piped())
             .stdout(Stdio::piped())
             .spawn()?;
    
         child
             .stdin
             .as_mut()
             .unwrap()
             .write_all(b"import this\ncopyright()\ncredits()")?;
    
         let output = child.wait_with_output()?;
    
         if output.status.success() {
             let raw = String::from_utf8(output.stdout)?;
             let words = raw
                 .split_whitespace()
                 .collect::<HashSet<_>>();
             println!("Unique words from Python output: {}", words.len());
             for word in words {
                 println!("{}", word);
             }
         } else {
             let err = String::from_utf8(output.stderr)?;
             anyhow::bail!("Python script failed: {}", err);
         }
         Ok(())
     }

    输出:

     Unique words from Python output: 132
     Errors
     Reserved.
     those!
     silenced.
     ambiguity,
     Corporation
     never
     implicit.
     it
     Simple
     thousands
     of
     counts.
     ugly.
     at
     Beautiful
     break
     Centrum,
     the
     implementation
     development.
     never.
     Namespaces
     Foundation.
     first
     Python
     Amsterdam.
     Zope
     enough
     way
     beats
     only
     Flat
     1991-1995
     honking
     great
     one--
     Software
     CNRI,
     special
     Peters
     Explicit
     more
     In
     cast
     Copyright
     Zen
     explicitly
     complicated.
     Foundation,
     idea
     aren't
     Thanks
     (c)
     Rights
     often
     Tim
     by
     --
     be
     CWI,
     explain,
     Python,
     pass
     unless
     it.
     www.python.org
     Now
     are
     preferably
     better
     2001-2024
     information.
     Initiatives.
     guess.
     one
     for
     Sparse
     Special
     complex.
     Although
     There
     BeOpen.com.
     --obvious
     is
     1995-2001
     nested.
     dense.
     you're
     should
     All
     easy
     idea.
     Mathematisch
     refuse
     supporting
     If
     good
     Readability
     than
     may
     bad
     hard
     to
     let's
     practicality
     face
     purity.
     Unless
     do
     and
     *right*
     The
     Complex
     See
     obvious
     a
     silently.
     rules.
     Dutch.
     now.
     Research
     temptation
     Corporation,
     National
     cases
     Stichting
     it's
     not
     that
     2000
     BeOpen,

    Stdio::piped() 方法用于创建一个管道,以便在父进程和子进程之间进行通信。通过将标准输入、标准输出和标准错误设置为管道,父进程可以向子进程发送数据,并从子进程接收输出和错误信息。 as_mut() 将 `&mut Option

    ` 转为 `Option`,从而可以对 `Option` 内部的值进行可变借用操作。 `wait_with_output()` 方法用于等待子进程完成,并收集其标准输出和标准错误的内容。它返回一个 `Output` 结构体,包含子进程的退出状态、标准输出和标准错误。
  88. 将子进程 stdout 和 stderr 重定向到同一文件

     use std::fs::File;
     use std::io::Error;
     use std::process::{Command, Stdio};
    
     fn main() -> Result<(), Error> {
         let output = File::create("output.txt")?;
         let err = output.try_clone()?;
    
         Command::new("cmd")
             .args(&["/C", "dir", ".", "oops"])
             .stdout(Stdio::from(output))
             .stderr(Stdio::from(err))
             .spawn()?
             .wait_with_output()?;
    
         Ok(())
     }

    输出到 output.txt

      驱动器 C 中的卷是 OS
      卷的序列号是 A406-AC09
    
     C:\Users\ASUS\Documents\Rust_start\rust_cookbook\os\external 的目录
    
     2026/01/13  下午 10:03    
    <DIR>          .
     2026/01/13  下午 09:07    
    <DIR>          ..
     2026/01/13  下午 09:07                 8 .gitignore
     2026/01/13  下午 09:10             1,532 Cargo.lock
     2026/01/13  下午 09:10               115 Cargo.toml
     2026/01/13  下午 10:03                 0 output.txt
     2026/01/13  下午 09:07    
    <DIR>          src
     2026/01/13  下午 09:08    
    <DIR>          target
    
     C:\Users\ASUS\Documents\Rust_start\rust_cookbook\os\external 的目录
    
                 4 个文件          1,655 字节
                 4 个目录 375,073,021,952 可用字节
  89. 矩阵相加

     use ndarray::arr2;
    
     fn main() {
         let a = arr2(&[[1, 2, 3], [4, 5, 6]]);
         let b = arr2(&[[6, 5, 4], [3, 2, 1]]);
    
         let sum = &a + &b;
    
         println!("Sum:\n{}", sum);
     }
  90. 矩阵相乘

     use ndarray::arr2;
    
     fn main() {
         let a = arr2(&[[1, 2, 3], [4, 5, 6]]);
         let b = arr2(&[[6, 5], [4, 3], [2, 1]]);
    
         println!("Sum:\n{}", a.dot(&b));
     }
  91. 标量、向量、矩阵相乘

     use ndarray::{arr1, arr2, Array1};
    
     fn main() {
         let scalar = 7;
    
         let vector = arr1(&[1, 1, 4]);
    
         let matrix = arr2(&[[5, 1, 4], [1, 9, 1]]);
    
         let rec: Array1<_> = scalar * vector;
         println!("{}", rec);
         let rec_mat = matrix.dot(&rec);
         println!("{}", rec_mat);
     }

    数乘直接用 * 运算符即可。 矩阵或向量之间任意乘,能乘者用 dot() 方法。

  92. 向量的比较

     use ndarray::Array;
     use approx::assert_abs_diff_eq;
    
     fn main() {
         let a = Array::from(vec![1., 2., 3., 4., 5.]);
         let b = Array::from(vec![5., 4., 3., 2., 1.]);
         let mut c = a.clone();
         let d = b.clone();
    
         let z = a + b;
         let w = &c + &d;
    
         assert_abs_diff_eq!(z, Array::from(vec![6., 6., 6., 6., 6.]));
    
         println!("c = {}", c);
         c[0] = 10.;
         c[1] = 10.;
    
         assert_abs_diff_eq!(w, Array::from(vec![6., 6., 6., 6., 6.]));
    
         println!("w = {}", w);
     }

    输出:

     c = [1, 2, 3, 4, 5]
     w = [6, 6, 6, 6, 6]

    为什么不直接用 assert!() 判断是否相等? 在 Rust 中,直接使用 assert!() 来判断两个浮点数数组是否相等可能会导致问题,因为浮点数在计算过程中可能会出现微小的误差(见 27)。这些误差可能导致两个看似相等的浮点数实际上并不完全相等,从而使得 assert!() 断言失败。 为了解决这个问题,approx crate 提供了 assert_abs_diff_eq!() 宏,它允许我们指定一个容差范围,在这个范围内的浮点数被认为是相等的。这样,即使存在微小的计算误差,我们仍然可以正确地判断两个浮点数数组是否“足够接近”而不是完全相等。 因此,使用 assert_abs_diff_eq!() 可以提高代码的鲁棒性,避免由于浮点数精度问题导致的断言失败。 实际使用中如何获得判断结果并在其他地方读取呢? approx crate 还提供了 abs_diff_eq!() 宏,它返回一个布尔值,表示两个浮点数是否在指定的容差范围内相等。这样,我们可以在需要的地方使用该宏来获得判断结果,并根据结果执行相应的逻辑操作。例如:

    use approx::abs_diff_eq;
    
    fn main() {
        let a = 0.1 + 0.2;
        let b = 0.3;
        if abs_diff_eq!(a, b, epsilon = 1e-10) {
            println!("a and b are approximately equal");
        } else {
            println!("a and b are not equal");
        }
    }
  93. 向量的范数(长度)

     use ndarray::{array, Array1, ArrayView1};
    
     fn l1_norm(x: ArrayView1<f64>) -> f64 {
         x.fold(0., |acc, elem| acc + elem.abs())
     }
    
     fn l2_norm(x: ArrayView1<f64>) -> f64 {
         x.dot(&x).sqrt()
     }
    
     fn normalize(mut x: Array1<f64>) -> Array1<f64> {
         let norm = l2_norm(x.view());
         x.mapv_inplace(|e| e/norm);
         x
     }
    
     fn main() {
         let x = array![1., 2., 3., 4., 5.];
         println!("||x||_2 = {}", l2_norm(x.view()));
         println!("||x||_1 = {}", l1_norm(x.view()));
         println!("Normalizing x yields {:?}", normalize(x));
     }

    输出:

     ||x||_2 = 7.416198487095663
     ||x||_1 = 15

    范数的定义

    范数(Norm)是数学中用于衡量向量大小或长度的函数。它将向量映射到非负实数,满足以下性质:

    1. 非负性(Non-negativity):对于任意向量 x,范数 ||x|| ≥ 0,且当且仅当 x = 0 时,||x|| = 0。
    2. 齐次性(Homogeneity):对于任意标量 α 和向量 x,有 ||αx|| = |α| * ||x||。
    3. 三角不等式(Triangle Inequality):对于任意向量 x 和 y,有 ||x + y|| ≤ ||x|| + ||y||。 常见的范数类型包括:
    • L1 范数(曼哈顿范数):向量各元素绝对值之和,表示为 ||x||_1 = Σ |x_i|。
    • L2 范数(欧几里得范数):向量各元素平方和的平方根,表示为 ||x||_2 = sqrt(Σ x_i^2)。
    • 无穷范数(最大范数):向量各元素绝对值的最大值,表示为 ||x||_∞ = max |x_i|。
  94. 矩阵求逆

     use nalgebra::Matrix3;
    
     fn main() {
         let m1 = Matrix3::new(2.0, 1.0, 1.0, 3.0, 2.0, 1.0, 2.0, 1.0, 2.0);
         println!("Matrix m1:\n{}", m1);
         match m1.try_inverse() {
             Some(inv) => println!("Inverse of m1:\n{}", inv),
             None => println!("Matrix m1 is not invertible."),
         }
     }

    输出:

     Matrix m1:
    
     ┌       ┐
     │ 2 1 1 │
     │ 3 2 1 │
     │ 2 1 2 │
     └       ┘
    
     Inverse of m1:
    
     ┌          ┐
     │  3 -1 -1 │
     │ -4  2  1 │
     │ -1  0  1 │
     └          ┘

    逆的概念

    矩阵的逆(Inverse)是指对于一个方阵 A,如果存在另一个方阵 B,使得 A 与 B 的乘积等于单位矩阵 I(即 AB = BA = I),那么矩阵 B 就被称为矩阵 A 的逆,记作 A^(-1)。 只有当矩阵 A 是非奇异的(即行列式不为零)时,才存在逆矩阵。逆矩阵在解决线性方程组、计算矩阵分解和进行各种线性代数运算中具有重要作用。 try_inverse() 方法是如何计算逆的?

    try_inverse() 方法内核调用了 try_inverse_mut(),其求逆策略根据矩阵维度(维度为 $n$)而异:

    1. 小尺寸矩阵 ($n < 4$):使用显式的代数公式

      • 1x1: 直接计算 $1 / m_{11}$。
      • 2x2: 使用标准公式 $\frac{1}{ad-bc} \begin{pmatrix} d & -b \ -c & a \end{pmatrix}$。
      • 3x3: 使用克莱姆法则 (Cramer's rule),手工展开行列式和代数余子式的计算。
    2. 4x4 矩阵:使用高度优化的 MESA 算法(源自开源图形库)。通过循环展开 (Loop Unrolling) 硬编码了所有余子式和行列式的计算,性能极高。

    3. 大尺寸矩阵 ($n \ge 5$):使用通用的 LU 分解 (LU Decomposition)。通过将矩阵分解为下三角 ($L$) 和上三角 ($U$) 矩阵,再利用解线性方程组的方式求逆。

    提示:对于 $n \ge 5$ 的矩阵,由于使用了 LU 分解,即使求逆失败,原矩阵也可能被修改。而 $n < 5$ 的矩阵在求逆失败时通常能保持原样(因为使用了副本或显式公式)。

  95. (反)序列化矩阵

     use nalgebra::DMatrix;
    
     fn main() -> Result<(), Box<dyn std::error::Error>> {
         let row_slice: Vec<i32> = (1..5001).collect();
         let matrix = DMatrix::from_row_slice(50, 100, &row_slice);
    
         // 序列化矩阵
         let serialized_matrix = serde_json::to_string(&matrix)?;
    
         // 反序列化出矩阵
         let deserialized_matrix: DMatrix<i32> = serde_json::from_str(&serialized_matrix)?;
    
         // 验证反序列化出的矩阵 `deserialized_matrix` 等同于原始矩阵 `matrix`
         assert!(deserialized_matrix == matrix);
    
         Ok(())
     }
  96. 计算地球两点间距离

     fn main() {
         let earth_radius_kilometer = 6371.0_f64;
         let (paris_latitude_degrees, paris_longitude_degrees) = (48.85341_f64, 2.34880_f64);
         let (london_latitude_degrees, london_longitude_degrees) = (51.50853_f64, -0.12574_f64);
    
         let paris_latitude = paris_latitude_degrees.to_radians();
         let london_latitude = london_latitude_degrees.to_radians();
    
         let delta_latitude = (paris_latitude_degrees - london_latitude_degrees).to_radians();
         let delta_longitude = (paris_longitude_degrees - london_longitude_degrees).to_radians();
    
         let central_angle_inner = (delta_latitude / 2.0).sin().powi(2)
             + paris_latitude.cos() * london_latitude.cos() * (delta_longitude / 2.0).sin().powi(2);
         let central_angle = 2.0 * central_angle_inner.sqrt().asin();
    
         let distance = earth_radius_kilometer * central_angle;
    
         println!(
             "Distance between Paris and London on the surface of Earth is {:.1} kilometers",
             distance
         );
     }

    计算原理

    计算地球表面两点间的距离(即大圆距离)通常基于球面三角学。将地球近似为一个完美的球体,两点间的最短路径即为连接它们的大圆弧。本示例采用半正矢公式 (Haversine formula) 来实现,该公式在处理小距离时具有较好的数值稳定性。

    计算步骤如下:

    1. 弧度转换:将两点的经纬度从角度(Degrees)转换为弧度(Radians)。
    2. 计算差值:计算两点间的纬度差 $\Delta\phi$ 和经度差 $\Delta\lambda$。
    3. 应用半正矢公式:通过以下公式计算两点间的中央角 $\Delta\sigma$:

    $$\Delta\sigma = 2 \arcsin\left(\sqrt{\sin^2\left(\frac{\Delta\phi}{2}\right) + \cos\phi_1 \cos\phi_2 \sin^2\left(\frac{\Delta\lambda}{2}\right)}\right)$$

    其中 $\phi_1, \phi_2$ 分别是起点和终点的纬度。

    1. 求得距离:将中央角乘以地球半径 $R$:

    $$d = R \cdot \Delta\sigma$$

    其中地球平均半径 $R \approx 6371$ 公里。

  97. 创建复数与复数的计算

     use std::f64::consts::PI;
    
     use num::Complex;
    
     fn main() {
         let comp_int = Complex::new(10, 20);
         let complex_float = Complex::new(10.1, 20.1);
         let comp_add = Complex::new(5, 5);
    
         let sum = comp_int + comp_add;
    
         println!("Complex Float: {}", complex_float);
         println!("Sum of complex integers: {}", sum);
    
         let x = Complex::new(0.0, 2.0 * PI);
         let exp_x = x.exp();
    
         println!("e^(2πi) = {}", exp_x);
     }

    输出:

     Complex Float: 10.1+20.1i
     Sum of complex integers: 15+25i
     e^(2πi) = 1-0.00000000000000024492935982947064i

    欧拉公式

    欧拉公式(Euler's formula)是复分析中的一个重要公式,描述了复指数函数与三角函数之间的关系。它表明,对于任意实数 θ,有以下等式成立: $$e^{i\theta} = \cos(\theta) + i\sin(\theta)$$ 其中,$e$ 是自然对数的底数,$i$ 是虚数单位,满足 $i^2 = -1$。欧拉公式揭示了复指数函数与三角函数之间的深刻联系,是复数理论和信号处理等领域的重要基础。

    e^(2πi) 的含义

    根据欧拉公式,当 θ = 2π 时,有: $$e^{i \cdot 2\pi} = \cos(2\pi) + i\sin(2\pi) = 1 + 0i = 1$$ 这表明复指数函数在复平面上以单位圆为轨迹,绕原点旋转一整圈后回到起点 (1, 0)。因此,$e^{2\pi i}$ 的值为 1,表示在复平面上绕原点旋转 360 度后回到初始位置。

    数值误差解释

    在计算机中进行浮点数运算时,由于有限的精度和舍入误差,可能会导致结果与理论值存在微小的偏差。在上述代码中,计算 $e^{2\pi i}$ 时,理论上应得到 1,但由于浮点数运算的限制,实际结果可能会出现一个非常接近 0 的虚部(如 -0.00000000000000024492935982947064i)。这种误差通常是由于计算过程中涉及的三角函数和指数函数的近似计算引起的。尽管如此,这种误差在大多数应用中是可以忽略不计的,因为它非常小,不会对结果产生实质性的影响。

  98. 集中趋势的度量

    平均数

     fn main() {
         let data = [1, 1, 4, 5, 1, 4, 1, 9, 1, 9, 8, 1, 0];
    
         let sum = data.iter().sum::<i32>();
         let count = data.len() as i32;
    
         let mean = match count {
             positive if positive > 0 => Some(sum as f64 / count as f64),
             _ => None,
         };
    
         println!("Mean: {:?}", mean);
     }

    本示例使用了 iter() 方法来创建一个迭代器,遍历数组 data 中的每个元素。通过调用 sum() 方法,计算出数组中所有元素的总和。然后,使用 len() 方法获取数组的长度,即元素的数量。最后,通过将总和除以元素数量,计算出平均值(Mean)。为了避免除以零的情况,使用了 match 语句来检查元素数量是否大于零,如果是,则计算平均值并返回 Some(value),否则返回 None

    中位数

     use std::cmp::Ordering;
    
     fn partition(data: &[i32]) -> Option<(Vec<i32>, i32, Vec<i32>)> {
         match data.len() {
             0 => None,
             _ => {
                 let (pivot_slice, tail) = data.split_at(1);
                 let pivot = pivot_slice[0];
                 let (l, r) = tail.iter()
                     .fold((vec![], vec![]), |mut splits, next| {
                         let (l, r) = &mut splits;
                         if next < &pivot {
                             l.push(*next);
                         } else {
                             r.push(*next);
                         }
                         splits
                     });
                 Some((l, pivot, r))
             }
         }
     }
    
     fn select(data: &[i32], k: usize) -> Option<i32> {
         match partition(data) {
             None => None,
             Some((l, pivot, r)) => {
                 match k.cmp(&l.len()) {
                     Ordering::Less => select(&l, k),
                     Ordering::Equal => Some(pivot),
                     Ordering::Greater => select(&r, k - l.len() - 1),
                 }
             }
         }
     }
    
     fn median(data: &[i32]) -> Option<f64\> {
         match data.len() {
             0 => None,
             n if n % 2 == 1 => {
                 select(data, n / 2).map(|m| m as f64)
             }
             n => {
                 let mid1 = select(data, n / 2 - 1)?;
                 let mid2 = select(data, n / 2)?;
                 Some((mid1 as f64 + mid2 as f64) / 2.0)
             }
         }
    
     }
    
     fn main() {
         let data = [4, 5, 1, 4, 1, 9, 1, 9, 8, 1, 0, 8, 9, 6, 4, 9, 7, 6, 9, 9, 9];
    
         let part = partition(&data);
         println!("Partition: {:?}", part);
    
         let sel = select(&data, 5);
         println!("Select: {:?}", sel);
    
         let med = median(&data);
         println!("Median: {:?}", med);
     }

    本示例实现了计算中位数(Median)的功能。首先,定义了 partition 函数,用于将数据分割为小于、等于和大于基准值的三部分。然后,定义了 select 函数,使用递归方式选择第 k 小的元素。最后,定义了 median 函数,根据数据长度的奇偶性计算中位数。如果数据长度为奇数,则选择中间的元素;如果为偶数,则计算中间两个元素的平均值。通过这些函数的组合,实现了高效的中位数计算。

    标准差

     fn mean(data: &[i32]) -> Option<f32> {
         let sum = data.iter().sum::<i32>() as f32;
         let count = data.len();
    
         match count {
             positive if positive > 0 => Some(sum / count as f32),
             _ => None,
         }
     }
    
     fn std_deviation(data: &[i32]) -> Option<f32> {
         match (mean(data), data.len()) {
             (Some(data_mean), count) if count > 0 => {
                 let variance = data.iter().map(|value| {
                     let diff = data_mean - (*value as f32);
    
                     diff * diff
                 }).sum::<f32>() / count as f32;
    
                 Some(variance.sqrt())
             },
             _ => None
         }
     }
    
     fn main() {
         let data = [3, 1, 6, 1, 5, 8, 1, 8, 10, 11];
    
         let data_mean = mean(&data);
         println!("Mean is {:?}", data_mean);
    
         let data_std_deviation = std_deviation(&data);
         println!("Standard deviation is {:?}", data_std_deviation);
    
         let zscore = match (data_mean, data_std_deviation) {
             (Some(mean), Some(std_deviation)) => {
                 let diff = data[4] as f32 - mean;
    
                 Some(diff / std_deviation)
             },
             _ => None
         };
         println!("Z-score of data at index 4 (with value {}) is {:?}", data[4], zscore);
     }

    本示例实现了计算平均值(Mean)和标准差(Standard Deviation)的功能。首先,定义了 mean 函数,用于计算数据的平均值。然后,定义了 std_deviation 函数,使用平均值计算数据的方差,并取平方根得到标准差。最后,在 main 函数中,计算并打印了数据的平均值和标准差。此外,还计算了数据中某个特定索引处元素的 Z 分数(Z-score),用于衡量该元素相对于整体数据的偏离程度。

  99. 大数阶乘计算

     use num::bigint::{BigInt, ToBigInt};
    
     fn factorial(n: u32) -> BigInt {
         let mut result = 1.to_bigint().unwrap();
         for i in 1..=n {
             result *= i.to_bigint().unwrap();
         }
         result
     }
    
     fn main() {
         let num = 1000u32;
         let fact = factorial(num);
         println!("{}! = {}", num, fact);
     }

    输出:

     1000! = 402387260077093773543702433923003985719374864210714632543799910429938512398629020592044208486969404800479988610197196058631666872994808558901323829669944590997424504087073759918823627727188732519779505950995276120874975462497043601418278094646496291056393887437886487337119181045825783647849977012476632889835955735432513185323958463075557409114262417474349347553428646576611667797396668820291207379143853719588249808126867838374559731746136085379534524221586593201928090878297308431392844403281231558611036976801357304216168747609675871348312025478589320767169132448426236131412508780208000261683151027341827977704784635868170164365024153691398281264810213092761244896359928705114964975419909342221566832572080821333186116811553615836546984046708975602900950537616475847728421889679646244945160765353408198901385442487984959953319101723355556602139450399736280750137837615307127761926849034352625200015888535147331611702103968175921510907788019393178114194545257223865541461062892187960223838971476088506276862967146674697562911234082439208160153780889893964518263243671616762179168909779911903754031274622289988005195444414282012187361745992642956581746628302955570299024324153181617210465832036786906117260158783520751516284225540265170483304226143974286933061690897968482590125458327168226458066526769958652682272807075781391858178889652208164348344825993266043367660176999612831860788386150279465955131156552036093988180612138558600301435694527224206344631797460594682573103790084024432438465657245014402821885252470935190620929023136493273497565513958720559654228749774011413346962715422845862377387538230483865688976461927383814900140767310446640259899490222221765904339901886018566526485061799702356193897017860040811889729918311021171229845901641921068884387121855646124960798722908519296819372388642614839657382291123125024186649353143970137428531926649875337218940694281434118520158014123344828015051399694290153483077644569099073152433278288269864602789864321139083506217095002597389863554277196742822248757586765752344220207573630569498825087968928162753848863396909959826280956121450994871701244516461260379029309120889086942028510640182154399457156805941872748998094254742173582401063677404595741785160829230135358081840096996372524230560855903700624271243416909004153690105933983835777939410970027753472000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
  100. 验证并提取电子邮件信息

     use lazy_static::lazy_static;
     use regex::Regex;
    
     fn extract_login(input: &str) -> Option<&str> {
         lazy_static! {
             static ref RE: Regex = Regex::new(r"(?x)
     ^(?P
    <login>[^@\s]+)@
     ([[:word:]]+\.)*
     [[:word:]]+$
     ").unwrap();
         }
         RE.captures(input)
             .and_then(|cap| {
                 cap.name("login").map(|login| login.as_str())
             })
     }
    
     fn main() {
         let test = "shizuku@hotmail.com";
         println!("{:?}", extract_login(test));
         let test_invalid = "invalid-email-address^@^^@09zp.address";
         println!("{:?}", extract_login(test_invalid));
     }

    正则表达式的规则

    该正则表达式用于验证电子邮件地址的格式,并提取登录名部分。具体规则如下:

    • (?x):启用扩展模式,使正则表达式中的空白字符和注释被忽略,便于阅读和维护。
    • ^:匹配字符串的开头。
    • `(?P [^@\s]+)`:定义一个命名捕获组 "login",匹配一个或多个非 `@` 和非空白字符的字符序列,表示电子邮件地址的登录名部分。
    • @:匹配字符 @,作为登录名和域名的分隔符。
    • ([[:word:]]+\.)*:匹配零个或多个由字母、数字或下划线组成的子域名,后跟一个点号 .
    • [[:word:]]+:匹配由字母、数字或下划线组成的顶级域名部分。
    • $:匹配字符串的结尾。

    通过这些规则,该正则表达式能够有效地验证电子邮件地址的基本格式,并提取出登录名部分供后续使用。

  101. 提取 HashTag

     use lazy_static::lazy_static;
    
     use regex::Regex;
     use std::collections::HashSet;
    
     fn extract_hashtag(text: &str) -> HashSet<&str> {
         lazy_static! {
             static ref HASHTAG: Regex = Regex::new(
     r"\#[a-zA-z][0-9a-zA-z_]*"
             ).unwrap();
         }
         HASHTAG
             .find_iter(text)
             .map(|mat| mat.as_str())
             .collect()
     }
    
     fn main() {
         let tweet = "Hello #world, this is a #RustLang example with #regex and #lazy_static!";
         let hashtags = extract_hashtag(tweet);
         for tag in &hashtags {
             println!("{}", tag);
         }
         println!("Total unique hashtags: {}", hashtags.len());
     }
  102. 通过匹配正则表达式筛选日志文件

     use std::fs::File;
     use std::io::{BufRead, BufReader};
     use regex::RegexSetBuilder;
     use anyhow::Result;
    
     fn main() -> Result<()> {
         let log_path = "application.log";
         let buffered = BufReader::new(File::open(log_path)?);
    
         let set = RegexSetBuilder::new(&[
             r#"version "\d\.\d\.\d""#,
             r#"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:443"#,
             r#"warning.*timeout expired"#,
         ]).case_insensitive(true)
             .build()?;
    
         buffered
             .lines()
             .filter_map(|line| line.ok())
             .filter(|line| set.is_match(line.as_str()))
             .for_each(|x| println!("{x:?}"));
         Ok(())
     }

    随便写点日志:

    application.log

     hahahahah
     version "1.1.4" as important selector? 
     MAN!
     whatThe FUCK
    
     114.114.114.114:443 as Login
    
     ma?
    
     warning: unused import: `lazy_static::lazy_static`
     --> src/main.rs:2:5
     |
     warning: ASSHOLE timeout expired
     lalalsiz

    输出:

     "version \"1.1.4\" as important selector? "
     "114.114.114.114:443 as Login"
     "warning: ASSHOLE timeout expired"
  103. 文本模式替换

     use lazy_static::lazy_static;
    
     use std::borrow::Cow;
     use regex::Regex;
    
     fn reformat_dates<'a>(before: &'a str) -> Cow<'a, str> {
         lazy_static! {
             static ref ISO8601_DATE_REGEX : Regex = Regex::new(
                 r"(?P
    <y>\d{4})-(?P<m>\d{2})-(?P<d>\d{2})"
                 ).unwrap();
         }
         ISO8601_DATE_REGEX.replace_all(before, "$m/$d/$y")
     }
    
     fn main() {
         let before = "2012-03-14, 2013-01-15 and 2014-07-05";
         let after = reformat_dates(before);
         println!("Before: {}", before);
         println!("After:  {}", after);
     }

    此文本格式替换将 YYYY-MM-DD 格式的日期转换为 MM/DD/YYYY 格式。输出:

     Before: 2012-03-14, 2013-01-15 and 2014-07-05
     After:  03/14/2012, 01/15/2013 and 07/05/2014
  104. 收集 Unicode 字符

     use unicode_segmentation::UnicodeSegmentation;
    
     fn main() {
         let name = "José Guimarães\r\n";
         let chi = "χi";
         let mand = "汉字";
         let graphemes = UnicodeSegmentation::graphemes(name, true)
             .collect::<Vec<&str>>();
         println!("{:?}", graphemes);
         let chi_graphemes = UnicodeSegmentation::graphemes(chi, true)
             .collect::<Vec<&str>>();
         println!("{:?}", chi_graphemes);
         let mand_graphemes = UnicodeSegmentation::graphemes(mand, true)
             .collect::<Vec<&str>>();
         println!("{:?}", mand_graphemes);
     }

    输出:

     ["J", "o", "s", "é", " ", "G", "u", "i", "m", "a", "r", "ã", "e", "s", "\r\n"]
     ["χ", "i"]
     ["汉", "字"]
  105. 自定义结构体并实现 FromStr trait

     use std::str::FromStr;
    
     #[derive(Debug, PartialEq)]
     struct RGB {
         r: u8,
         g: u8,
         b: u8,
     }
    
     impl FromStr for RGB {
         type Err = std::num::ParseIntError;
    
         // 解析格式为 '#rRgGbB..' 的颜色十六进制代码
         // 将其转换为 'RGB' 实例
         fn from_str(hex_code: &str) -> Result<Self, Self::Err> {
    
             // u8::from_str_radix(src: &str, radix: u32) 
             // 将给定的字符串切片转换为 u8
             let r: u8 = u8::from_str_radix(&hex_code[1..3], 16)?;
             let g: u8 = u8::from_str_radix(&hex_code[3..5], 16)?;
             let b: u8 = u8::from_str_radix(&hex_code[5..7], 16)?;
    
             Ok(RGB { r, g, b })
         }
     }
    
     fn main() {
         let code: &str = &r"#fa7268";
         match RGB::from_str(code) {
             Ok(rgb) => {
                 println!(
                     r"The RGB color code is: R: {} G: {} B: {}",
                     rgb.r, rgb.g, rgb.b
                 );
             }
             Err(_) => {
                 println!("{} is not a valid color hex code!", code);
             }
         }
    
         // 测试 from_str 函数执行是否符合预期
         assert_eq!(
             RGB::from_str(&r"#fa7268").unwrap(),
             RGB {
                 r: 250,
                 g: 114,
                 b: 104
             }
         );
     }
  106. 从 HTML 网页提取所有链接

     use anyhow::Result;
     use select::document::Document;
     use select::predicate::Name;
    
     async fn get_href(url: &str) -> Result<Vec<String>> {
         let res = reqwest::get(url)
             .await?
             .text()
             .await?;
    
         let mut links = vec![];
    
         Document::from(res.as_str())
             .find(Name("a"))
             .filter_map(|n| n.attr("href"))
             .for_each(|link| links.push(link.to_string()));
    
         Ok(links)
     }
    
     #[tokio::main]
     async fn main() -> Result<()> {
         let url = "https://buuoj.cn/";
         let links = get_href(url).await?;
    
         println!("{:?}", links);
    
         Ok(())
     }

    输出:

     ["/", "/faq", "/match/matches", "#", "/links", "/resources", "/license", "/contact", "https://www.ctfwp.com/", "https://love.dasctf.com//#/user/register?invitecode=780JPB", "/notifications", "/users", "#", "/scoreboard", "/plugins/affiliation/affiliations", "/plugins/ctfd_scoreplus/categories\t", "#", "/challenges", "/plugins/new-challenges/challenges", "/plugins/new-challenges/solves", "/situation", "#", "/lang/en?next=https%3A%2F%2Fbuuoj.cn%2F", "/lang/zh?next=https%3A%2F%2Fbuuoj.cn%2F", "/register", "/login?next=https%3A%2F%2Fbuuoj.cn%2F", "https://buuoj.cn/das", "https://buuoj.cn/resources", "http://beian.miit.gov.cn/publish/query/indexFirst.action", "http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=45032302000077", "https://it.buu.edu.cn/", "https://www.dbappsecurity.com.cn/", "https://buuoj.cn/contact"]    
  107. 检查网页死链

     use anyhow::Result;
     use reqwest::StatusCode;
     use select::document::Document;
     use select::predicate::Name;
     use url::{Position, Url};
    
     async fn get_base_url(url: &str) -> Result<String> {
         let parsed_url = Url::parse(url)?;
         let base_url = parsed_url[..Position::BeforePath].to_string();
         Ok(base_url)
     }
    
     async fn check_link(url: &str) -> Result<StatusCode> {
         let response = reqwest::get(url).await?;
         Ok(response.status())
     }
    
     async fn get_href(url: &str) -> Result<Vec<String>> {
         let res = reqwest::get(url)
             .await?
             .text()
             .await?;
    
         let mut links = vec![];
    
         Document::from(res.as_str())
             .find(Name("a"))
             .filter_map(|n| n.attr("href"))
             .for_each(|link| links.push(link.to_string()));
    
         Ok(links)
     }
    
     #[tokio::main]
     async fn main() -> Result<()> {
         let url = "https://buuoj.cn/";
         let links = get_href(url).await?;
    
         for link in links {
             match get_base_url(&link).await {
                 Ok(_) => println!("Link: {}, Status: {}", get_base_url(&link).await?, check_link(get_base_url(&link).await?.as_ref()).await?),
                 Err(e) => println!("Invalid URL: {}, Error: {}", link, e),
             }
         }
    
         Ok(())
     }

    本实例只检查了链接的根 URL 是否有效,未处理相对路径链接。要做这些处理需要更多的代码逻辑,比如将相对路径转换为绝对路径等。多个连接需要使用不同套接字,可以使用 reqwest::Client 来实现连接池以提高效率。本测试没用 UserAgent,某些网站可能会拒绝请求,可以添加 UserAgent 来模拟浏览器请求。

    输出:

     Invalid URL: /, Error: relative URL without a base
     Invalid URL: /faq, Error: relative URL without a base
     Invalid URL: /match/matches, Error: relative URL without a base
     Invalid URL: #, Error: relative URL without a base
     Invalid URL: /links, Error: relative URL without a base
     Invalid URL: /resources, Error: relative URL without a base
     Invalid URL: /license, Error: relative URL without a base
     Invalid URL: /contact, Error: relative URL without a base
     Link: https://www.ctfwp.com, Status: 200 OK
     Link: https://love.dasctf.com, Status: 200 OK
     Invalid URL: /notifications, Error: relative URL without a base
     Invalid URL: /users, Error: relative URL without a base
     Invalid URL: #, Error: relative URL without a base
     Invalid URL: /scoreboard, Error: relative URL without a base
     Invalid URL: /plugins/affiliation/affiliations, Error: relative URL without a base  
     Invalid URL: /plugins/ctfd_scoreplus/categories , Error: relative URL without a base
     Invalid URL: #, Error: relative URL without a base
     Invalid URL: /challenges, Error: relative URL without a base
     Invalid URL: /plugins/new-challenges/challenges, Error: relative URL without a base 
     Invalid URL: /plugins/new-challenges/solves, Error: relative URL without a base     
     Invalid URL: /situation, Error: relative URL without a base
     Invalid URL: #, Error: relative URL without a base
     Invalid URL: /lang/en?next=https%3A%2F%2Fbuuoj.cn%2F, Error: relative URL without a base
     Invalid URL: /lang/zh?next=https%3A%2F%2Fbuuoj.cn%2F, Error: relative URL without a base
     Invalid URL: /register, Error: relative URL without a base
     Invalid URL: /login?next=https%3A%2F%2Fbuuoj.cn%2F, Error: relative URL without a base
     Link: https://buuoj.cn, Status: 200 OK
     Link: https://buuoj.cn, Status: 200 OK
     Link: http://beian.miit.gov.cn, Status: 521 <unknown status code>
     Link: http://www.beian.gov.cn, Status: 200 OK
     Link: https://it.buu.edu.cn, Status: 200 OK
     Link: https://www.dbappsecurity.com.cn, Status: 200 OK
     Link: https://buuoj.cn, Status: 200 OK
  108. 从 MediaWiki 标记页面提取所有唯一性链接

    由于 WikiPedia 强制要求 UserAgent,因此需要自定义 UserAgent 才能成功获取内容。同时,本示例也使用了 reqwest::Client

     use lazy_static::lazy_static;
     use regex::Regex;
     use std::borrow::Cow;
     use std::collections::HashSet;
     use std::error::Error;
    
     fn extract_links<'a>(content: &'a str) -> HashSet<Cow<'a, str>> {
         lazy_static! {
             static ref WIKI_REGEX: Regex = Regex::new(
                 r"(?x)
     \&lsqb;\&lsqb;(?P
    <internal>[^\&lsqb;\&rsqb;|]*)[^\&lsqb;\&rsqb;]*\&rsqb;\&rsqb;
     |
     (url=|URL\||\&lsqb;)(?P
    <external>http.*?)[ \|}]"
             )
             .unwrap();
         }
    
         let links: HashSet<_> = WIKI_REGEX
             .captures_iter(content)
             .map(|c| match (c.name("internal"), c.name("external")) {
                 (Some(val), None) => Cow::from(val.as_str().to_lowercase()),
                 (None, Some(val)) => Cow::from(val.as_str()),
                 _ => unreachable!(),
             })
             .collect();
    
         links
     }
    
     #[tokio::main]
     async fn main() -> Result<(), Box<dyn Error>> {
         let client = reqwest::Client::builder()
             .user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0")
             .build()?;
    
         let content = client
             .get("https://en.wikipedia.org/w/index.php?title=Rust_(programming_language)&action=raw")
             .send()
             .await?
             .text()
             .await?;
    
         println!("Content length: {}", content.len());
         if content.len() > 0 {
             println!("{:#?}", extract_links(content.as_str()));
         } else {
             println!("Received empty content.");
         }
    
         Ok(())
     }

    输出:

     Content length: 114759
     {                                                                                   
         "https://web.archive.org/web/20210923101530/https://blog.rust-lang.org/2015/05/11/traits.html",
         "https://web.archive.org/web/20250223193908/https://www.rust-lang.org/policies/licenses ",
         "imperative programming",
         "https://web.archive.org/web/20201028102013/https://doc.rust-lang.org/1.0.0/complement-lang-faq.html#how-fast-is-rust? ",
         "powerpc",
         "haiku (operating system)",
         "option type",
         "elm (programming language)",
         "https://www.theregister.com/2021/11/19/rust_foundation_ceo/ ",
         "stack overflow",
         "https://doc.rust-lang.org/reference/behavior-considered-undefined.html ",      
         "redox (operating system)",
         "macos",
         "iterator",
         "https://web.archive.org/web/20220630164523/https://doc.rust-lang.org/rustc/platform-support.html ",
         "algebraic data type",
         "swf",
         "programming language",
         "for loop",
         "https://lwn.net/Articles/1049831/ ",
         "c sharp (programming language)",
         "https://github.com/rust-lang/rust/pull/5412 ",
         "illumos",
         "https://web.archive.org/web/20160807075745/http://www.drdobbs.com/jvm/the-rise-and-fall-of-languages-in-2013/240165192 ",
         "kubernetes",
         "https://doi.org/10.1007/978-1-4842-6589-5_1 ",
         "office of the national cyber director",
         "https://web.archive.org/web/20251002151554/https://rust-lang.org/governance/ ",
         "smart pointer",
         "https://web.archive.org/web/20210122142035/https://www.infoq.com/news/2020/12/cpp-rust-interop-cxx/",
         "dynamic allocation",
         "standard library",
         "linux.conf.au",
         "https://web.archive.org/web/20241116192422/https://doc.rust-lang.org/rust-by-example/scope/lifetime.html ",
         "https://web.archive.org/web/20211125104022/https://www.infoworld.com/article/3113083/new-challenger-joins-rust-to-upend-c-language.html ",
         "https://www.cambridge.org/core/journals/proceedings-of-the-international-astronomical-union/article/what-can-the-programming-language-rust-do-for-astrophysics/B51B6DF72B7641F2352C05A502F3D881 ",
         "https://web.archive.org/web/20240202045212/https://bugzilla.mozilla.org/show_bug.cgi?id=680521 ",
         "https://web.archive.org/web/20200701105007/https://www.infoq.com/news/2020/06/deno-1-ready-production/ ",
         "object-oriented programming",
         "https://www.infoq.com/news/2020/12/cpp-rust-interop-cxx/",
         "lwn.net",
         "nature (journal)",
         "package manager",
         "erlang (programming language)",
         "request for comments",
         "mit license",
         "https://web.archive.org/web/20120124160628/https://mail.mozilla.org/pipermail/rust-dev/2012-January/001256.html ",
         "https://www.drdobbs.com/jvm/the-rise-and-fall-of-languages-in-2013/240165192 ",
         "https://doc.rust-lang.org/rust-by-example/scope/lifetime.html ",
         "brendan eich",
         "https://web.archive.org/web/20210408001446/https://arstechnica.com/gadgets/2021/04/google-is-now-writing-low-level-android-code-in-rust/ ",
         "https://blog.desdelinux.net/en/google-develops-a-new-bluetooth-stack-for-android-written-in-rust/ ",
         "techrepublic",
         "programming style",
         "static typing",
         "https://www.theregister.com/2021/11/23/rust_moderation_team_quits/ ",
         "dangling pointers",
         "msvc",
         "trait (computer programming)",
         "null pointer",
         "https://www.whitehouse.gov/oncd/briefing-room/2024/02/26/press-release-technical-report/ ",
         "content delivery network",
         "zdnet",
         "bracket#curly brackets",
         "web service",
         "idris (programming language)",
         "https://lwn.net/Articles/910762/ ",
         "file system",
         "https://github.com/rust-lang/rustc_codegen_gcc#Motivation ",
         "desktop environment",
         "http://venge.net/graydon/talks/intro-talk-2.pdf ",
         "class (programming)",
         "linting",
         "category:pattern matching programming languages",
         "category:software using the apache license",
         "limbo (programming language)",
         "bloomberg l.p.",
         "irc",
         "parsing",
         "https://web.archive.org/web/20230507053637/https://www.theregister.com/2023/04/17/rust_foundation_apologizes_trademark_policy/ ",
         "https://web.archive.org/web/20220714105527/https://www.zdnet.com/article/the-rust-programming-language-just-took-a-huge-step-forwards/ ",
         "https://github.com/rust-lang/rust/blob/master/COPYRIGHT ",
         "https://web.archive.org/web/20230513082735/https://www.theregister.com/2023/04/27/microsoft_windows_rust/ ",
         "category:articles with example rust code",
         "https://www.youtube.com/watch?v=oUIjG-y4zaA ",
         "ember.js",
         "dropbox",
         "autocomplete",
         "trait object (rust)",
         "https://blog.rust-lang.org/2023/06/20/introducing-leadership-council.html ",   
         "mountain view, california",
         "ieee 754",
         "https://www.cnet.com/tech/services-and-software/firefox-mozilla-gets-overhaul-in-a-bid-to-get-you-interested-again/ ",
         "https://web.archive.org/web/20170710194643/https://doc.rust-lang.org/nomicon/races.html",
         "https://web.archive.org/web/20220506040523/https://www.nature.com/articles/d41586-020-03382-2 ",
         "polkadot (cryptocurrency)",
         "https://books.google.com/books?id=0Vv6DwAAQBAJ",
         "https://www.zdnet.com/article/microsoft-opens-up-rust-inspired-project-verona-programming-language-on-github/",
         "servo (software)",
         "android open source project",
         "https://web.archive.org/web/20230722190056/http://github.com/rust-lang/rust/blob/master/COPYRIGHT ",
         "https://doc.rust-lang.org/stable/rust-by-example/std/box.html ",
         "http://www.infoworld.com/article/3046100/open-source-tools/rusts-redox-os-could-show-linux-a-few-new-tricks.html ",
         "http://nondot.org/sabre/ ",
         "category:free software projects",
         "https://web.archive.org/web/20220625140128/https://www.cambridge.org/core/journals/proceedings-of-the-international-astronomical-union/article/what-can-the-programming-language-rust-do-for-astrophysics/B51B6DF72B7641F2352C05A502F3D881 ",
         "https://web.archive.org/web/20240919102849/https://www.technologyreview.com/2023/02/14/1067869/rust-worlds-fastest-growing-programming-language/ ",
         "membership organization",
         "breaking changes",
         "p6 (microarchitecture)",
         "https://web.archive.org/web/20201107233444/https://doc.rust-lang.org/reference/procedural-macros.html ",
         "outline of the rust programming language",
         "binary number",
         "cloudflare",
         "category:high-level programming languages",
         "https://doc.rust-lang.org/book/ch18-02-trait-objects.html 18.2. Using Trait Objects That Allow for Values of Different Types]",
         "radix",
         "https://web.archive.org/web/20250118013136/https://www.whitehouse.gov/oncd/briefing-room/2024/02/26/press-release-technical-report/ ",
         "https://dlnext.acm.org/doi/abs/10.1145/3445814.3446724 ",
         "meta platforms",
         "https://web.archive.org/web/20210208212031/https://blog.mozilla.org/blog/2021/02/08/mozilla-welcomes-the-rust-foundation/",
         "ibm",
         "https://web.archive.org/web/20220625140128/https://www.infoq.com/presentations/rust-borrow-checker/ ",
         "proceedings of the international astronomical union",
         "https://dl.acm.org/doi/10.1145/3586037 ",
         "function (computer programming)",
         "v8 (javascript engine)",
         "functional programming",
         "npm",
         "typescript",
         "https://www.zdnet.com/article/rust-foundation-appoints-rebecca-rumbul-as-executive-director/",
         "nil (programming language)",
         "concurrency (computer science)",
         "type punning",
         "https://www.technologyreview.com/2023/02/14/1067869/rust-worlds-fastest-growing-programming-language/ ",
         "cryptocurrency",
         "structured programming",
         "text editor",
         "https://doc.rust-lang.org/rust-by-example/scope/lifetime/explicit.html ",      
         "mesa (programming language)",
         "bus (computing)#address bus",
         "variable shadowing",
         "https://web.archive.org/web/20210410161528/https://www.infoworld.com/article/3606774/rust-language-moves-to-independent-foundation.html",
         "https://doc.rust-lang.org/reference/procedural-macros.html ",
         "https://www.rust-lang.org/policies/licenses ",
         "https://web.archive.org/web/20221027030926/https://blog.rust-lang.org/inside-rust/2022/05/19/governance-update.html ",
         "http://repositorio.inesctec.pt/handle/123456789/5492 ",
         "https://web.archive.org/web/20190413075055/https://www.marksei.com/fedora-29-new-features-startis/ ",
         "category:procedural programming languages",
         "web browser",
         "data race",
         "dangling pointer",
         "java (programming language)",
         "https://lwn.net/Articles/547145/ ",
         "facebook",
         "type inference",
         "https://rust-lang.github.io/rust-clippy/master/index.html ",
         "https://mail.mozilla.org/pipermail/rust-dev/2012-January/001256.html ",        
         "category:functional languages",
         "https://web.archive.org/web/20160317215950/https://www.mozilla.org/en-US/firefox/45.0/releasenotes/ ",
         "https://www.computerworld.com/article/3137050/mozilla-plans-to-rejuvenate-firefox-in-2017.html ",
         "https://web.archive.org/web/20210408001446/https://arstechnica.com/gadgets/2021/04/google-is-now-writing-low-level-android-code-in-rust/",
         "#rustfmt",
         "https://web.archive.org/web/20220711001629/https://www.theregister.com/2021/11/30/aws_reinvent_rust/ ",
         "domain name",
         "https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html 4.2. References and Borrowing]",
         "https://www.theregister.co.uk/2018/06/27/microsofts_next_cloud_trick_kicking_things_out_of_the_cloud_to_azure_iot_edge/",
         "https://www.infoworld.com/article/3606774/rust-language-moves-to-independent-foundation.html",
         "http://docs.idris-lang.org/en/latest/reference/uniqueness-types.html ",        
         "programming paradigm",
         "gecko (software)#quantum",
         "\"hello, world!\" program",
         "higher-order function",
         "https://doi.org/10.1145/3377811.3380413 ",
         "calling convention",
         "safety properties",
         "https://security.googleblog.com/2023/01/supporting-use-of-rust-in-chromium.html ",
         "history of programming languages",
         "category:free and open source compilers",
         "rust for linux",
         "https://www.zdnet.com/article/programming-language-rust-mozilla-job-cuts-have-hit-us-badly-but-heres-how-well-survive/ ",
         "comparison of programming languages",
         "https://www.zdnet.com/article/aws-to-sponsor-rust-project/ ",
         "https://dl.acm.org/doi/10.1145/3158154 ",
         "ruby (programming language)",
         "energy efficiency in computing",
         "literal (computer programming)",
         "fuchsia (operating system)",
         "https://www.infoq.com/presentations/rust-borrow-checker/ ",
         "https://www.nature.com/articles/d41586-020-03382-2 ",
         "android (operating system)",
         "https://web.archive.org/web/20181121072557/http://docs.idris-lang.org/en/latest/reference/uniqueness-types.html ",
         "https://www.unicode.org/glossary/ ",
         "usenix",
         "https://www.rust-lang.org/",
         "stack-based memory allocation",
         "https://web.archive.org/web/20201017192915/https://www.bloomberg.com/news/articles/2020-10-17/ethereum-blockchain-killer-goes-by-unassuming-name-of-polkadot ",    
         "https://www.zdnet.com/article/the-rust-programming-language-just-took-a-huge-step-forwards/ ",
         "https://rustc-dev-guide.rust-lang.org/backend/codegen.html ",
         "inline expansion",
         "system software",
         "https://web.archive.org/web/20220624101013/https://www.infoworld.com/article/3218074/what-is-rust-safe-fast-and-easy-software-development.html ",
         "https://www.zdnet.com/article/microsoft-why-we-used-programming-language-rust-over-go-for-webassembly-on-kubernetes-app/ ",
         "https://web.archive.org/web/20231126231034/https://doc.rust-lang.org/reference/influences.html ",
         "abstract syntax tree",
         "https://therecord.media/memory-related-software-bugs-white-house-code-report-oncd ",
         "javascript",
         "general-purpose programming language",
         "#rust foundation",
         "octal",
         "mozilla",
         "arm architecture family",
         "https://www.theregister.com/2023/04/17/rust_foundation_apologizes_trademark_policy/ ",
         "united states",
         "https://web.archive.org/web/20201213020220/https://www.engadget.com/mozilla-firefox-250-employees-layoffs-151324924.html ",
         "https://github.com/rust-lang/rust/issues/25860 ",
         "standard ml",
         "microsoft azure",
         "https://www.theregister.com/2022/09/20/rust_microsoft_c/ ",
         "memory leak",
         "macro (computer science)",
         "record (computer science)",
         "https://techcrunch.com/2017/09/29/its-time-to-give-firefox-another-chance/ ",  
         "risc-v",
         "https://web.archive.org/web/20240717145511/https://www.theregister.com/2023/02/02/system76_cosmic_xfce_updates/ ",
         "https://web.archive.org/web/20220611034046/https://dl.acm.org/doi/10.1145/3102980.3103006 ",
         "https://www.engadget.com/mozilla-firefox-250-employees-layoffs-151324924.html ",
         "plan 9 from bell labs",
         "mit technology review",
         "microkernel",
         "https://www.theregister.com/2022/07/12/oracle_linux_9/ ",
         "hexadecimal",
         "https://www.theregister.com/2021/01/14/rust_os_theseus/ ",
         "mozilla foundation",
         "category:compiled programming languages",
         "https://web.archive.org/web/20220714112619/https://www.theregister.com/2021/01/14/rust_os_theseus/ ",
         "https://web.archive.org/web/20160910211537/https://techcrunch.com/2013/04/03/mozilla-and-samsung-collaborate-on-servo-mozillas-next-gen-browser-engine-for-tomorrows-multicore-processors/",
         "foreign function interface",
         "category:programming languages created in 2015",
         "haskell",
         "pattern matching",
         "syntax (programming languages)",
         "alef (programming language)",
         "https://web.archive.org/web/20220421043549/https://www.zdnet.com/article/microsoft-why-we-used-programming-language-rust-over-go-for-webassembly-on-kubernetes-app/ ",
         "channel (programming)",
         "https://www.cs.cmu.edu/~aldrich/papers/classic/tse12-typestate.pdf",
         "https://archive.today/20211226213836/http://venge.net/graydon/talks/intro-talk-2.pdf ",
         "https://blog.mozilla.org/blog/2021/02/08/mozilla-welcomes-the-rust-foundation ",
         "async/await",
         "https://rust-unofficial.github.io/too-many-lists/ ",
         "https://www.theregister.com/2023/01/12/google_chromium_rust/ ",
         "file:cargo clippy hello world example.png",
         "object lifetime",
         "result type",
         "ocaml",
         "https://www.mozilla.org/en-US/firefox/45.0/releasenotes/ ",
         "https://web.archive.org/web/20200117143852/https://www.zdnet.com/article/microsoft-opens-up-rust-inspired-project-verona-programming-language-on-github/",
         "file:cargo compiling.webm",
         "optimizing compiler",
         "https://web.archive.org/web/20210825165930/https://blog.desdelinux.net/en/google-develops-a-new-bluetooth-stack-for-android-written-in-rust/ ",
         "dr. dobb's journal",
         "https://web.archive.org/web/20210410031305/https://www.zdnet.com/article/awss-shane-miller-to-head-the-newly-created-rust-foundation/",
         "category:multi-paradigm programming languages",
         "system76",
         "https://www.infoq.com/news/2020/06/deno-1-ready-production/ ",
         "trademark",
         "x86-64",
         "category:statically typed programming languages",
         "computer performance",
         "linux",
         "conditional expression",
         "discord",
         "pointer (computer programming)",
         "https://web.archive.org/web/20220531114141/https://doc.rust-lang.org/stable/rust-by-example/std/box.html ",
         "https://dl.acm.org/doi/10.1145/3136014.3136031 ",
         "backward compatibility",
         "duck typing",
         "https://web.archive.org/web/20180924092749/http://www.unicode.org/glossary/ ", 
         "#traits",
         "https://web.archive.org/web/20230513020437/https://www.computerworld.com/article/3137050/mozilla-plans-to-rejuvenate-firefox-in-2017.html ",
         "https://dl.acm.org/doi/10.1145/3428204 ",
         "nonprofit organization",
         "webassembly",
         "napier88",
         "aarch64",
         "https://doc.rust-lang.org/rust-by-example/scope/raii.html",
         "zero-copy",
         "instant messaging",
         "https://blog.rust-lang.org/2020/08/18/laying-the-foundation-for-rusts-future.html ",
         "category:software using the mit license",
         "https://web.archive.org/web/20230320172900/https://www.techrepublic.com/article/the-rust-programming-language-now-has-its-own-independent-foundation/ ",
         "language server protocol",
         "sather",
         "algebraic data types",
         "ruffle (software)",
         "unicode codepoint",
         "https://doi.org/10.1145/3102980.3103006 ",
         "open sourced",
         "https://doc.rust-lang.org/stable/std/primitive.usize.html",
         "amazon web services",
         "linux on ibm z",
         "utility software",
         "unicode consortium",
         "https://blog.rust-lang.org/2015/05/11/traits.html",
         "github",
         "memory safety",
         "https://www.bloomberg.com/news/articles/2020-10-17/ethereum-blockchain-killer-goes-by-unassuming-name-of-polkadot ",
         "list of programming languages",
         "https://github.com/vlang/v/blob/master/doc/docs.md#introduction ",
         "git",
         "cnet",
         "huawei",
         "https://web.archive.org/web/20230815025149/https://techcrunch.com/2017/09/29/its-time-to-give-firefox-another-chance/ ",
         "https://foundation.rust-lang.org/news/2021-02-08-hello-world/ ",
         "https://www.theregister.com/2023/12/01/9front_humanbiologics/ ",
         "category:mozilla",
         "ascii",
         "indentation style",
         "computerworld",
         "spark (programming language)",
         "signedness",
         "reference counting",
         "intermediate representation",
         "https://www.bleepingcomputer.com/news/software/this-flash-player-emulator-lets-you-securely-play-your-old-games/",
         "concurrent computing",
         "mips architecture",
         "software fault isolation",
         "file:rust standard libraries.svg",
         "https://web.archive.org/web/20201202022933/https://blog.rust-lang.org/2020/08/18/laying-the-foundation-for-rusts-future.html ",
         "function call",
         "https://doc.rust-lang.org/reference/attributes/codegen.html ",
         "dynamic dispatch",
         "http://reviews.cnet.com/8301-3514_7-57577639/samsung-joins-mozillas-quest-for-rust/ ",
         "scripting language",
         "https://web.archive.org/web/20190927092433/https://www.theregister.co.uk/2018/06/27/microsofts_next_cloud_trick_kicking_things_out_of_the_cloud_to_azure_iot_edge/",
         "netbsd",
         "https://web.archive.org/web/20160321192838/http://www.infoworld.com/article/3046100/open-source-tools/rusts-redox-os-could-show-linux-a-few-new-tricks.html ",     
         "comment (computer programming)",
         "https://doc.rust-lang.org/reference/macros-by-example.html ",
         "https://github.com/rust-lang/rustc_codegen_cranelift ",
         "double precision float",
         "https://web.archive.org/web/20230513161542/https://umbrella.cisco.com/blog/zeromq-helping-us-block-malicious-domains ",
         "https://rustmagazine.org/issue-1/2022-review-the-adoption-of-rust-in-business/ ",
         "newsqueak",
         "method call",
         "web proxy",
         "bleeping computer",
         "build system (software development)",
         "https://blog.rust-lang.org/2016/08/10/Shape-of-errors-to-come.html ",
         "c (programming language)",
         "https://www.infoworld.com/article/3113083/new-challenger-joins-rust-to-upend-c-language.html ",
         "google",
         "chromium (web browser)",
         "typestate analysis",
         "self-hosting (compilers)",
         "https://web.archive.org/web/20220714073400/https://www.theregister.com/2022/07/12/oracle_linux_9/ ",
         "destructor (computer programming)",
         "#standard library",
         "rust (fungus)",
         "https://www.marksei.com/fedora-29-new-features-startis/ ",
         "https://www.theregister.com/2022/06/23/linus_torvalds_rust_linux_kernel/ ",    
         "https://doc.rust-lang.org/stable/std/primitive.isize.html",
         "https://doc.rust-lang.org/reference/influences.html ",
         "field (computer science)",
         "category:rust (programming language)",
         "https://www.usenix.org/conference/osdi20/presentation/boos ",
         "https://github.com/rust-lang/rust/milestone/3?closed=1 ",
         "infoworld",
         "https://blog.rust-lang.org/inside-rust/2022/05/19/governance-update.html ",    
         "scheme (programming language)",
         "https://web.archive.org/web/20190421131142/https://doc.rust-lang.org/rust-by-example/scope/raii.html",
         "https://web.archive.org/web/20250807061432/https://bitfieldconsulting.com/posts/rust-errors-option-result ",
         "https://web.archive.org/web/20230513164135/https://www.usenix.org/conference/osdi20/presentation/boos ",
         "32-bit computing",
         "https://web.archive.org/web/20230113004438/https://security.googleblog.com/2023/01/supporting-use-of-rust-in-chromium.html ",
         "https://www.infoworld.com/article/2336216/white-house-urges-developers-to-dump-c-and-c.html ",
         "https://www.infoworld.com/article/3218074/what-is-rust-safe-fast-and-easy-software-development.html ",
         "https://www.darpa.mil/research/programs/translating-all-c-to-rust ",
         "generic programming",
         "floating point",
         "https://counterexamples.org/nearly-universal.html ",
         "linux kernel developers summit",
         "volatile (computer programming)",
         "https://web.archive.org/web/20220728221531/https://www.theregister.com/2022/06/23/linus_torvalds_rust_linux_kernel/ ",
         "list of programming languages by type",
         "strongly typed",
         "apache license",
         "https://web.archive.org/web/20221009202615/https://doc.rust-lang.org/reference/attributes/codegen.html ",
         "undefined behavior",
         "https://web.archive.org/web/20230531035222/https://rustc-dev-guide.rust-lang.org/overview.html ",
         "statically typed",
         "hermes (programming language)",
         "compiler",
         "https://web.archive.org/web/20220318025804/https://www.zdnet.com/article/mozilla-lays-off-250-employees-while-it-refocuses-on-commercial-products/ ",
         "https://web.archive.org/web/20201101145703/https://www.rust-lang.org/learn/get-started#ferris ",
         "while loop",
         "the register",
         "doubly linked list",
         "integer type",
         "linux kernel",
         "https://www.usenix.org/publications/loginonline/empirical-study-rust-linux-success-dissatisfaction-and-compromise ",
         "https://github.com/Speykious/cve-rs ",
         "llvm",
         "variable (computer science)",
         "boolean type",
         "domain name system",
         "https://www.theregister.com/2023/04/27/microsoft_windows_rust/ ",
         "universal character set characters#surrogates",
         "lock (computer science)",
         "type safety",
         "nominal type system",
         "https://web.archive.org/web/20220421083509/https://www.zdnet.com/article/programming-language-rust-mozilla-job-cuts-have-hit-us-badly-but-heres-how-well-survive/ ",
         "standard output",
         "https://web.archive.org/web/20130426010754/http://lwn.net/Articles/547145/ ",  
         "https://doi.org/10.1007/978-1-4842-5599-5_1 ",
         "https://web.archive.org/web/20231117141103/https://lwn.net/Articles/910762/ ", 
         "gecko (software)",
         "https://dl.acm.org/doi/10.1145/2959689.2960081 ",
         "integrated development environment",
         "reference (computer science)",
         "https://web.archive.org/web/20220712060927/https://dlnext.acm.org/doi/abs/10.1145/3445814.3446724 ",
         "executive director",
         "object code",
         "ios",
         "https://www.zdnet.com/article/mozilla-lays-off-250-employees-while-it-refocuses-on-commercial-products/ ",
         "v (programming language)",
         "compilation error",
         "polymorphism (computer science)",
         "alphabet inc.",
         "https://web.archive.org/web/20240415053627/https://dl.acm.org/doi/10.1145/3443420 ",
         "swift (programming language)",
         "https://www.rust-lang.org/governance ",
         "https://doi.org/10.1145/3611096.3611097 ",
         "cyclone (programming language)",
         "zig (programming language)",
         "https://bugzilla.mozilla.org/show_bug.cgi?id=680521 ",
         "toolchain",
         "project verona",
         "engadget",
         "code of conduct",
         "semicolon#programming",
         "category:concurrent programming languages",
         "learning curve",
         "chairperson",
         "freebsd",
         "https://web.archive.org/web/20130404142333/http://reviews.cnet.com/8301-3514_7-57577639/samsung-joins-mozillas-quest-for-rust/ ",
         "https://arstechnica.com/gadgets/2021/04/google-is-now-writing-low-level-android-code-in-rust/ ",
         "token (parser)",
         "https://www.extremetech.com/internet/115207-mozilla-releases-rust-0-1-the-language-that-will-eventually-usurp-firefoxs-c ",
         "array (data structure)",
         "windows",
         "buffer overflow",
         "https://github.com/rust-lang/rust-clippy ",
         "https://web.archive.org/web/20231119135434/https://www.infoq.com/news/2019/03/rust-npm-performance/ ",
         "blockchain",
         "browser engine",
         "scope (computer science)",
         "https://web.archive.org/web/20181225175312/http://nondot.org/sabre/ ",
         "https://www.infoq.com/news/2019/03/rust-npm-performance/ ",
         "https://www.theregister.com/2023/02/02/system76_cosmic_xfce_updates/ ",        
         "firecracker (software)",
         "list of rust software and tools",
         "https://github.com/graydon/rust-prehistory/tree/master ",
         "clu (programming language)",
         "https://www.theregister.com/2023/05/05/modular_struts_its_mojo_a/ ",
         "https://web.archive.org/web/20220714093245/https://www.theregister.com/2021/11/23/rust_moderation_team_quits/ ",
         "single precision float",
         "monomorphization",
         "double free",
         "https://rustc-dev-guide.rust-lang.org/overview.html ",
         "code formatter",
         "deno (software)",
         "cross-platform software",
         "opendns",
         "internet of things",
         "https://web.archive.org/web/20220714172029/https://www.cnet.com/tech/services-and-software/firefox-mozilla-gets-overhaul-in-a-bid-to-get-you-interested-again/ ",  
         "type system",
         "samsung",
         "gnu compiler collection",
         "https://dl.acm.org/doi/10.1145/3443420 ",
         "queer community",
         "https://web.archive.org/web/20160915133745/https://blog.rust-lang.org/2016/08/10/Shape-of-errors-to-come.html ",
         "stratis (configuration daemon)",
         "file synchronization",
         "crustacean",
         "https://www.theregister.com/2021/11/30/aws_reinvent_rust/ ",
         "microsoft",
         "affine type",
         "union type",
         "https://www.zdnet.com/article/rust-takes-a-major-step-forward-as-linuxs-second-official-language/ ",
         "generic function",
         "darpa",
         "https://web.archive.org/web/20211118062346/https://www.zdnet.com/article/rust-foundation-appoints-rebecca-rumbul-as-executive-director/",
         "#safety",
         "strong and weak typing",
         "bounded parametric polymorphism",
         "https://www.infoq.com/news/2019/04/rust-1.34-additional-registries ",
         "firefox",
         "immutable object",
         "https://web.archive.org/web/20220419124635/https://foundation.rust-lang.org/news/2021-02-08-hello-world/ ",
         "covid-19 pandemic",
         "heise online",
         "statement (computer science)",
         "https://doc.rust-lang.org/1.0.0/complement-lang-faq.html#how-fast-is-rust? ",  
         "bloomberg news",
         "c++",
         "mojo (programming language)",
         "https://doc.rust-lang.org/std/primitive.char.html ",
         "https://survey.stackoverflow.co/2025/technology ",
         "https://web.archive.org/web/20211225124131/https://www.bleepingcomputer.com/news/software/this-flash-player-emulator-lets-you-securely-play-your-old-games/",      
         "https://web.archive.org/web/20220714164454/https://www.infoq.com/news/2019/04/rust-1.34-additional-registries ",
         "category:systems programming languages",
         "https://doc.rust-lang.org/rustc/platform-support.html ",
         "garbage collection (computer science)",
         "techcrunch",
         "beta (programming language)",
         "https://bitfieldconsulting.com/posts/rust-errors-option-result ",
         "stable release",
         "https://doc.rust-lang.org/nomicon/races.html",
         "overhead (computing)",
         "https://web.archive.org/web/20230421052332/https://doc.rust-lang.org/reference/macros-by-example.html ",
         "file:crates.io website.png",
         "executable",
         "white house",
         "https://techcrunch.com/2013/04/03/mozilla-and-samsung-collaborate-on-servo-mozillas-next-gen-browser-engine-for-tomorrows-multicore-processors/",
         "cisco",
         "cranelift",
         "https://www.rust-lang.org/learn/get-started#ferris ",
         "https://www.techrepublic.com/article/the-rust-programming-language-now-has-its-own-independent-foundation/ ",
         "affine type system",
         "static dispatch",
         "linker (computing)",
         "bugzilla",
         "https://blog.rust-lang.org/2025/02/13/2024-State-Of-Rust-Survey-results.html ",
         "https://umbrella.cisco.com/blog/zeromq-helping-us-block-malicious-domains ",   
         "https://web.archive.org/web/20220714110957/https://www.theregister.com/2021/11/19/rust_foundation_ceo/ ",
         "https://www.heise.de/en/background/Programming-language-Rust-2024-is-the-most-comprehensive-edition-to-date-10393917.html ",
         "https://www.zdnet.com/article/awss-shane-miller-to-head-the-newly-created-rust-foundation/",
     }
  109. 解析 URL 字符串为 Url 类型

     use url::{Url, ParseError};
    
     fn main() -> Result<(), ParseError> {
         let s = "https://shizukuaqua.top/privacy-policy/";
    
         let parsed = Url::parse(s)?;
         println!("Parsed URL: {}", parsed);
    
         Ok(())
     }
  110. 通过移除路径段创建根 URL

     use url::Url;
     use anyhow::Result;
    
     fn main() -> Result<()> {
         let full = "https://shizukuaqua.top/privacy-policy/?id=114514";
         let url = Url::parse(full)?;
         let base = base_url(url)?;
         println!("Base URL: {}", base);
         Ok(())
     }
    
     fn base_url(mut url: Url) -> Result<Url> {
         match url.path_segments_mut() {
             Ok(mut path) => {
                 path.clear();
             }
             Err(_) => {
                 return Err(anyhow::anyhow!("Cannot be base"));
             }
         }
    
         url.set_query(None);
    
         Ok(url)
     }
  111. 通过根 URL 创建新的 URL

     use anyhow::{Ok, Result};
     use url::Url;
    
     fn main() -> Result<()> {
         let path = "/privacy-policy/";
         let base = "https://shizukuaqua.top";
    
         let full_url = build_full_url(base, path)?;
         println!("Full URL: {}", full_url);
    
         Ok(())
     }
    
     fn build_full_url(base: &str, path: &str) -> Result<Url> {
         let base = Url::parse(base).expect("Failed while parsing");
         let full_url = base.join(path)?;
    
         Ok(full_url)
     }
  112. 提取 URL 源

    这是针对 RFC 6454 中“源”的实现。

     use url::Url;
     use anyhow::{Result, Ok};
    
     fn main() -> Result<()> {
         let s = "ftp:/rest-lang.org/examples";
    
         let url = Url::parse(s)?;
    
         println!("Scheme: {}", url.scheme());
         println!("Host: {:?}", url.host());
         println!("Port: {:?}", url.port_or_known_default());
         println!("Origin: {:?}", url.origin());
    
         Ok(())
     }

    输出:

     Scheme: ftp
     Host: Some(Domain("rest-lang.org"))
     Port: Some(21)
     Origin: Tuple("ftp", Domain("rest-lang.org"), 21)
  113. 从 URL 移除片段标识符和查询对

     use url::{Url, Position};
     use anyhow::Result;
    
     fn main() -> Result<()> {
         let parsed = Url::parse("https://github.com/rust-lang/rust/issues?labels=E-easy&state=open")?;
         let cleaned: &str = &parsed[..Position::AfterPath];
         println!("Cleaned URL: {}", cleaned);
    
         Ok(())
     }
  114. 从字符串获取 MIME 类型

     use mime::{Mime, APPLICATION_OCTET_STREAM};
    
     fn main() {
         let invalid_mimetype = "オナニー";
         let default_mime = invalid_mimetype
             .parse::<Mime>()
             .unwrap_or(APPLICATION_OCTET_STREAM);
    
         println!("MIME: {}, default_mime: {}", invalid_mimetype, default_mime);
    
         let valid_mimetype = "text/css";
         let parsed_mime = valid_mimetype
             .parse::<Mime>()
             .unwrap_or(APPLICATION_OCTET_STREAM);
    
         println!("MIME: {}, parsed_mime: {}", valid_mimetype, parsed_mime);
     }
  115. 从文件名获取 MIME 类型

     use mime::Mime;
    
     fn find_mimetype(filename: &String) -> Mime {
         let parts: Vec<&str> = filename.split(".").collect();
    
         let res = match parts.last() {
             Some(v) => {
                 match *v {
                     "html" => mime::TEXT_HTML,
                     "css" => mime::TEXT_CSS,
                     "js" => mime::TEXT_JAVASCRIPT,
                     "png" => mime::IMAGE_PNG,
                     "pdf" => mime::APPLICATION_PDF,
                     "jpg" | "jpeg" => mime::IMAGE_JPEG,
                     "gif" => mime::IMAGE_GIF,
                     "json" => mime::APPLICATION_JSON,
                     "xml" => mime::TEXT_XML,
                     _ => mime::TEXT_PLAIN,
                 }
             },
             None => mime::TEXT_PLAIN,
         };
         return res;
     }
    
     fn main() {
         let filenames = vec!["index.html", "styles.css", "script.js", "image.png", "document.pdf", "unknownfile.xyz"];
         for file in filenames {
             let filename = String::from(file);
             let mimetype = find_mimetype(&filename);
             println!("Filename: {}, MIME type: {}", filename, mimetype);
    
         }
     }
  116. 获取 HTTP 响应的 MIME 类型

     use anyhow::{Ok, Result};
     use mime::Mime;
     use std::str::FromStr;
     use reqwest::header::CONTENT_TYPE;
    
     #[tokio::main]
     async fn main() -> Result<()> {
         let response = reqwest::get("https://www.rust-lang.org/logos/rust-logo-32x32.png").await?;
         let headers = response.headers();
    
         let _ = match headers.get(CONTENT_TYPE) {
             Some(ct) => {
                 let ct = Mime::from_str(ct.to_str()?)?;
                 let mediatype = match (ct.type_(), ct.subtype()) {
                     (mime::IMAGE, mime::PNG) => "This is a PNG image.",
                     (mime::IMAGE, mime::JPEG) => "This is a JPEG image.",
                     (mime::TEXT, mime::HTML) => "This is an HTML document.",
                     (mime::APPLICATION, mime::JSON) => "This is a JSON document.",
                     _ => "Unknown content type.",
                 };
                 println!("Content-Type: {}, {}", ct, mediatype);
                 Ok(())
             }
             None => {
                 println!("Content-Type header not found.");
                 Ok(())
             },
         };
    
         Ok(())
     }

    需启用 tokio 的异步运行时支持:

     tokio = { version = "1.49.0", features = ["macros", "rt-multi-thread"] }
  117. 发出 HTTP GET 请求

     use anyhow::Result;
     use std::io::Read;
    
     fn main() -> Result<()> {
         let mut res = reqwest::blocking::get("http://httpbin.org/get")?;
    
         let mut body = String::new();
    
         res.read_to_string(&mut body)?;
    
         println!("Status: {}", res.status());
         println!("Headers:\n{:#?}", res.headers());
         println!("Body: {}", body);
    
         Ok(())
     }

    需要启用 blocking 功能:

     reqwest = { version = "0.13.1", features = ["blocking"] }

    输出:

     Status: 200 OK
     Headers:
     {
         "connection": "close",
         "content-length": "261",
         "access-control-allow-credentials": "true",
         "access-control-allow-origin": "*",
         "content-type": "application/json",
         "date": "Sat, 17 Jan 2026 00:43:04 GMT",
         "server": "gunicorn/19.9.0",
     }
     Body: {
     "args": {},
     "headers": {
         "Accept": "*/*",
         "Host": "httpbin.org",
         "User-Agent": "Go-http-client/1.1",
         "X-Amzn-Trace-Id": "Root=1-696adb18-5e9f087c38c169ea6781f511"
     },
     "origin": "91.243.81.108",
     "url": "http://httpbin.org/get"
     }

    也可以使用异步版本:

     use anyhow::{Ok, Result};
    
     #[tokio::main]
     async fn main() -> Result<()> {
         let res = reqwest::get("http://httpbin.org/get").await?;
    
         println!("Status: {}", res.status());
         println!("Headers:\n{:#?}", res.headers());
         let body = res.text().await?;
         println!("Body: {}", body);
    
         Ok(())
     }

    需启用 tokio 的异步运行时支持:

     tokio = { version = "1.49.0", features = ["macros", "rt-multi-thread"] }
  118. 为 REST 请求设置自定义消息标头和 URL 参数

     use serde::Deserialize;
     use anyhow::Result;
     use std::collections::HashMap;
     use url::Url;
     use reqwest::blocking::Client;
     use reqwest::header::{HeaderMap, HeaderValue, USER_AGENT};
    
     #[derive(Deserialize, Debug)]
     pub struct HeadersEcho {
         pub headers: HashMap<String, String>,
     }
    
     fn main() -> Result<()> {
         let url = Url::parse_with_params("http://httpbin.org/headers",
                                         &[("lang", "rust"), ("browser", "servo")])?;
    
         let mut headers = HeaderMap::new();
         headers.insert(USER_AGENT, HeaderValue::from_static("Rust-test"));
         headers.insert("Authorization", HeaderValue::from_static("Bearer DEadBEEfc001cAFeEDEcafBAd"));
         headers.insert("X-Powered-By", HeaderValue::from_static("Shizuku"));
    
         let response = Client::new()
             .get(url)
             .headers(headers)
             .send()?;
    
         let out: HeadersEcho = response.json()?;
         assert_eq!(out.headers["Authorization"],
                 "Bearer DEadBEEfc001cAFeEDEcafBAd");
         assert_eq!(out.headers["User-Agent"], "Rust-test");
         assert_eq!(out.headers["X-Powered-By"], "Shizuku");
    
         println!("{:?}", out);
         Ok(())
     }

    输出:

     HeadersEcho { headers: {"Authorization": "Bearer DEadBEEfc001cAFeEDEcafBAd", "Host": "httpbin.org", "User-Agent": "Rust-test", "X-Powered-By": "Guybrush Threepwood", "X-Amzn-Trace-Id": "Root=1-696ae0f2-64e3692c671675a905ec4493", "Accept": "*/*"} }

    这个例子展示了如何使用 reqwest 库发出带有自定义头和 URL 参数的 HTTP GET 请求,并将响应解析为结构体。现使用 HeaderMap 来设置多个自定义头。

  119. 查询 GitHub API

     use serde::Deserialize;
     use reqwest::Error;
    
     #[derive(Deserialize, Debug)]
     struct User {
         login: String,
         id: u32,
     }
    
     #[tokio::main]
     async fn main() -> Result<(), Error> {
         let req_url = format!(
             "https://api.github.com/repos/{owner}/{repo}/stargazers",
             owner = "rust-lang",
             repo = "rust"
         );
         println!("Requesting URL: {}", req_url);
    
         let client = reqwest::Client::new();
         let resp = client
             .get(&req_url)
             .header("User-Agent", "Rust-API-Client")
             .header("Accept", "application/vnd.github.v3+json")
             .send()
             .await?;
    
         let users: Vec<User> = resp.json().await?;
    
         for i in users {
             println!("User login: {}, id: {}", i.login, i.id);
         }
    
         Ok(())
     }

    注意启用 features:

     [dependencies]
     reqwest = { version = "0.13.1", features = ["json"] }
     serde = { version = "1.0.228", features = ["serde_derive"] }
     tokio = { version = "1.49.0", features = ["macros", "rt-multi-thread"] }

    注意:需要添加User-Agent头,否则GitHub API会返回403错误。

  120. 找以一个 GitHub 用户

     use reqwest::Result;
     use std::time::Duration;
     use reqwest::ClientBuilder;
    
     #[tokio::main]
     async fn main() -> Result<()> {
         let user = "ljhflag";
         let request_url = format!("https://api.github.com/users/{}", user);
         println!("{}", request_url);
    
         let timeout = Duration::new(5, 0);
         let client = ClientBuilder::new().timeout(timeout).build()?;
         let response = client
             .head(&request_url)
             .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0")
             .header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
             .send()
             .await?;
    
         if response.status().is_success() {
             println!("{} is a user!", user);
    
         } else {
             println!("{} is not a user!", user);
         }
    
         Ok(())
     }
  121. 下载资源到临时目录

     use std::io::copy;
     use std::fs::File;
     use anyhow::Result;
     use tempfile::Builder;
    
     #[tokio::main]
     async fn main() -> Result<()> {
         let tmpdir = Builder::new()
             .prefix("example")
             .tempdir()?;
         let target = "https://www.rust-lang.org/logos/rust-logo-512x512.png";
         let resp = reqwest::get(target).await?;
    
         let mut dest = {
             let fname = resp
                 .url()
                 .path_segments()
                 .and_then(|segm| segm.last())
                 .and_then(|name| if name.is_empty() { None } else { Some(name) } )
                 .unwrap_or("tmp.bin");
    
             println!("Downloading {} to {}", target, fname);
             let fname = tmpdir.path().join(fname);
             println!("Saving to {:?}", fname);
             File::create(fname)?
         };
         let content = resp.text().await?;
         copy(&mut content.as_bytes(), &mut dest)?;
         Ok(())
     }
  122. 使用 HTTP range 请求头进行部分下载

     use anyhow::{Ok, Result};
     use reqwest::StatusCode;
     use reqwest::header::{CONTENT_LENGTH, HeaderValue, RANGE};
     use std::fs::File;
     use std::str::FromStr;
    
     struct PartialRangeIter {
         start: u64,
         end: u64,
         chunk_size: u32,
     }
    
     impl PartialRangeIter {
         pub fn new(start: u64, end: u64, buffer: u32) -> Result<Self> {
             if buffer == 0 {
                 anyhow::bail!("Buffer size must be greater than zero");
             }
             Ok(PartialRangeIter {
                 start,
                 end,
                 chunk_size: buffer,
             })
         }
     }
    
     impl Iterator for PartialRangeIter {
         type Item = HeaderValue;
         fn next(&mut self) -> Option<Self::Item> {
             if self.start > self.end {
                 None
             } else {
                 let prev_start = self.start;
                 self.start += std::cmp::min(self.chunk_size as u64, self.end - self.start + 1);
                 Some(
                     HeaderValue::from_str(&format!("bytes={}-{}", prev_start, self.start - 1))
                         .expect("string provided by format!"),
                 )
             }
         }
     }
    
     fn main() -> Result<()> {
         let url = "https://httpbin.org/range/102400?duration=2";
         const CHUNK_SIZE: u32 = 10240;
    
         let client = reqwest::blocking::Client::new();
         let response = client.head(url).send()?;
         let length = response
             .headers()
             .get(CONTENT_LENGTH)
             .ok_or(anyhow::anyhow!("!"))?;
         let length = u64::from_str(length.to_str()?).map_err(|_| anyhow::anyhow!("invalid Content-Length header"))?;
    
         let mut output_file = File::create("download.bin")?;
    
         println!("starting download...");
         for range in PartialRangeIter::new(0, length - 1, CHUNK_SIZE)? {
             println!("range {:?}", range);
             let mut response = client.get(url).header(RANGE, range).send()?;
    
             let status = response.status();
             if !(status == StatusCode::OK || status == StatusCode::PARTIAL_CONTENT) {
                 anyhow::bail!("Unexpected server response: {}", status)
             }
             std::io::copy(&mut response, &mut output_file)?;
         }
    
         let content = response.text()?;
         std::io::copy(&mut content.as_bytes(), &mut output_file)?;
    
         println!("Finished with success!");
         Ok(())
     }
  123. POST

    本示例通过向 paste.rs 发送 POST 请求来创建一个新的 paste。

     use std::fs::File;
     use std::io::Read;
     use anyhow::Result;
    
     #[tokio::main]
     async fn main() -> Result<()> {
         let paste_api = "https://paste.rs";
         let mut file = File::open("message.txt")?;
    
         let mut contents = String::new();
         file.read_to_string(&mut contents)?;
    
         let client = reqwest::Client::new();
         let res = client.post(paste_api)
             .body(contents)
             .send()
             .await?;
         let response_text = res.text().await?;
         println!("Your paste is located at: {}",response_text );
         Ok(())
     }

    这样会得到一个反馈,有链接,内容即为刚才上传的内容。


Ciallo~(∠・ω< )⌒★