4. 函数、参数传递与返回值设计

函数、参数传递与返回值设计 学习卡片

三种函数签名与所有权影响

本案例围绕一个“文本统计”小任务展开:统计字符串的字符数,并返回其中第一个单词的引用。通过三种签名对比值传递、引用传递和返回引用的差异,再用生命周期标注让返回引用通过编译。

  • 值传递:fn take_owned(s: String) -> usize,调用后实参被 move,调用方不能再使用原变量。
  • 引用传递:fn count_chars(s: &str) -> usize,只借用,调用后原变量仍可用。
  • 返回引用:fn first_word(s: &str) -> &str,返回的引用与输入引用绑定同一生命周期,调用方需保证被引用数据存活。
调用方 main 栈帧 let s = String::from("rust") 堆: ['r','u','s','t'] ① 值传递 take_owned(s: String) fn take_owned(s: String) -> usize move 所有权 s 在 main 中失效 返回 usize(Copy) 调用后 s 不可再用 适用:需要转移所有权或消费数据 代价:调用方失去原变量 ② 引用传递 count_chars(s: &str) fn count_chars(s: &str) -> usize 借用 &s(只读) s 在 main 中仍有效 返回 usize(Copy) 调用后 s 可继续使用 适用:只读统计、避免拷贝 代价:无所有权转移 ③ 返回引用 first_word(s: &str) fn first_word<'a>(s: &'a str) -> &'a str 借用 &s,返回引用同生命周期 'a 返回 &'a str(借用) s 在 main 中仍有效 返回引用不得超出 s 存活期 适用:零拷贝切片、解析 约束:生命周期必须标注

完整可运行代码

下面代码把三种签名放在同一个程序中,并演示调用方在每种情况下的可用性。注意 first_word 的返回引用与输入引用共享生命周期 'a,这是编译器接受它的关键。

fn take_owned(s: String) -> usize {
    s.chars().count()
}

fn count_chars(s: &str) -> usize {
    s.chars().count()
}

fn first_word<'a>(s: &'a str) -> &'a str {
    match s.find(' ') {
        Some(i) => &s[..i],
        None => s,
    }
}

fn main() {
    // ① 值传递:s1 被 move
    let s1 = String::from("hello rust");
    let n1 = take_owned(s1);
    println!("take_owned 返回: {}", n1);
    // println!("{}", s1); // 取消注释会编译错误:value borrowed here after move

    // ② 引用传递:s2 仍可用
    let s2 = String::from("hello rust");
    let n2 = count_chars(&s2);
    println!("count_chars 返回: {}, s2 仍可用: {}", n2, s2);

    // ③ 返回引用:借用 s2,返回切片
    let w = first_word(&s2);
    println!("first_word 返回: {}", w);
    // s2 在 w 使用期间保持不可变借用,不能同时可变借用
}

生命周期标注为什么必要

当函数返回引用时,编译器无法自动判断返回的引用来自哪个输入参数。若签名写成 fn first_word(s: &str) -> &str,编译器会报 missing lifetime specifier。加上 <'a> 后,明确告诉编译器:返回的引用与输入 s 的生命周期相同,调用方必须保证 s 在返回引用使用期间一直存活。

常见错误:返回局部变量的引用。例如 fn bad() -> &str { let s = String::from("x"); &s } 会报 returns a value referencing data owned by the current function,因为 s 在函数结束时被释放。

签名选择速查

签名形式所有权变化调用方后续可用性典型场景
fn f(s: String)move 进函数原变量失效消费数据、转换后不再需要原值
fn f(s: &str)只读借用原变量仍可用统计、校验、格式化
fn f(s: &mut String)可变借用借用结束后可用原地修改、追加内容
fn f<'a>(s: &'a str) -> &'a str借用并返回借用原变量在返回引用使用期内被借用切片、解析、零拷贝提取
验证方法:把 first_word 的 <'a> 去掉,观察编译器报错;再把 take_owned(s1) 后对 s1 的打印取消注释,观察 move 错误。两次报错分别对应“生命周期缺失”和“所有权已转移”,是本案例最直接的验证点。