无废话学习rust【基础篇】

1 篇文章 0 订阅
1 篇文章 0 订阅

声明:

  • 本blog偏向学习/速查手册,需要阅读者会使用Linux,并具备编程基础(最好C++)
  • 本blog不会事无巨细介绍方方面面,而是专注于快速掌握rust,并理解rust的语言特性
  • 本blog不追求教科书般的严谨表达,而是专注于更加容易理解与记忆的表达
  • 作者只是rust的初学者,内容中如有错误,欢迎大家批评指正

准备环境

# rust快速安装与执行
curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
cargo new hello_cargo
cd hello_cargo
cargo run

基础语法

赋值

let a = 12;            // rust会自动推导类型,一旦类型确定,就不可再变更
let i:i32 = 12;        // (不可变变量)赋值语句
let mut m:i32 = 12;    // (可变变量)赋值语句

数据类型

基本类型

// 整型(默认i32)
let i:i8 = -1;       // 有符号类型,从i8到i128
let u:u8 = 0;        // 无符号类型,从u8到u128
let s:isize;         // 按CPU位长确定类型(32 or 64),分为有符号isize和无符号usize

// 浮点型(默认f64)
let f:f32 = 1.0;     // 只有f32和f64两种

// 布尔型(全小写)
let b:bool = false;  // true or false

// 字符型(单引号)
let c:char = 'a';    // 固定4字节Unicode编码,可通过std::mem::size_of::<char>()验证

类型转换

// rust禁止隐式类型转换
let i:i32 = 2;
let f:f32 = 3.0;
let r = i * f;    // 此处会报错
// 通过as进行显式类型转换
let i:i32 = 2;
let f:f32 = 3.0;
let x:f32 = i as f32;
let r:f32 = f * x;

 复合类型

// 元组:不可变、多类型
// 在语法层面,“tup”和“(x, y)”是可以等价替换的
let tup = (32, 6.4);            // 自动推导类型
let tup:(i32, f64) = (32, 6.4); // 严格定义类型
let (x, y) = tup;
let (x, y):(i32, f64) = (1, 2.0);
let (x, y) = (y, x);
let first = tup.0;
let second = tup.1;

// 数组:可变,单类型
let arr = [1,2,3,4,5];           // 自动推导类型
let arr:[i8;5] = [1,2,3,4,5];    // 严格定义类型和长度
let arr = [3;5];                 // 批量初始化
let first = arr[0];
let second = arr[1];

let mut arr = [1,2,3];    // 数组默认不可变,需要使用mut定义,才是“传统意义“上的数组
arr[0] = 4;

// 切片(slice):部分引用,无所有权
let arr = [0,1,2,3,4,5,6,7,8,9];
let f5 = &arr[0..5];    // 切片必须也只能引用

 切片引用

// 读引用
let a = [0,1,2,3,4,5,6,7,8,9];
let b = &a[0..5];
println!("{:?}", b);

// 写引用
let mut a = [0,1,2,3,4,5,6,7,8,9];
let b = &mut a[0..5];
b[0] = 10;
println!("{:?}", b);

控制流

分支

// 标准的分支判断用法
let mut a = 0;
if a == 0 {            // 判断条件可以不加括号
    a = 0;
} else if (a > 0) {    // 也可以加括号,但有warning
    a = 1;
} else {
    a = -1;
}

// 单行分支判断模拟三元运算符
let x = if a==0 {0} else {1};    // 大括号中不写分号,解释详见“语言特性”-“表达式和语句”部分

循环

常规语法
// for循环
for i in (1..10).rev() {    // exp..exp是范围表达式,类型为std::ops::Range,详见官方Reference
    println!("for loop: {}", i);
}

for e in [1,3,5,7,9] {
    println!("for loop: {}", e);
}

// while循环
let mut c = 0;
while c < 10 {
    c += 1;
    if c % 2 == 0 {
        println!("while loop continue: {}", c);
        continue;
    }
    println!("while loop: {}", c);
}

// loop循环(死循环)
let mut c = 0;
loop {
    if c > 10 {
        break;
    }
    println!("infinite loop");
    c += 1;
}
 特性用法
