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 编译期防数据竞争的武器
相关推荐
子兮曰3 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰3 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
小羊没烦恼!3 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
爱勇宝3 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
胡写代码3 天前
别再前后端各写一套表单校验了
java·后端
伞伞悦读3 天前
【第38期】Python 模块与包详解:import、from、模块搜索路径、包结构和 __init__
开发语言·python
大勇前进3 天前
原生 PHP 还是 Laravel?小项目到底要不要上框架
后端
yuzhi_liu3 天前
我用 LangGraph4j 实现 Multi-Agent Supervisor
后端
alsmile3 天前
Node-RED 之外,国产规则引擎的新方案:基于标准语法,Go 先行实现
后端·开源·go
大白803 天前
PHP 内存溢出排查思路:看懂报错日志,精准定位问题
后端