👈 2 所有权

字符串

切片

s 的类型为 Strings1s4s 的切片,它们均为 &str

let s = String::from("Rust");
let s1 = &s[..];   // "Rust"
let s2 = &s[0..3]; // "Rus"
let s3 = &s[..2];  // "Ru"
let s4 = &s[1..];  // "ust"

&strString 的转化:

// &str -> String
let str0 = "Rust";
let s1 = String::from(str0);
let s2 = str0.to_string();
 
// String -> &str
let s0 = String::from("Rust");
let str1 = &s0;
let str2 = &s0[1..3];
let str3 = s0.as_str();

索引访问:&strString 均为 UTF-8 编码,不同字符的字节长度不一致,例如 "中国" 的长度为 6 个字节(1 个汉字长为 3 个字节),因此不能用索引访问单个字符。

fn main() {
    let mut s = String::from("hello world");
    let word = first_word(&s);
    s.clear(); // panic!
    println!("the first word is: {}", word);
}
 
fn first_word(s: &String) -> &str {
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' { return &s[0..i]; }
    }
    &s[..]
}

无法过编:

error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
 --> src\main.rs:4:5
  |
3 |     let word = first_word(&s);
  |                           -- immutable borrow occurs here
4 |     s.clear();
  |     ^^^^^^^^^ mutable borrow occurs here
5 |     println!("the first word is: {}", word);
  |                                       ---- immutable borrow later used here

更好的函数签名:

fn first_word(s: &str) -> &str {

这样,first_word()String&str 都适用。

追加

let mut s = String::from("Hello");
s.push_str(", Rust"); // 追加 &str
s.push('!'); // 追加 char

插入

let mut s1 = String::from("ord!");
s1.insert_str(0, ", w"); // 插入 &str -> ", word!"
s1.insert(5, 'l'); // 插入 char -> ", world!"
 
// 不能在一个字符的中间插入
let mut s2 = String::from("世");
s2.insert(1, '界'); // panic
s2.insert(3, '界'); // ok

替换

replace():适用于 String&str,接收目标字符串与替换字符串,返回一个新的字符串。

let s1 = "I like rust. We all like rust.";
let s2 = s1.replace("rust", "Rust"); // "I like Rust. We all like Rust."

replacen():适用于 String&str,比 replace() 多一个参数,表示替换的个数。

let s1 = "I like rust. We all like rust.";
let s2 = s1.replace("rust", "Rust", 1); // "I like Rust. We all like rust."

replace_range():只适用于 String,将指定范围内的部分替换为替换字符串,直接在原串上操作。

let mut s = String::from("I like rust.");
s.replace_range(7..8, "R"); // "I like Rust."

删除

都只适用于 String

pop():删除并返回字符串尾字符。

let mut s = String::from("Rust");
let c = s.pop(); // 返回 'o', s -> "Hell"

remove():删除并返回字符串指定位置的字符。

truncate():删除字符串中从指定位置开始到结尾的全部字符。

clear():清空字符串。

连接

+ / +=:右侧必须为字符串的切片引用类型,不能直接传递 String。返回一个新串,因此无需 mut 声明。

format!:类似 println!,格式化地连接字符串。

元组

元组由多种类型组合形成,长度固定,顺序固定。

let tup: (i32, f64, u8) = (500, 6.4, 1);

用模式匹配解构元组:

let tup = (500, 6.4, 1);
let (x, y, z) = tup; // 解构
println!("y = {y}"); // y = 6.4

也可以不进行解构而单独访问一个元素:

let tup = (500, 6.4, 1);
println!("tup.1 = {tup.1}"); // tup.1 = 6.4

元组可用于函数返回多个值:

fn main() {
    let s1 = String::from("abc");
    let (s2, n) = get_len(s1);
    println!("\"{s2}\".len() = {n}"); // "abc".len() = 3
}
 
fn get_len(s: String) -> (String, usize) {
    let n = s.len();
    (s, n)
}

结构体

结构体类似于元组,但可以为每个字段命名。

struct User {
    active: bool,
    username: String,
    sign_in_count: u64,
}
 
fn main() {
    // 创建一个可变实例, 初始化顺序任意
    let mut user1 = User {
        username: String::from("Somnia"),
        active: true,
        sign_in_count: 1, // "," 可选
    };
	
    // 点号访问
    user1.username = String::from("Somnia1337");
    println!(
        "{} signed in {} times.",
        user1.username, user1.sign_in_count
    );
	
    // 调用下面自定义的简化实例化方法
    let user2 = build_user(String::from("Nancy"));
	
    // 根据已有结构体新建结构体
    let user3 = User {
        // 可变性可以不同
        active: false,
        ..user1 // 表示其余成员同 user1, 尾部不能加 ","
                // 这里, String 类型的 user1.username 的所有权转移给 user3
                // 因此 user1.username 已失效, 但 user1 仍有效
    };
}
 
// 简化结构体的实例化
fn build_user(username: String) -> User {
    // 返回一个 User 实例
    User {
        username,
        active: true,
        sign_in_count: 1,
    }
}

元组结构体:字段没有名称的结构体。

struct Color(i32, i32, i32);
let black = Color(0, 0, 0);

单元结构体:不关心该类型的内容, 只关心其行为。

struct AlwaysEqual;
let subject = AlwaysEqual;
impl SomeTrait for AlwaysEqual {
	// ...
}

大部分情况下,结构体中使用 String 而非 &str 表示字符串:

struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

这样,结构体实例独立地拥有和管理自身的所有数据,而不依赖外部资源,从而,只要结构体实例有效,其数据就有效。

结构体更新:

let user2 = User {
	active: user1.active,
	username: user1.username,
	email: String::from("another@example.com"),
	sign_in_count: user1.sign_in_count,
};
 
// 简化为
 
let user2 = User {
	email: String::from("another@example.com"),
	..user1
};

更新时,未实现 Copy 的字段会被转移,不过结构体实例本身不会转移。

在上例中,创建 user2 时重新定义了 email 字段,其他字段继承自 user1(包括未实现 Copyusername)。那么接下来,user1.username 不再可用,但 user1 的其余字段仍然可用。

元组结构体的应用场景:需要为整个元组类型命名,使其与一般的元组类型区分开来(例如 struct Point(i32, i32)(i32, i32) 是两种类型),但不要求为每个字段命名。

枚举

扑克牌的花色可以枚举:

enum PokerSuit {
	Clubs,
	Spades,
	Diamonds,
	Hearts,
}

用操作符 :: 访问枚举成员:

// 创建实例
let heart: PokerSuit = PokerSuit::Hearts;

扑克牌还需要数值:

enum PokerCard {
	Clubs(u8),
	Spades(u8),
	Diamonds(u8),
	Hearts(u8),
}
let card = PokerCard::Hearts(3);

各种类型都可以放在枚举类型里:

struct Ipv4Addr {
	// ...
}
 
struct Ipv6Addr {
	// ...
}
 
enum IpAddr { // 在枚举中放结构体
	V4(Ipv4Addr),
	V6(Ipv6Addr),
}
 
enum Addr { // 在枚举中放枚举
	Ip(IpAddr),
	// ...
}
enum IpAddr {
	V4,
	V6,
}

将值关联到类型:

enum IpAddr {
	V4(String),
	V6(String),
}

从而易于修改:

enum IpAddr {
	V4(u8, u8, u8, u8),
	V6(String),
}

构造实例时,使用自动定义的 IpAddr::V4() 等:

enum IpAddr {
	V4(u8, u8, u8, u8),
	V6(String),
}
 
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));