break返回循环结果
// break返回循环结果(类似return语句)
let mut c = 0;
let r = loop {
    if c > 10 {
        break c;
    }
    c += 1;
};    // 这里必须加分号,解释详见“语言特性”-“表达式和语句”部分
println!("result: {}", r)    // r输出为11
根据label跳出循环
// 按label退出循环(类似goto语句) 
let mut c = 0;
'outer: loop {        // 指定外层循环label
    'inner: loop {    // 指定内存循环label
        if c/10 != c%10 {
            break;           // 默认情况下,break跳出内层循环
        }
        if c == 99 {
            break 'outer;    // 指定label后,break跳出外层循环
        }
        c += 1;
        println!("inner loop: {c}");
    }
    c += 10;
    println!("outer loop: {c}");
}
println!("result: {c}");

match

let c = 0;
let r = match c {   // match也是一种表达式,也有返回值
    0 => "0",       // 任意表达式都可以作为match的取值
    _ => "_",       // 通配匹配。match要求,匹配必须覆盖全集
};
println!("{:?}", &r);

// if let 就是 match 的语法糖,上下两套代码,完全等价
let c = 1;
let r = if let 0 = c {
    "0"
} else {    // 如果没有else,默认为空,视同于match中的“_ => ()”
    "_"     // 对应match中通配符“_”的部分
};
println!("{:?}", &r);

// match与Option
let c = Some(0);
let r:Option<i32> = match c {
    Some(i) => Some(i+1),   // Some(T)是Option的一个泛型枚举值
    None => None,
};
println!("{:?}", &r);

// match与Result
let c = Ok(0);
let r:Result<i32, String> = match c {
    Ok(i) => Ok(i+1),
    Err(e) => Err(e),
};
println!("{:?}", &r);

函数

fn add(x:i32, y:i32) -> i32 {    // 明确定义函数的参数类型和返回值类型
    x + y    // 用结尾表达式描述函数的返回值
}

fn print_type_of<T>(_:&T) {    // 泛型用法,暂不深究
    println!("{}", std::any::type_name::<T>())
}

fn main() {    // 空参数和空返回值,等同于:fn main() -> () {...}
    let r = add(1, 2);
    println!("result: {}", r);
    print_type_of(&r);
}

结构体

普通结构体

#[derive(Debug)]    // 该语句使println可以打印结构体
struct User {
    username: String,
    email: String,
}

fn main() {
    // 用法1
    let username = String::from("someone");
    let user1 = User {
        email:username.clone()+"@example.com",   // struct的成员变量,不接受引用
        username,                                // 语法糖:同名变量可以简写
    };
    println!("{:?}", user1);

    // 用法2
    let user2 = User {
        username:user1.username,    // 成员变量通过“点”运算符来取值
        email:user1.email,          // 成员变量的赋值,会导致所有权转移
    };
    println!("{:?}", user2);

    // 用法3
    let user3 = User {
        username:String::from("another"),
        ..user2                     // 省略补充用法:将未赋值的成员变量,使用该对象依次填充
    };
    println!("{:?}", user3);
}

元组结构体

#[derive(Debug)] // 该语句使println可以打印结构体
struct Point(i32, i32, i32);
struct Empty;    // 空结构体

fn main() {
    let p = Point(1, 2, 3);    // 定义一个元组结构的变量
    println!("{:?}", p);

    let mut p = Point(1, 2, 3);
    p.0 = 4;                   // 元组结构体,只能用下标方式访问
    println!("{:?}", p);

    let e = Empty;    // 空结构体一般用于只有成员函数,没有成员变量的场景
}

定义类/对象方法

struct Rectangle {
    width: u32,
    height: u32,
}

// rust没有构造函数
impl Rectangle {
    fn set(&mut self, width:u32, height:u32) {  // &mut self参数表示可写成员函数
        self.width = width;
        self.height = height;
    }

    fn area(&self) -> u32 {    // &self参数,表示只读成员函数
        self.width * self.height
    }

    fn square(size: u32) -> Rectangle { // 无self,表示关联函数(也就是静态函数或者类函数)
        Rectangle {width: size, height: size}
    }
}

// rust可以自定义析构函数
impl Drop for Rectangle {
    fn drop(&mut self) {
        println!("drop Rectangle");
    }
}

fn main() {
    let mut rect = Rectangle{width:10, height:20};
    rect.set(20, 20);
    println!("{:?}", rect.area());
    println!("{:?}", Rectangle::square(10).area());
}

