告别内存泄漏:你的Rust语言30天征服计划

发布于:2025-07-31 ⋅ 阅读:(24) ⋅ 点赞:(0)

欢迎踏上Rust学习之旅!


第一周:奠定基础 (Week 1: Laying the Foundation)

第1天:环境搭建与 “Hello, World!”

  • 核心概念: 安装Rust工具链 (rustup),它包含了编译器rustc和包管理器Cargo。Cargo是你的好朋友,用于创建项目、构建、测试和管理依赖。

  • 代码示例: main.rs 文件内容

    // main函数是每个可执行Rust程序的入口点
    fn main() {
        // println! 是一个宏,用于将文本打印到控制台
        println!("Hello, world!");
    }
    
  • 编程练习:

    1. 访问 rustup.rs 并按照说明安装Rust。

    2. 打开终端,运行 cargo new hello_rust 创建一个新项目。

    3. 进入 hello_rust/src/main.rs 文件,修改打印内容为你自己的问候语。

    4. 在项目根目录运行 cargo run,看到输出。

第2天:变量、可变性与数据类型初探

  • 核心概念: Rust中的变量默认是不可变的(immutable)。使用mut关键字可以使其变为可变的。Rust是静态类型语言,但编译器通常可以推断出你想要的类型。

  • 代码示例:

    fn main() {
        // 默认不可变
        let x = 5;
        println!("The value of x is: {}", x);
    
        // x = 6; // <-- 这行会编译错误!
    
        // 使用 mut 关键字使其可变
        let mut y = 10;
        println!("The initial value of y is: {}", y);
        y = 20;
        println!("The new value of y is: {}", y);
    }
    
  • 编程练习: 创建一个程序,声明一个不可变变量spaces并赋值为一个字符串。再声明一个同名的可变变量,记录字符串的长度。这种“遮蔽”(Shadowing)是允许的。

第3天:标量数据类型 (Scalar Types)

  • 核心概念: 了解Rust的四种基本标量类型:整数、浮点数、布尔值和字符。

  • 代码示例:

    fn main() {
        // 整数 (Integer)
        let apples: i32 = 5; // 带类型标注
        let bananas = 10u64; // 带类型后缀
    
        // 浮点数 (Floating-Point)
        let pi: f64 = 3.14159;
    
        // 布尔值 (Boolean)
        let is_rust_fun: bool = true;
    
        // 字符 (Character) - 使用单引号
        let heart_eyed_cat = '😻';
    
        println!("Apples: {}, Bananas: {}, Pi: {}", apples, bananas, pi);
        println!("Is Rust fun? {}, Look: {}", is_rust_fun, heart_eyed_cat);
    }
    
  • 编程练习: 编写一个程序,进行一些基本的数学运算,比如整数加法和浮点数除法,并将结果打印出来。

第4天:函数 (Functions)

  • 核心概念: 使用fn关键字定义函数。函数可以有参数和返回值。表达式的最后一个值若不带分号,则会作为函数的返回值。

  • 代码示例:

    fn main() {
        let sum = add_five(10);
        println!("10 + 5 = {}", sum);
    }
    
    // 定义一个函数,接收一个 i32 参数,返回一个 i32
    fn add_five(x: i32) -> i32 {
        x + 5 // 这是一个表达式,它的值将作为函数的返回值
    }
    
  • 编程练习: 编写一个名为celsius_to_fahrenheit的函数,它接收一个f64类型的摄氏温度作为参数,返回转换后的华氏温度(公式:F=Ctimes1.8+32)。在main函数中调用它并打印结果。

第5天:控制流 (Control Flow)

  • 核心概念: 学习if-else表达式和三种循环:loop, while, for

  • 代码示例:

    fn main() {
        let number = 6;
    
        if number % 4 == 0 {
            println!("number is divisible by 4");
        } else if number % 3 == 0 {
            println!("number is divisible by 3");
        } else {
            println!("number is not divisible by 4 or 3");
        }
    
        // for 循环
        for i in 1..=5 { // `..=` 表示包含5
            println!("Looping: {}", i);
        }
    }
    
  • 编程练习: 使用loop循环,在循环体中将一个计数器加一,当计数器达到10时,使用break关键字并从循环中返回计数器的两倍值。

