起步

Hello, Rust - 图1

包管理(类比 JS 的 NPM)

Hello, Rust - 图2
Cargo config 文件样例:

  1. [source.crates-io]
  2. registry = "https://github.com/rust-lang/crates.io-index"
  3. replace-with = 'ustc'
  4. [source.ustc]
  5. registry = "git://mirrors.ustc.edu.cn/crates.io-index"

语言基础

常量&变量

变量隐藏实际上只是复用了变量名,变量本身是完全重新申明的! 常量不可以被隐藏!

  1. fn main() {
  2. // 定义常量
  3. const PI: f64 = 3.14;
  4. // 定义可变变量
  5. let mut num = 5;
  6. // 输出 PI=3.14,num=5
  7. println!("PI={},num={}", PI, num);
  8. num = 10;
  9. // 输出 num=10
  10. println!("num={}", num);
  11. // 定义不可变变量
  12. let num2 = 5;
  13. // 会报错:cannot assign twice to immutable variable
  14. // num2 = 6; // 注释掉才能编译通过
  15. println!("num2={}", num2);
  16. // 变量隐藏,通过 let 关键词复用变量名
  17. let num2 = 10;
  18. // 输出 num2=10
  19. println!("num2={}", num2);
  20. // 隐藏可变变量 num,之后 num 将不可变了
  21. let num = 20;
  22. // num = 222; // 需要注释掉才能编译通过,证明是一个全新的不可变变量
  23. // 输出 num=20
  24. println!("num={}", num);
  25. // 常量不可以被隐藏
  26. // let PI = 11; // 需要注释掉才能编译通过
  27. }

数据类型

Hello, Rust - 图3

  1. fn main() {
  2. // f64
  3. let n1 = 4.63;
  4. let n2: f32 = 4.63;
  5. let c1: char = '中';
  6. let b1 = n1 == n2;
  7. println!("{}, {}", b1, c1);
  8. // 定义字符串
  9. let s1: &str = "中文";
  10. println!("{} -> {}", c1, s1);
  11. // 不加 r 前缀,转义
  12. let s2: &str = "A\nB";
  13. // 加上 r 前缀,原样输出
  14. let s3: &str = r"A\nB";
  15. println!("{} -> {}", s2, s3);
  16. }

函数

  1. fn main() {
  2. fn1();
  3. fn2(45);
  4. let res3 = fn3(3);
  5. println!("res3={}", res3);
  6. }
  7. // 无参数无返回值
  8. fn fn1() {
  9. println!("call fn1");
  10. }
  11. // 有参数无返回值
  12. fn fn2(num: i32) {
  13. println!("call fn2, arg num={}", num);
  14. }
  15. // 有参数有返回值
  16. fn fn3(num: i32) -> i64 {
  17. println!("call fn3");
  18. return (num * 2).into();
  19. }

控制流(循环&条件判定)

  1. fn main() {
  2. // Loop 循环
  3. let mut i = 0;
  4. loop {
  5. i = i + 1;
  6. println!("loop, i={}", i);
  7. if i > 5 {
  8. break;
  9. }
  10. }
  11. // While 循环
  12. while i < 15 {
  13. i = i + 1;
  14. println!("while, i={}", i);
  15. }
  16. // For 循环
  17. for j in (i..20) {
  18. println!("for, j={}", j);
  19. }
  20. // For 循环,包含 25
  21. for j in (i..=25) {
  22. println!("for, j={}", j);
  23. }
  24. // IF 判定
  25. println!("i={}", i);
  26. if i == 15 {
  27. println!("i={}", i);
  28. }
  29. // 条件表达式作为结果
  30. let res = if i == 15 { 5 } else { 6 };
  31. println!("res={}", res);
  32. }

所有权