枚举

#[derive(Debug)]
enum IpAddrKind {V4, V6}    // 定义枚举

fn main() {
    let four = IpAddrKind::V4;  // 创建枚举实例
    let six = IpAddrKind::V6;
    dbg!(&six);    // dbg!会获取并返回参数的所有权,所以这里需要引用
}

接口与泛型 

接口

此处没有实现泛型接口,更多内容详见:

// 定义一个接口
trait Summary {
    // 没有默认实现的方法
    fn summarize_author(&self) -> String;

    // 有默认实现的方法
    fn summarize(&self) -> String {
        format!("(Summarize: {}...)", self.summarize_author())
    }
}

trait Display {
    fn display(&self) -> String;
}

// 定义一个“类”
struct Tweet {
    username: String,
    content: String,
}

// 定义“类”的“接口”实现
impl Summary/*接口*/ for Tweet/*类*/ {
    fn summarize_author(&self) -> String {
        format!("@{}({})", self.username, self.content.len())
    }
}

impl Display for Tweet {
    fn display(&self) -> String {
        format!("Display: @{}({})", self.username, self.content.len())
    }
}

// 以接口作为参数,从而实现多态
fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}

// 实现多个接口
fn multi_notify(item: &(impl Summary + Display)) {  // 由于优先级问题,所以这里多接口实现类的引用,需要加括号
    println!("Breaking news! {}", item.summarize());
    println!("Breaking news! {}", item.display());
}

fn main() {
    let t = Tweet {
        username: String::from("horse_ebooks"),
        content: String::from("of course, as you probably already know, people"),
    };
    notify(&t);
    multi_notify(&t);
}

泛型

闭包与迭代器

闭包

闭包有三种类型:

  • FnOnce:捕获(非传参)变量的所有权转移,通常配合move关键字进行定义
  • FnMut:以“可变引用”的方式捕获(非传参)变量
  • Fn:以“不可变引用”的方式捕获(非传参)变量
// Fn(只读引用)类型闭包
fn closure_read(c:impl Fn() -> usize) -> usize {
    c()
}

// FnMut(可写引用)类型闭包
fn closure_mut(mut c:impl FnMut()) {
    c();
}

// FnOnce(所有权转移)类型闭包
fn closure_once(c:impl FnOnce()->String) -> impl FnOnce()->String {
    let mut s = c();
    move || -> String {
        s.push_str("_fn");
        s
    }
}

fn main() {
    // FnOnce(所有权转移)类型闭包
    let mut s = String::from("hello");
    let c = move || -> String {
        s.push_str("_main");
        s
    };
    let c = closure_once(c);
    let s = c();
    println!("{}", s);

    // FnMut(可写引用)类型闭包
    let mut s = String::from("hello");
    let c = || {
        s.push_str("_mut");
    };
    closure_mut(c);
    println!("{}", s);

    // Fn(只读引用)类型闭包
    let c = || {
        s.len()
    };
    let r = closure_read(c);
    println!("{}", r);
}

迭代器

fn main() {
    // 只读引用迭代器
    let v = vec![1,2,3];
    let v_iter = v.iter();
    for v in v_iter {
        println!("iter: {}", v);
    }

    // 读写引用迭代器
    let mut v = vec![1,2,3];
    let v_iter = v.iter_mut();
    for v in v_iter {
        *v += 1;
    }
    println!("{:?}", v);

    // 所有权迭代器
    let v = vec![1,2,3];        // 此处vec并不需要mut,因为后续vec元素的所有权转移,而非原地修改
    let mut v2:Vec<i32> = Vec::new();
    let v_iter = v.into_iter(); // 此时v的所有权转移,并被包裹在了类型为IntoIter的v_iter变量中
    for v in v_iter {           // IntoIter类型的特点为,对集合元素的使用,也会转移元素的所有权
        v2.push(v+1);           // 此处,每个元素的所有权都被转移到了新的集合v2中
    }
    println!("{:?}", v2);

    // 迭代器的map用法
    let v = vec![1,2,3];
    let v2:Vec<_> = v.iter().map(|x| x + 2).collect();  // 此处v.iter是只读引用,所以整体视同于复制
    println!("iter: {:?}", v);
    println!("iter: {:?}", v2);

    // 迭代器的filter用法
    let v = vec![1,2,3];
    let v2:Vec<_> = v.into_iter().filter(|x| x % 2 > 0).collect();  // 此处v.into_iter转移了所有权,所以整体视同于移动
    println!("iter: {:?}", v2);
}

