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 编译期防数据竞争的武器
相关推荐
程序猿DD4 小时前
OctaFuse Gateway 2.15.0:MCP 聚合、向量 Embeddings 与官方目录同步
后端·agent
余槐i6 小时前
PostgreSQL 函数索引报错 must be immutable:3种绕法与代价
数据库·后端·sql·postgresql·性能优化
需要8267 小时前
Spring Bean 生命周期:从实例化到销毁的完整钩子
java·后端·spring
死磕音视频8 小时前
让小模型做选择题:限定候选项并读取 logits 概率的入门实践(附 Python 代码)
开发语言·python·大模型·文本分类·transformers
凤山老林8 小时前
Spring Boot 接入国密 SM2/SM3/SM4 的一些实践记录
spring boot·后端·python
伯恩bourne9 小时前
Spring Boot Bean 注册的两种方式:@Component 家族 vs @Configuration + @Bean
java·spring boot·后端
张彦峰ZYF9 小时前
停顿短不等于延迟低:G1、ZGC 与低延迟服务的目标分野、代价账本与选型标定
后端·回收暂停、应用停止总时长·到达安全点、分配停滞·屏障吃吞吐、并发线程吃核心·并发窗口吃堆余量·分区式靠挑回收集兑现目标·染色指针式不支持压缩引用
pride.li10 小时前
第03章 Python 教程-基础语法:注释与变量
服务器·前端·后端·python·算法·计算机视觉
顽疲10 小时前
Java vue 养老系统源码详解:月账单 care_month_bill 与 Quartz 扣费 Job 实践
java·开发语言·vue.js·vue养老·springboot养老·java养老
Wang's Blog10 小时前
Java 项目实战: 交友项目 - 安装开发工具与环境准备
java·开发语言·交友