第6天:复合类型:元组(Tuple)和数组(Array)

  • 核心概念: 元组是固定长度、可包含多种类型的集合。数组是固定长度、所有元素必须是相同类型的集合。

  • 代码示例:

    fn main() {
        // 元组 (Tuple)
        let user_info: (&str, i32, bool) = ("Alice", 30, true);
        // 解构元组
        let (name, age, _is_active) = user_info;
        println!("User: {}, Age: {}", name, age);
        // 通过索引访问
        println!("First element: {}", user_info.0);
    
        // 数组 (Array)
        let months: [&str; 3] = ["January", "February", "March"];
        println!("The first month is: {}", months[0]);
    }
    
  • 编程练习: 创建一个元组来存储一个HTTP状态(状态码u16,消息&str),例如(200, "OK")。然后创建一个包含5个浮点数的数组,计算并打印它们的平均值。

第7天:所有权 (Ownership)

  • 核心概念: 内存由“所有者”管理。当所有者离开作用域,值被清理。值只能有一个所有者。赋值操作会发生“移动”(Move)。

  • 代码示例:

    fn main() {
        // s1 拥有一个 String
        let s1 = String::from("hello");
    
        // s1 的所有权被“移动”到 s2
        let s2 = s1;
    
        // println!("s1 is {}", s1); // <-- 这行会编译错误!因为 s1 不再拥有数据
        println!("s2 is {}", s2); // s2 现在是所有者,可以被使用
    }
    
  • 编程练习: 创建一个String类型的变量s1。将其赋值给s2。然后尝试打印s1,仔细阅读并理解编译器关于“值被移动”(value moved)的错误信息。


第二周:深入所有权与结构化数据

第8天:引用 (References) 与借用 (Borrowing)

  • 核心概念: 如果不想转移所有权,可以创建值的“引用”,这被称为“借用”。引用默认不可变。&mut创建可变引用。

  • 代码示例:

     
    fn main() {
        let s1 = String::from("hello");
        // calculate_length 函数“借用”了 s1,而不是获取所有权
        let len = calculate_length(&s1);
        // 因为 s1 的所有权没有被移动,所以在这里仍然可以访问它
        println!("The length of '{}' is {}.", s1, len);
    }
    
    fn calculate_length(s: &String) -> usize {
        s.len()
    } // s 在这里离开作用域,但因为它不拥有所引用的数据,所以什么也不会发生
    
  • 编程练习: 重写第7天的练习。创建一个计算String长度的函数,该函数接收String的引用&String作为参数。在main函数中调用此函数后,验证原来的String变量仍然可用。

第9天:切片 (Slices)

  • 核心概念: 切片允许你引用集合中一部分连续的元素序列,它本身不持有所有权。

  • 代码示例:

    fn main() {
        let sentence = String::from("hello world");
        let hello = &sentence[0..5]; // or &sentence[..5]
        let world = &sentence[6..11]; // or &sentence[6..]
    
        println!("First word: {}, Second word: {}", hello, world);
    }
    
  • 编程练习: 编写一个函数,它接收一个字符串切片&str,并返回它找到的第一个单词的切片。提示:通过遍历字符串中的字节来寻找空格。

第10天:结构体 (Structs)

  • 核心概念: 使用struct关键字创建自定义的复合数据类型。

  • 代码示例:

    // 定义一个结构体
    struct Rectangle {
        width: u32,
        height: u32,
    }
    
    fn main() {
        // 创建一个 Rectangle 实例
        let rect1 = Rectangle {
            width: 30,
            height: 50,
        };
    
        println!(
            "The area of the rectangle is {} square pixels.",
            rect1.width * rect1.height
        );
    }
    
  • 编程练习: 定义一个User结构体,包含username (String)、email (String) 和 active (bool) 字段。在main函数中,创建一个User实例并打印其email

第11天:结构体上的方法 (Methods on Structs)

  • 核心概念: 使用impl块为结构体定义方法。方法的第一个参数通常是&self&mut selfself

  • 代码示例:

     

    struct Rectangle {
        width: u32,
        height: u32,
    }
    
    // 为 Rectangle 实现方法
    impl Rectangle {
        // `&self` 是 `self: &Rectangle` 的缩写
        fn area(&self) -> u32 {
            self.width * self.height
        }
    }
    
    fn main() {
        let rect1 = Rectangle { width: 30, height: 50 };
        // 使用点号调用方法
        println!("The area is {}", rect1.area());
    }
    
  • 编程练习: 为第10天的User结构体实现一个deactivate方法,它接收一个&mut self,将active字段设置为false