智能指针

fn main() {
    // Box智能指针(拥有所有权)
    let mut a = Box::new(5);
    *a += 1;
    println!("{}", a);

    // Rc智能指针(只读引用)
    use std::rc::Rc;
    let a = Rc::new(5); // Rc是不可变引用
    let a = Rc::clone(&a);
    println!("{}", Rc::strong_count(&a));   // 打印指针的引用计数
                                            //
    // RcCell智能指针(读写引用)
    use std::cell::RefCell;
    let a = RefCell::new(5);        // RefCell不在编译期检查是否可变
    println!("{:?}", a.borrow());   // RefCell的只读borrow用法
    let mut w = a.borrow_mut();     // RefCell的读写borrow用法
    *w += 1;
    println!("{:?}", a);            // 由于w始终没有销毁,所以a的值,无法读取,只记录borrow状态
    std::mem::drop(w);              // 强制回收
    println!("{:?}", a);            // 在没有任何其他borrow时,a才能被正常使用
}
  • 持有者数量的区别:
    • Rc<T> 允许相同数据有多个所有者
    • Box<T> 和 RefCell<T> 单一所有者
  • 编译期检查的区别:
    • Box<T> 允许在编译时执行不可变或可变借用检查
    • Rc<T>仅允许在编译时执行不可变借用检查
    • RefCell<T> 允许在运行时执行不可变或可变借用检查。
  • 可读可写的区别:
    • Box<T>是可写的
    • Rc<T>是只读的
    • RefCell<T> 可以在自身是不可变的情况下修改其内部的值
  • 是否可以用于多线程:
    • Box<T>允许
    • Rc<T>RefCell<T> 只能用于单线程场景

无畏并发

线程与channel

use std::thread;
use std::sync::mpsc;
use std::time::Duration;

fn main() {
    let v = vec![0,1,2,3,4,5,6,7,8,9];
    let (tx, rx) = mpsc::channel();         // 如果对tx、rx进行clone,即可进行“多写”或“多读”模式
    let handle = thread::spawn(move || {    // 传递给线程的闭包,必须通过move进行所有权转移,不能引用
        for i in 0..10 {
            println!("hi number {}[{}] from the spawned thread!", i, v[i]);
            thread::sleep(Duration::from_millis(10));
        }
        tx.send(v).unwrap();    // send会导致所有权转移
    });

    for i in 1..5 {
        println!("hi number {} from the main thread!", i);
        thread::sleep(Duration::from_millis(10));
    }

    handle.join().unwrap();
    let received = rx.recv().unwrap();  // 还有try_recv的非阻塞方法
    println!("receive: {:?}", received);
}

 线程与Mutex

Arc与Mutex的关系很像Rc与RefCell的关系,都是使用不可变的指针获取其内部的可变引用

use std::thread;
use std::sync::{Mutex, Arc};

fn main() {
    let counter = Arc::new(Mutex::new(0));  // Arc类似Rc,简单理解为线程安全的Rc
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Arc::clone(&counter); // 将内部变量counter复制10次,分别move给10个线程
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();  // counter自身指向不可变,但指向的内容可变
            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Result: {}", *counter.lock().unwrap());
}

代码管理

内部模块

多文件模型

src/                   # 模块名就是文件名(不包含扩展名rs)
├── main.rs            # 主函数文件,文件名必须为main.rs
├── modbardir          # 一级模块(目录)
│   ├── mod.rs         # 一级模块(mod.rs为特殊文件,用于定义其所在的目录模块的内容)
│   ├── modbar.rs      # 二级模块(文件)
│   └── modbar2dir     # 二级模块(目录)
│       ├── mod.rs     # 二级模块(mod.rs为特殊文件,用于定义其所在的目录模块的内容)
│       └── modbar2.rs # 三级模块(文件)
└── modfoo.rs          # 零级模块(与主函数同级)

main.rs

// 模块的引入,都是以当前文件的地址作为起始地址
mod modfoo;     // mod关键字用于引入模块(文件)
mod modbardir;  // mod关键字用于引入模块(目录)