还可以关联更多类型:

enum Message {
    Quit,                       // 不关联
    Move { x: i32, y: i32 },    // 结构体
    Write(String),              // 字符串
    ChangeColor(i32, i32, i32), // 元组
}

这可以等价改写为:

struct QuitMessage; // 类单元结构体
struct MoveMessage { // 一般结构体
    x: i32,
    y: i32,
}
struct WriteMessage(String); // 元组结构体
struct ChangeColorMessage(i32, i32, i32); // 元组结构体
 
enum Message {
	Quit(QuitMessage),
	Move(MoveMessage),
	Write(WriteMessage),
	ChangeColor(ChangeColorMessage),
}

Option 枚举

其他语言用 null 表示空值。

我称之为我十亿美元的错误。当时,我在使用一个面向对象语言设计第一个综合性的面向引用的类型系统。我的目标是通过编译器的自动检查来保证所有引用的使用都应该是绝对安全的。不过在设计过程中,我未能抵抗住诱惑,引入了空引用的概念,因为它非常容易实现。就是因为这个决策,引发了无数错误、漏洞和系统崩溃,在之后的四十多年中造成了数十亿美元的苦痛和伤害。

——Tony Hoare,null 的发明者

Rust 抛弃了 null,改用 Option 枚举表示空值。

enum Option<T> {
	Some(T),
	None,
}

成员 Some(T) 表示含有值,T 为泛型类型,可以指代任何类型。None 则表示没有值。

Option<T> 枚举被包含在 prelude 库中,两个成员也在库中。

prelude 是 Rust 标准库,包含最常用的类型和函数等,自动被引入。

let some_num = Some(5);
let absent_num: Option<i32> = None;

使用 None 时必须手动声明类型,因为编译器无法通过 None 推断 Some 中的类型。

为了创建一个可能的空值,必须显式地放入 Option<T> 中,随后,使用时必须明确地处理其为空的情况,否则无法过编,这就是 Option<T> 保证安全性的机制。

数组

数组 array,动态数组 Vector

// 创建数组
let a = [1, 2, 3];
let b: [i32; 3] = [8, 6, 4];
let c = [6; 4]; // 4 个 6, [6, 6, 6, 6]
 
// 二维数组
let d: [[i32; 3]; 2] = [a, b];
 
// 访问元素
let a0 = a[0]; // 1
 
// 创建元素为非基本类型的数组
let strs: [String; 8] = std::array::from_fn(|s| String::from("Rust"));
 
// 切片
let slice: &[i32] = &a[1..3];
 
// 借用元素
for arr in d {
	for x in arr {
		println!("{x}");
	}
	
	let mut sum = 0;
	for i in 0..arr.len() {
		sum += a[i];
	}
	println!("{sum}");
}

👉 4 流程控制