第12天:枚举 (Enums) 与模式匹配 (Pattern Matching)

  • 核心概念: enum允许你定义一个可以枚举出不同可能值的类型。match控制流运算符必须穷尽所有可能性。

  • 代码示例:

     
    enum Message {
        Quit,
        Move { x: i32, y: i32 },
        Write(String),
        ChangeColor(i32, i32, i32),
    }
    
    fn process_message(msg: Message) {
        match msg {
            Message::Quit => println!("Quit"),
            Message::Move { x, y } => println!("Move to x: {}, y: {}", x, y),
            Message::Write(text) => println!("Text message: {}", text),
            Message::ChangeColor(r, g, b) => println!("Change color to R:{}, G:{}, B:{}", r, g, b),
        }
    }
    
    fn main() {
        process_message(Message::Write(String::from("hello")));
        process_message(Message::Quit);
    }
    
  • 编程练习: 创建一个TrafficLight枚举,包含RedYellowGreen三个变体。编写一个函数,接收一个TrafficLight枚举,使用match返回每种灯需要等待的时间(u8类型)。

第13天:Option 枚举

  • 核心概念: Option<T>用于处理可能为空的值,避免了null带来的问题。它有两个变体:Some(T)None

  • 代码示例:

    fn find_division_result(numerator: f64, denominator: f64) -> Option<f64> {
        if denominator == 0.0 {
            None
        } else {
            Some(numerator / denominator)
        }
    }
    
    fn main() {
        let result1 = find_division_result(10.0, 2.0);
        let result2 = find_division_result(10.0, 0.0);
    
        match result1 {
            Some(value) => println!("Result 1: {}", value),
            None => println!("Result 1: Cannot divide by zero"),
        }
    
        // if let 是 match 的一种简写形式
        if let Some(value) = result2 {
            println!("Result 2: {}", value);
        } else {
            println!("Result 2: Cannot divide by zero");
        }
    }
    
  • 编程练习: 编写一个函数,接收一个整数数组的切片,如果切片不为空,则返回Some包含第一个元素的值,否则返回None。在main函数中使用match来处理这个Option结果。

第14天:包、Crate和模块 (Packages, Crates, and Modules)

  • 核心概念: 使用moduse来组织代码,将代码分割到不同文件和模块中。

  • 代码示例:

    // 文件结构:
    // ├── src
    // │   ├── main.rs
    // │   └── front_of_house.rs
    
    // src/main.rs
    mod front_of_house; // 声明 front_of_house 模块,Rust会查找 front_of_house.rs
    
    // 使用 use 关键字将 hosting 引入作用域
    use front_of_house::hosting;
    
    fn main() {
        hosting::add_to_waitlist();
    }
    
    // src/front_of_house.rs
    // 模块默认是私有的,需要用 pub 关键字使其公开
    pub mod hosting {
        pub fn add_to_waitlist() {
            println!("Added to waitlist!");
        }
    }
    
  • 编程练习: 将第11天的User结构体及其impl块移动到一个名为models.rs的独立文件中。然后在main.rs中通过mod models;use models::User;来使用它。


第三周:集合、错误处理与泛型

第15天:Vector (动态数组)

  • 核心概念: Vec<T>是一种可增长的、存储在堆上的集合类型。

  • 代码示例:

     
    fn main() {
        // 创建一个可变的 vector
        let mut v: Vec<i32> = Vec::new();
    
        // 添加元素
        v.push(5);
        v.push(6);
        v.push(7);
    
        // 访问元素(安全的方式)
        match v.get(2) {
            Some(third) => println!("The third element is {}", third),
            None => println!("There is no third element."),
        }
    
        // 遍历 vector
        for i in &v {
            println!("{}", i);
        }
    }
    
  • 编程练习: 创建一个Vec<i32>,向其中添加数字1到5。然后遍历这个vector,将每个元素乘以2,并将结果存储在一个新的vector中。