use crate::modfoo::foo;                 // 绝对引用
use modbardir::modbar::bar;             // 相对引用
use modbardir::modbar::bar as barfn;    // 相对引用(通过as重命名)
use modbardir::ref_bar;                 // 相对引用(super间接引用)
use modbardir::modbar2::bar2;           // 相对引用(pub use间接引用)

fn main() {
    crate::modfoo::foo();   // 绝对引用(crate为关键字)
    modfoo::foo();          // 相对引用(以当前路径为基础)
    foo();                  // 相对引用(通过use声明)

    crate::modbardir::modbar::bar();    // 绝对引用
    modbardir::modbar::bar();           // 相对引用(以当前路径为基础)
    bar();                              // 相对引用(通过use声明)
    barfn();                            // 相对引用(通过as重命名)
    ref_bar();                          // 相对引用(super间接引用)

    crate::modbardir::modbar2dir::modbar2::bar2();  // 绝对引用
    modbardir::modbar2dir::modbar2::bar2();         // 相对引用(以当前路径为基础)
    modbardir::modbar2::bar2();                     // 相对引用(pub use间接引用)
    bar2();                                         // 相对引用(pub use间接引用)
}

modfoo.rs

// 任何元素(不限于函数)如果想被外部使用,都必须通过pub声明
pub fn foo() {
    println!("mod: foo");
}

// 所有未声明为pub的都是私有的,私有的都无法访问
fn _private_foo() {
    println!("mod: private_foo");
}

modbardir/mod.rs

// mod.rs文件用于定义目录模块的内容
pub mod modbar;                 // 只有通过pub声明的子模块,才能被外部引用
pub mod modbar2dir;
pub use modbar2dir::modbar2;    // 通过pub user声明的多级子模块,可作为直接子模块使用

// super关键字,用于指定父模块
pub fn ref_bar() {
    use super::modbardir::modbar::bar;
    bar();
}

modbardir/modbar.rs 

pub fn bar() {
    println!("mod: bar");
}

modbardir/modbar2dir/mod.rs 

pub mod modbar2;

modbardir/modbar2dir/modbar2.rs 

pub fn bar2() {
    println!("mod: bar2")
}

单文件模型 

// 单文件模型与多文件模型,表达的是相同的模块组织形式
// 通过单文件形式,可以更容易了解模块组织形式

mod modfoo {
    pub fn foo() {
        println!("mod: foo");
    }

    fn _private_foo() {
        println!("mod: private_foo");
    }
}

mod modbardir {
    pub use modbar2dir::modbar2;

    pub fn ref_bar() {
        use super::modbardir::modbar::bar;
        bar();
    }

    pub mod modbar {
        pub fn bar() {
            println!("mod: bar");
        }
    }

    pub mod modbar2dir {
        pub mod modbar2 {
            pub fn bar2() {
                println!("mod: bar2")
            }
        }
    }
}

use crate::modfoo::foo;                 // 绝对引用
use modbardir::modbar::bar;             // 相对引用
use modbardir::modbar::bar as barfn;    // 相对引用(通过as重命名)
use modbardir::ref_bar;                 // 相对引用(super间接引用)
use modbardir::modbar2::bar2;           // 相对引用(pub use间接引用)

fn main() {
    crate::modfoo::foo();   // 绝对引用(crate为关键字)
    modfoo::foo();          // 相对引用(以当前路径为基础)
    foo();                  // 相对引用(通过use声明)

    crate::modbardir::modbar::bar();    // 绝对引用
    modbardir::modbar::bar();           // 相对引用(以当前路径为基础)
    bar();                              // 相对引用(通过use声明)
    barfn();                            // 相对引用(通过as重命名)
    ref_bar();                          // 相对引用(super间接引用)

    crate::modbardir::modbar2dir::modbar2::bar2();  // 绝对引用
    modbardir::modbar2dir::modbar2::bar2();         // 相对引用(以当前路径为基础)
    modbardir::modbar2::bar2();                     // 相对引用(pub use间接引用)
    bar2();                                         // 相对引用(pub use间接引用)
}

外部依赖

前置操作

# 在你的项目文件夹(此例中,为hello文件夹)所在的目录,执行一下命令,以创建一个本地库
cargo new --lib diy_lib
cd diy_lib
cargo build

