Rust教程04:引用、借用与切片

1.1 引用:借来看看,不过户

上一章的痛点:函数想用一下值,就得把所有权交出去。引用的解决思路:借用(borrow)------我看看/用用,东西还是你的。

rust 复制代码
fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1);   // &s1 是"指向 s1 的引用"
    println!("'{}' 的长度是 {}", s1, len);  // ✅ s1 还能用!
}

fn calculate_length(s: &String) -> usize {  // 参数类型是引用
    s.len()
}   // s 离开作用域,但它不拥有值,所以什么都不释放
  • &s1 创建一个指向 s1 值的引用,不取得所有权
  • 引用离开作用域时,指向的值不会被释放
  • 创建引用的行为叫借用

内存示意:

复制代码
栈              堆
s (引用) ─────┐
              ↓
s1 [ptr,len,cap] ──→ "hello"

1.2 可变引用 &mut

借来还想改?用可变引用:

rust 复制代码
fn main() {
    let mut s = String::from("hello");  // 原变量本身必须可变
    change(&mut s);                     // 可变借用
    println!("{}", s);                  // hello, world
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

1.3 借用的两条铁律

规则 1 :同一作用域内,要么有任意多个不可变引用 ,要么只有一个可变引用 ------不能混用。

规则 2 :引用必须始终有效(不许悬垂)。

规则 1 演示:可变与不可变不能共存

rust 复制代码
let mut s = String::from("hello");

let r1 = &s;          // ✅ 不可变借用
let r2 = &s;          // ✅ 再来几个也行
println!("{} {}", r1, r2);
// ↑ r1、r2 最后一次使用在这里,之后它们的借用结束

let r3 = &mut s;      // ✅ 此时没有不可变借用活着,可以可变借用
r3.push_str("!");
println!("{}", r3);

但如果交错使用就会报错:

rust 复制代码
let mut s = String::from("hello");
let r1 = &s;
let r2 = &mut s;            // ❌ 编译错误!
// println!("{}", r1);      // r1 之后还要用,和可变借用冲突

为什么这样规定? 这就是 Rust 在编译期消灭数据竞争的方式:一个人写的时候绝不允许任何人读。多线程里最臭名昭著的 bug 类,在 Rust 里根本编译不过。

规则 2 演示:悬垂引用

rust 复制代码
// ❌ 编译错误
fn dangle() -> &String {
    let s = String::from("hello");
    &s          // 返回指向 s 的引用
}               // 但 s 在这里被释放了!引用指向了无效内存

编译器报错:this function's return type contains a borrowed value, but there is no value for it to borrow from

修复:直接返回值(转移所有权),而不是返回引用:

rust 复制代码
fn no_dangle() -> String {
    String::from("hello")   // ✅ 所有权移交出去
}

1.4 字符串切片 &str

切片(slice) 是"指向集合中一段连续元素"的引用,同样不取得所有权。

rust 复制代码
let s = String::from("hello world");
let hello = &s[0..5];    // "hello"(含头不含尾)
let world = &s[6..11];   // "world"

// 等价简写
let a = &s[..5];    // 从 0 开始
let b = &s[6..];    // 到结尾
let c = &s[..];     // 整个字符串

切片的内部结构:[起始指针 + 长度],依然只是"看",不拥有数据。

实用示例:取第一个单词

rust 复制代码
fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[..i];   // 找到空格,返回它前面的部分
        }
    }
    &s[..]                    // 没有空格,整个就是单词
}

fn main() {
    let s = String::from("hello world");
    let word = first_word(&s);
    println!("第一个单词:{}", word);   // hello
}

注意参数类型用的是 &str 而不是 &String------这是惯例

rust 复制代码
// &str 作为参数,String 和 &str 都能传进来(自动 deref 转换)
let s1 = String::from("hello");
let s2 = "world";
first_word(&s1);   // ✅ &String 自动转成 &str
first_word(s2);    // ✅ 本来就是 &str

字符串字面量的真相

rust 复制代码
let s = "hello";   // 类型是 &str ------ 指向编译进二进制的字符串的切片!

这就是为什么字面量天生不可变:它是不可变引用。

1.5 其他切片

数组也能切片:

rust 复制代码
let a = [1, 2, 3, 4, 5];
let slice: &[i32] = &a[1..4];   // [2, 3, 4]
println!("{:?}", slice);

1.6 借用与修改的经典冲突

切片会"锁定"原数据,防止你改它:

rust 复制代码
let mut s = String::from("hello world");
let word = first_word(&s);
// s.clear();              // ❌ 错误:clear 需要可变借用,但 word 还在借用 s
println!("{}", word);

s.clear() 放到 println! 之后就合法了(word 不再被使用,借用结束)。这个机制保证:word 永远指向有效的数据------编译器替你盯着。

小结

  • &T 不可变引用(可多个)、&mut T 可变引用(同时只能一个),二者不能同时存活
  • 引用不能悬垂:不许返回指向局部变量的引用
  • 字符串切片 &str 是最常用的字符串类型;函数参数优先用 &str
  • 字符串字面量就是 &str
  • 借用规则是 Rust 编译期防数据竞争的武器
相关推荐
Rain的Java大神之路2 小时前
线上接口负载满了如何解决
java·数据库·redis·后端·缓存·面试·架构
jerryinwuhan2 小时前
《Python数据预处理》第四章
开发语言·python
长江&后浪2 小时前
防火墙的描述
开发语言·网络·php
观无2 小时前
若依EasyExcel实现单元格合并
开发语言·前端·javascript
雨晨源码(同名B站)2 小时前
基于Python的网易云音乐评论数据情感化分析系统 音乐爬虫信息可视化 |SnowNLP评论情感分析
开发语言·hadoop·爬虫·python·信息可视化·毕业设计
咸甜适中2 小时前
rust语言AI编程学习笔记(五)clap命令行参数解析
学习·rust·ai编程
梦醒沉醉2 小时前
19、Rust程序设计语言——高级特性
rust
我不是疯子是傻子3 小时前
串口通信数据帧粘包拆包再进化:基于 Qt 的滑动窗口与 CRC 校验实战
开发语言·qt
程序员老陆3 小时前
Qt中实现窗口真全屏(覆盖Windows任务栏)
开发语言·windows·qt