第16天:字符串 (String)

  • 核心概念: &str是字符串切片,String是堆上分配、可变的字符串。

  • 代码示例:

     
    fn main() {
        let mut s = String::from("foo");
        s.push_str("bar");
        println!("{}", s); // "foobar"
    
        let s1 = String::from("Hello, ");
        let s2 = String::from("world!");
        // + 操作符会获取 s1 的所有权
        let s3 = s1 + &s2; 
        println!("{}", s3);
        // println!("{}", s1); // s1 在这里不再有效
    }
    
  • 编程练习: 创建一个空的String,然后使用push_str()push()方法向其中添加文本和字符,最后将其与其他&str拼接成一句话并打印。

第17天:哈希表 (Hash Maps)

  • 核心概念: HashMap<K, V>用于存储键值对。

  • 代码示例:

     
    use std::collections::HashMap;
    
    fn main() {
        let mut scores = HashMap::new();
    
        scores.insert(String::from("Blue"), 10);
        scores.insert(String::from("Yellow"), 50);
    
        let team_name = String::from("Blue");
        // get 方法返回一个 Option<&V>
        let score = scores.get(&team_name).copied().unwrap_or(0);
        println!("Blue team score: {}", score);
    
        // 遍历
        for (key, value) in &scores {
            println!("{}: {}", key, value);
        }
    }
    
  • 编程练习: 创建一个HashMap来统计一段文本中每个单词出现的次数。

第18天:错误处理与Result

  • 核心概念: Result<T, E>用于处理可能失败的操作。?运算符可以简化错误传播。

  • 代码示例:

     
    use std::fs::File;
    use std::io::Read;
    
    // 函数返回一个 Result
    fn read_username_from_file() -> Result<String, std::io::Error> {
        let mut f = File::open("hello.txt")?; // `?` 如果是 Err,则直接返回 Err
        let mut s = String::new();
        f.read_to_string(&mut s)?; // `?` 如果是 Err,则直接返回 Err
        Ok(s) // 如果成功,则返回 Ok(s)
    }
    
    fn main() {
        match read_username_from_file() {
            Ok(username) => println!("Username: {}", username),
            Err(e) => println!("Error reading file: {}", e),
        }
    }
    
  • 编程练习: 编写一个safe_divide函数,接收两个f64参数。如果除数为零,则返回一个Err包含描述错误的字符串;否则返回Ok包含除法结果。在main中调用它并使用match处理Result

第19天:泛型 (Generics)

  • 核心概念: 泛型是具体类型或其他属性的抽象替代,用于减少代码重复。

  • 代码示例:

     
    // 泛型结构体
    struct Point<T> {
        x: T,
        y: T,
    }
    
    // 泛型函数
    fn print_point<T: std::fmt::Display>(p: &Point<T>) {
        println!("Point is at ({}, {})", p.x, p.y);
    }
    
    fn main() {
        let integer_point = Point { x: 5, y: 10 };
        let float_point = Point { x: 1.0, y: 4.0 };
    
        print_point(&integer_point);
        print_point(&float_point);
    }
    
  • 编程练习: 创建一个泛型函数largest<T: PartialOrd>(list: &[T]) -> &T,它可以找到任何实现了PartialOrd trait(即可比较大小)的类型的切片中的最大元素。

第20天:Trait (特性)

  • 核心概念: Trait类似于接口,定义了某种类型必须提供的方法签名。

  • 代码示例:

    // 定义一个 Trait
    pub trait Summary {
        fn summarize(&self) -> String;
    }
    
    pub struct Tweet {
        pub username: String,
        pub content: String,
    }
    
    // 为 Tweet 类型实现 Summary Trait
    impl Summary for Tweet {
        fn summarize(&self) -> String {
            format!("{}: {}", self.username, self.content)
        }
    }
    
    fn main() {
        let tweet = Tweet {
            username: String::from("horse_ebooks"),
            content: String::from("of course, as you probably already know, people"),
        };
        println!("1 new tweet: {}", tweet.summarize());
    }
    
  • 编程练习: 定义一个名为CanSpeak的trait,它有一个speak(&self) -> String方法。为你之前创建的User结构体和新创建的Cat结构体实现这个trait。