依赖引入(Cargo.toml文件)

[package]
name = "hello"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
clap = "2.27.1"	# 来自 crates.io
rand = { git = "https://github.com/rust-lang-nursery/rand" }	# 来自网上的仓库
diy_lib = { path = "../diy_lib" }	# 来自本地文件系统的路径
// 此处只展示本地外部依赖的用法,其他类型的外部依赖,用法相同
extern crate diy_lib;    通过extern crate来指定外部依赖,此时diy_lib可以作为顶级mod来使用

fn main() {
    let r = diy_lib::add(1, 1);
    println!("{r}")
}

语言特性

不可变变量的本质

不可变变量在栈上的本质就是“两个变量”,当第二个a生效时,第一个a无法再被访问到而已。

// 打印变量地址后发现,两个a并不在同一个地址空间
let a = 1;
println!("{:p}", &a);
let a = 2;
println!("{:p}", &a);

表达式和语句

基本概念

  • 带分号的是语句,不带分号的是表达式
  • 语句没有返回值,表达式有返回值
  • 代码块也是表达式
    • 代码块、函数体、分支语句、循环语句,都可以看作是抽象的“代码块”
    • 代码块具有默认返回值——空值:即一对小括号
// 任何不以分号结尾的,都是表达式,任何表达式代表某个“值”
let a = 0;    // 任何能表示一个值的代码,都是表达式
let a = {0};  // 任何表达式,都可以用大括号括起来
let a = {};   // 代码块也是表达式,默认返回空值
let a = {0;}; // "0;"是语句,当代码块没有结尾表达式时,默认返回空值

// 任何一个代码块,最多只能有1个表达式,来代表这个代码块所表达的“值”
let a = {
    let i = 0;    // “let i = 0;”是一条语句,它并不影响该代码块所表达的值
    1             // “1”是表达式,即为该代码块所表达的值
};                // let a = {...};为一条完整的语句,所以大括号的后面要加分号

// if语句也是表达式,也具有返回值,这就是if实现三目运算符的原理
let x = if a==0 {0} else {1};

break & return

这两个关键字的特殊之处在于:分别用于解决“循环表达式”和“函数表达式”的返回值问题

  • break关键字用于循环语句,是为循环语句定义返回值的唯一方法
  • return关键字用于函数,是让函数提前返回的唯一方法,一般情况下,结尾表达式可以取代return语句

所有权

最好能有的预备知识:

  • C++的指针与引用
  • C++的const指针与指向const的指针
  • C++的析构函数
  • C++的unique_ptr智能指针

 堆栈与作用域

  • 堆:通过引用计数来决定内存的释放
  • 栈:通过函数退出来决定内存的释放
  • 作用域:作用域是“大括号”决定的,“大括号”的范围即是变量的生命周期

赋值与引用

rust和其他语言最大的不同就是所有的“赋值”过程,默认情况下,都被看作是“移动”而非“复制”。

// 栈上复制
let a = 0;                        // 定义变量
let b = a;                        // 复制变量,当移动看待
let c = &b;                       // 引用变量
println!("{}, {}, {}", a, b, c);  // 由于b是a的复制,所以可以正常输出

// 堆上移动
let s1 = String::from("hello"); // 定义对象
let s2 = s1;                    // 移动对象
let s3 = &s2;                   // 引用对象
println!("{}, {}", s2, s3);     // 此处无法打印s1

有些地方会区分基础数据类型是复制,对象类型是引用,但作者认为,既然我们已经选择了rust,那么思想上遵守更严格的统一“约定”,会更加符合rust的思维。

移动的本质

println!("{}", std::mem::size_of::<String>());   // 无论字符串是什么,String都只包含24Byte的meta信息

let s1 = String::from("hello"); // 栈上的s1只记录meta信息,真正的字符串内容记录在堆上
let s2 = s1;                    // s2先将s1的meta信息复制过来,然后再把s1的meta信息清空,从而完成移动过程
let s3 = s2.clone();            // s3只移动了s2的拷贝,所以s2本身依然可以使用
println!("{}, {}", s2, s3);     // 此处无法打印s1

传参导致的移动

参数移动,是学习rust过程中,非常“反直觉”的一种现象,需要特别注意!

fn foo(s:String) -> String {
    println!("{}", s);
    return s;    // 如果不return,那么s会在函数结束后销毁
}