Rust 中非常特别设计,通过所用权的引用和借用,实现了无GC下的内存管理。 这个在我看来是 Rust 的设计理念之一,需要重点理解!

  1. fn main() {
  2. let s1 = "s1";
  3. let s1_copy = s1;
  4. println!("s1={}, s1_copy={}", s1, s1_copy);
  5. let s2 = String::from("s2");
  6. // 变量复制 -> 指针移动,原始指针会失效
  7. let s2_copy = s2;
  8. // 如下语句会失效,需要注释才能运行
  9. // println!("s2={}", s2); // error: value borrowed here after move
  10. // 手动指定要进行深拷贝
  11. let s2_new = s2_copy.clone();
  12. println!("s2_copy={}, s2_new={}", s2_copy, s2_new);
  13. // 引用移动
  14. let s3 = String::from("Hello");
  15. let s3_len = calculate_string_length(s3);
  16. // println!("s3={}, s3 len={}", s3, s3_len); // 会报错, s3 已经被释放
  17. // 借用(用完会把s4的所有权换回来)
  18. let s4 = String::from("Rust");
  19. let s4_len = calculate_string_length_pass_address(&s4);
  20. // 此时可以正常打印出来
  21. println!("s4={}, s4 len={}", s4, s4_len);
  22. }
  23. fn calculate_string_length(str: String) -> usize {
  24. str.len()
  25. }
  26. fn calculate_string_length_pass_address(str: &String) -> usize {
  27. return str.len();
  28. }

所有权(悬垂指针)

  1. // 特性:所有权(悬垂引用:悬垂指针是其指向的内存可能已经被分配给其它持有者)
  2. fn main() {
  3. // 如下调用会报错,返回的引用指向的内存区域已经被释放了
  4. // let reference_to_nothing = dangle();
  5. let safe_str = safe();
  6. println!("safe_str={}", safe_str);
  7. }
  8. // 需要先注释,避免编译错误
  9. // fn dangle() -> &String {
  10. // let s = String::from("hello");
  11. // &s // 返回字符串 s 的引用,s 离开函数就被释放,引用是无效的
  12. // }
  13. fn safe() -> String {
  14. let s = String::from("hello");
  15. s
  16. }

Slice 类型

  1. // Slice 类型(无所有权的类型,字符串字面量就是slice)
  2. fn main() {
  3. let str = String::from("hello rust");
  4. let part1 = &str[0..5];
  5. let part2 = &str[6..str.len()];
  6. println!("str={}, part1={}, part2={}", str, part1, part2);
  7. // 获取全部 str
  8. let str_copy = &str[..];
  9. println!("str={}, str_copy={}", str, str_copy);
  10. }

猜数字游戏

  1. use rand::Rng;
  2. use std::cmp::Ordering;
  3. use std::io;
  4. fn main() {
  5. let source = rand::thread_rng().gen_range(1..=100);
  6. println!("猜数字游戏!随机数字[0-100]已就绪");
  7. // 循环进行猜测~
  8. loop {
  9. println!("请输入你的猜测:");
  10. // 定义一个可变变量
  11. let mut num = String::new();
  12. io::stdin().read_line(&mut num).expect("输入错误");
  13. // 转换输入的字符串为数字
  14. // trim 是为了移除输入内容中的换行符
  15. let num: u16 = match num.trim().parse() {
  16. Ok(num) => num,
  17. Err(_) => {
  18. println!("你的输入是{},不是数字,请重新输入", num.trim());
  19. // 重新输入
  20. continue;
  21. }
  22. };
  23. // 比较两个数字
  24. match num.cmp(&source) {
  25. Ordering::Equal => {
  26. println!("恭喜,猜测正确!");
  27. // 退出循环
  28. break;
  29. }
  30. Ordering::Greater => {
  31. println!("你猜得太大了!");
  32. }
  33. Ordering::Less => println!("你猜得太小了!"),
  34. }
  35. }
  36. }

常见问题

  1. VSCode 中安装 Rust 插件后,无法启动 Language Server

image.png
这是由于这个插件默认的 rustup 地址不对,需要在 Code -> Perferences -> Settings 中去修改“rust-client.rustupPath”的值为具体的 rustup 地址。该地址一般可用如下方式找到:

  1. # 找到 cargo bin 目录
  2. cd ~/.cargo/bin
  3. # 打印当前目录路径
  4. pwd
  5. # 拼接上 rustup 进行配置,可参考下图

image.pngimage.png