第21天:生命周期 (Lifetimes)

  • 核心概念: 生命周期是编译器用来确保所有借用都有效的范围。大多数情况编译器可自动推断,复杂时需手动标注。

  • 代码示例:

     

    // 'a 是生命周期参数
    // 它告诉 Rust,返回的引用的生命周期与 x 和 y 中较短的那个生命周期相关联
    fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
        if x.len() > y.len() {
            x
        } else {
            y
        }
    }
    
    fn main() {
        let string1 = String::from("long string is long");
        let result;
        {
            let string2 = String::from("xyz");
            // result 的生命周期被限制在 string2 的生命周期内
            result = longest(string1.as_str(), string2.as_str());
            println!("The longest string is {}", result);
        }
        // println!("The longest string is {}", result); // <-- 这里会报错,因为 string2 和 result 都已失效
    }
    
  • 编程练习: 编写一个结构体ImportantExcerpt<'a>,它包含一个字段part,类型为&'a str。验证你不能创建一个比它所引用的字符串活得更长的ImportantExcerpt实例。


第四周:高级特性与并发

第22天:闭包 (Closures)

  • 核心概念: 闭包是可以捕获其环境的匿名函数。

  • 代码示例:

     

    fn main() {
        let x = 4;
        // 这个闭包捕获了环境中的 x
        let equal_to_x = |z| z == x;
        let y = 4;
        assert!(equal_to_x(y));
    
        let v1 = vec![1, 2, 3];
        // map 方法接收一个闭包作为参数
        let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();
        println!("v2: {:?}", v2); // v2: [2, 3, 4]
    }
    
  • 编程练习: 使用find方法和一个闭包,在一个Vec<i32>中查找第一个大于10的数字。

第23天:迭代器 (Iterators)

  • 核心概念: 迭代器是处理元素序列的一种懒惰(lazy)且高效的方式。

  • 代码示例:

    fn main() {
        let v1 = vec![1, 2, 3, 4, 5];
        let iterator = v1.iter(); // 创建迭代器
    
        // 迭代器是懒惰的,需要消耗它们才能做事
        let total: i32 = iterator.sum(); // sum() 会消耗迭代器
        println!("Sum: {}", total);
    
        // 链式调用
        let v2: Vec<i32> = vec![1, 2, 3];
        let v3: Vec<_> = v2.iter().map(|x| x * 2).filter(|&x| x > 4).collect();
        println!("v3: {:?}", v3); // v3: [6]
    }
    
  • 编程练习: 创建一个从1到100的数字vector。使用迭代器、filtermap链式调用,找出所有能被3整除的数,将它们平方,然后使用sum计算总和。

第24天:智能指针 (Box, Rc, RefCell)

  • 核心概念: Box<T>在堆上分配值;Rc<T>允许多个所有者;RefCell<T>在运行时检查借用规则。

  • 代码示例: (Box 用于递归类型)

    // 使用 Box 来创建递归的 Cons List 类型
    enum List {
        Cons(i32, Box<List>),
        Nil,
    }
    use List::{Cons, Nil};
    
    fn main() {
        // (1, (2, (3, Nil)))
        let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
    }
    
  • 编程练习: 使用Rc<T>来创建一个允许多个列表共享同一个尾部(另一个列表)的数据结构。