fn main() {
    let s = String::from("hello");
    let s = foo(s);    // s在传参的时候,发生了移动,如果不重新赋值,那么s将会失效
    println!("{}", s);
}

引用的本质

“引用”和“借用”,是一个意思,这只是一种人为规定,需要了解这个规定,是因为编译器报错会使用“borrow”这个词

// 理解此代码需要读者熟练掌握C语言指针的概念
// 如果不理解指针,可忽略这段代码,直接看结论

let a = 0;      // 定义一个整型a
let b = &a;     // b保存a的地址
let c = &&a;    // c保存a地址的地址(此处隐含了一个匿名变量&a)

println!("a的值:\t\t{:?}", &a);    // 正常输出a的值0
println!("a的地址:\t{:p}", &a);    // 输出变量a的地址
println!("b的真值:\t{:p}", b);     // 通过指定类型,打印b真正的值,即a的地址
println!("b的值:\t{:?}", b);       // 虽然b是a的引用(就是指针),但rust会自动解引用,打印a的值
println!("*b的真值:\t{:?}", *b);   // 对b显式解引用,通常用于基础数据类型的修改
println!("b的地址:\t{:p}", &b);    // 打印b的地址,以此确认,b就是一个指针,有自己的地址空间

println!("c的真值:\t{:p}", c);     // 通过指定类型,打印c1真正的值,即a的引用(就是指针)的地址
println!("c的值:\t\t{:?}", c);     // 无论多少级引用,rust都会一直解引用,一直到真正的值为止
println!("c的地址:\t{:p}", &c);    // 打印c的地址,进一步证明,多级引用,会产生多个地址空间

// 通过使用裸指针,理解引用的本质
let rp = &a as *const i32;         // 通过裸指针的形式,获取a的地址,可以发现和b的真值相同,进一步确认,引用的本质就是指针
println!("rp的真值:\t{:p}", rp);   // 以指针形式,打印rp的真值,作为参照
println!("rp的值:\t\t{:?}", rp);   // 裸指针不会自动“解引用”,所以并不会打印a的值,依旧打印a的地址
let srp = unsafe{*rp};             // 裸指针无法直接取值,必须通过unsafe的表达式才行
println!("srp的值:\t{:?}", srp);   // 裸指针只有通过显示取值,才能得到真正a的值

// 语法糖:自动解引用
let mut s = String::from("hello");
let ms = &mut &mut s;              // 这里了两个&mut,主要为了验证多级解引用效果
ms.push_str(" world");             // 下标访问、成员方法访问,无需显式解引用,rust会自动解引用
println!("s的值:\t{:?}", s);

结论:

  1. 引用就是指针,只不过rust为这种指针赋予了特殊能力:自动解引用 & 安全性检查
  2. 基础类型不会自动解引用,必须手动解引用才能修改值,否则会被认为是修改引用目标
  3. 下标访问、成员方法访问,无需显式解引用,rust会自动解引用
  4. 多级引用就是多级指针,rust会自动解多级引用,直到所有引用全部解完为止

理解可变引用 

如果你理解C++中的“const指针”与“指向const的指针”的区别,那么你已经学会了可变引用

// 基础变量的可变引用
let a = 0;            // a是不可变变量
let mut ma = 0;       // mut_a是可变变量
let b = &a;           // b不可再引用新的对象,且引用的a不许修改
let b = &mut ma;      // b不可再引用新的对象,但引用的a可以修改
*b = 1;               // 对基础变量的修改,必须显式解引用
let mut b = &a;       // b可以重新指定引用对象,但引用的a不许修改
let mut b = &mut ma;  // b可以重新指定引用对象,且引用的a可以修改
let mut mc = 0;
b = &mut mc;          // 直接赋值表示修改b引用的对象,而不是修改引用的值

引用的读写规范

官方文档使用的名词为:“引用”和“可变引用”,而作者自己更喜欢叫“读引用”和“写引用”,此小节会使用作者喜欢的叫法进行总结。

// 读引用
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
let r3 = &s;
println!("{s}, {r1}, {r2}, {r3}");

// 写引用
let w = &mut s;
w.push_str(" world!");
println!("{s}");

规则:

  • 一写多读原则(核心原则)
    • 任何一个引用,从定义开始,到使用结束,作为其合法性检查范围
    • 在任意检查范围内,可以同时使用多个读引用,或者使用一个写引用
  • 变更失效原则
    • 原始变量产生变更,变更前定义的所有引用,无论读写,全部失效
    • 写引用产生变更,变更前定义的所有相同指向的读引用,全部失效
  • 用时判断原则
    • 任何引用,如果不被使用,则不会进行检查
    • 间接使用,也是使用(如函数参数,宏参数等)
  • 互斥替换原则
    • 读引用与写引用是互斥的,任意检查范围内,只能存在一种
    • 对一个变量的多次写引用,后者会替代前者,前者会失效

高级特征

代码示例

三大常用集合

更多用法与细节,详见std官方手册:std - Rust

String

特别注意:rust的String类型,不支持“下标”与“遍历”两种用法

fn main() {
    // 字符串初始化
    let _s = String::new();
    let _s = "initial contents".to_string();
    let _s = String::from("initial contents");

    // 字符串追加
    let mut s = String::from("foo");
    s.push_str("bar");
    s.push('x');
    println!("{}", s);

    // 字符串拼接
    let s = String::from("foo") + &String::from("bar"); // 前不引用后引用的原因为,add的定义形式为:fn add(self, s: &str) -> String
    println!("{}", s);
    let s = format!("{}-{}-{}", "foo", "x", "bar");
    println!("{}", s);

    // 使用安全但低效的方式,修改字符串
    let s = String::from("foo");
    let mut v = s.into_bytes();
    v[0] = 'z' as u8;
    println!("{}", String::from_utf8(v).unwrap());

    // 使用高效但unsafe的方式,修改字符串
    let mut s = String::from("foo");
    unsafe {   
        let b = s.as_bytes_mut();
        b[0] = 'z' as u8;
    }
    println!("{}", s);
}

Vector

fn main() {
    // vector初始化
    let _v: Vec<i32> = Vec::new();
    let mut v = vec![1, 2, 3];

    // vector追加
    v.push(4);

    // vector修改
    v[0] = 5;
    println!("{:?}", v);
    println!("{:?}, {:?}", v[0], v.get(0)); // 下标返回&T类型,而get()方法返回的是Option<&T>类型

    // vector遍历
    for e in &mut v {
        *e += 1;
    }
    println!("{:?}", v);
}

HashMap

use std::collections::HashMap;

fn main() {
    // 初始化空map,并通过insert插入数据
    let mut m = HashMap::new();
    m.insert(String::from("a"), 1); // 通过insert插入新值
    println!("{:?}", m);
    m.insert(String::from("a"), 2); // insert即能插入新值,也能覆盖老值
    println!("{:?}", m);
    m.entry(String::from("a")).or_insert(3);    // 使用entry返回一个枚举值,or_insert只有不存在key时才插入,并且会返回当前插入值的可变引用
    println!("{:?}", m);
    println!("{:?}", m.get("a"));   // 返回Option<T&>类型

    // 通过key和value的数据,直接对map进行初始化
    let keys  = vec![String::from("a"), String::from("b")];
    let values = vec![1, 2];
    let m: HashMap<_, _> = keys.iter().zip(values.iter()).collect();
    println!("{:?}", m);
}

Result语法糖

use std::io;
use std::io::Read;
use std::fs::File;

// 与open_with_match等效
fn open_with_delivery() -> Result<String, io::Error> {
    let mut s = String::new();
    // “?”是专门为Result类型设计的match语法糖,会将Result原样向上传递
    // “?”的另一个好处,是支持链式调用
    File::open("src/main.rs")?.read_to_string(&mut s)?;
    Ok(s)
}

fn open_with_match() -> Result<String, io::Error> {
    let f = File::open("src/main.rs");
    let mut f = match f {
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut s = String::new();
    match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }
}

fn open_or_panic() -> File {
    let _f = File::open("hello.txt").unwrap();   // unwrap返回File或者调用panic!宏
    let f = File::open("hello.txt").expect("my diy error message");   // except还能自定义错误信息
    f
}

fn main() {
    let r = open_with_delivery();
    println!("{:?}", r);
    let r = open_with_match();
    println!("{:?}", r);
    let r = open_or_panic();
    println!("{:?}", r);
}

参考信息

  • 16
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值