第25天:并发:线程 (Threads)

  • 核心概念: 使用thread::spawn创建新线程。move关键字常用于闭包中,以转移所有权。

  • 代码示例:

     
    use std::thread;
    use std::time::Duration;
    
    fn main() {
        let handle = thread::spawn(|| {
            for i in 1..10 {
                println!("hi number {} from the spawned thread!", i);
                thread::sleep(Duration::from_millis(1));
            }
        });
    
        for i in 1..5 {
            println!("hi number {} from the main thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    
        // 等待子线程结束
        handle.join().unwrap();
    }
    
  • 编程练习: 创建一个新线程,它持有一个Vec的所有权并打印其中的元素。在主线程中尝试访问这个Vec,观察编译错误。

第26天:并发:消息传递 (Message Passing)

  • 核心概念: 使用通道(Channel)在线程间安全地传递消息,避免共享内存。

  • 代码示例:

     
    use std::sync::mpsc; // multiple producer, single consumer
    use std::thread;
    
    fn main() {
        let (tx, rx) = mpsc::channel();
    
        thread::spawn(move || {
            let val = String::from("hi");
            tx.send(val).unwrap();
            // println!("val is {}", val); // val 的所有权已经转移,这里会报错
        });
    
        let received = rx.recv().unwrap();
        println!("Got: {}", received);
    }
    
  • 编程练习: 创建一个通道。在一个子线程中,发送多条消息(比如数字1到5)。在主线程中,使用for循环来接收并打印所有消息。

第27天:并发:共享状态 (MutexArc)

  • 核心概念: Mutex<T>提供互斥访问。Arc<T>是线程安全的引用计数指针,允许多个线程拥有同一个值的引用。

  • 代码示例:

     

    use std::sync::{Arc, Mutex};
    use std::thread;
    
    fn main() {
        // 使用 Arc 来允许多个所有者,Mutex 来保证一次只有一个线程能修改值
        let counter = Arc::new(Mutex::new(0));
        let mut handles = vec![];
    
        for _ in 0..10 {
            let counter = Arc::clone(&counter);
            let handle = thread::spawn(move || {
                // lock() 会阻塞当前线程,直到可以获取锁
                let mut num = counter.lock().unwrap();
                *num += 1;
            }); // 锁在这里被释放
            handles.push(handle);
        }
    
        for handle in handles {
            handle.join().unwrap();
        }
    
        println!("Result: {}", *counter.lock().unwrap()); // 最终结果是 10
    }
    
  • 编程练习: 重做以上练习,但这次每个线程将计数器增加2而不是1。验证最终结果是20。

第28天:Cargo进阶与生态系统

  • 核心概念: 通过在Cargo.toml中添加依赖来使用crates.io上的第三方库。

  • 代码示例:

    // 1. 在 Cargo.toml 文件中添加:
    // [dependencies]
    // serde = { version = "1.0", features = ["derive"] }
    // serde_json = "1.0"
    
    // 2. 在 main.rs 中使用:
    use serde::{Serialize, Deserialize};
    
    #[derive(Serialize, Deserialize, Debug)]
    struct Point {
        x: i32,
        y: i32,
    }
    
    fn main() {
        let point = Point { x: 1, y: 2 };
    
        // 序列化为 JSON 字符串
        let serialized = serde_json::to_string(&point).unwrap();
        println!("serialized = {}", serialized);
    
        // 反序列化
        let deserialized: Point = serde_json::from_str(&serialized).unwrap();
        println!("deserialized = {:?}", deserialized);
    }
    
  • 编程练习: 在你的项目中添加一个流行的第三方库,比如rand,用它来生成一个随机数并打印出来。

第29天:unsafe Rust

  • 核心概念: unsafe关键字让你绕过编译器的某些安全检查,用于与非Rust代码交互、或进行编译器无法理解的底层优化等。

  • 代码示例: (请谨慎使用)

     

    fn main() {
        let mut num = 5;
    
        // 创建裸指针
        let r1 = &num as *const i32;
        let r2 = &mut num as *mut i32;
    
        // 解引用裸指针必须在 unsafe 块中进行
        unsafe {
            println!("r1 is: {}", *r1);
            *r2 = 10; // 修改数据
            println!("r2 now points to: {}", *r2);
        }
    }
    
  • 编程练习: (仅为理解)编写一个unsafe块,创建一个指向整数的裸指针,并解引用它来读取值。思考在哪些情况下这个操作可能导致未定义行为。

第30天:下一步与项目构思

  • 核心概念: 回顾所学,总结Rust的核心优势。探索生态系统,如异步编程(async/await)、WebAssembly、嵌入式等。

  • 代码示例: (异步代码 async/await 概念)

     

    // 需要添加 tokio 依赖: `tokio = { version = "1", features = ["full"] }`
     #[tokio::main]
     async fn main() {
         println!("Hello");
        // .await 表示等待异步操作完成,但不会阻塞整个线程
        let result = fetch_data_from_db().await;
        println!("Data fetched: {}", result);
     }
    
     async fn fetch_data_from_db() -> String {
         // 模拟一个耗时的数据库查询
         tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
        "Some data from database".to_string()
     }
    
  • 编程练习: 为自己构思一个小的结业项目。例如:一个简单的命令行待办事项应用、一个TCP端口扫描器、一个简单的Web服务器。写下项目的功能需求和你计划使用的crates,然后开始动手吧!


祝您学习愉快,编程顺利!欢迎来到Rust的世界!


网站公告

今日签到

点亮在社区的每一天
去签到