Rust学习 - 函数

0 阅读5分钟

今日目标

  1. 理解Rust函数的设计哲学 - 为什么Rust选择表达式导向而非语句导向?
  2. 掌握函数参数传递的所有权语义 - 函数签名是"安全契约"
  3. 深入理解表达式vs语句 - 这是Rust与其他语言的核心差异
  4. 实战函数式编程模式 - 高阶函数、函数作为参数/返回值
  5. 性能优化实践 - inline、单态化、零成本抽象

核心原理

1. 函数:所有权系统的"契约边界"

在Rust中,函数远不止是可重用的代码块。它是Rust所有权系统和借用检查器执行其安全保证的核心边界。每个函数签名都是一份由编译器严格执行的**"安全契约"**。

设计哲学:"编译期显式化"

与C++的"拷贝构造"或Java的"一切皆引用"不同,Rust的参数传递机制是其"移动语义"(Move Semantics)和"借用"(Borrowing)规则的直接体现。

2. 参数传递的三种模式

模式语法所有权语义使用场景
移动T转移所有权消费数据、构造者模式
不可变借用&T临时只读访问读取数据、多个读者
可变借用&mut T临时独占访问修改数据、单一写者
复制T: Copy按位复制栈上小数据

3. 表达式 vs 语句:Rust的核心差异

语句 (Statement):不返回值,以分号结尾,如 let x = 5; 表达式 (Expression):返回值,无结尾分号,如 5 + 6 返回 11

为什么Rust选择表达式导向?

  1. 简洁性:减少return关键字的使用
  2. 一致性:块、函数、if/match都是表达式,统一心智模型
  3. 函数式编程:便于链式调用和组合
  4. 类型安全:编译器能更好地推断返回类型

代码实例

实例1:函数基础与表达式返回值

fn main() {
    let sum = add(5, 3);
    println!("5 + 3 = {}", sum);
    
    let value = {
        let x = 10;
        let y = 20;
        x + y  // 块表达式返回值
    };
    println!("块表达式值: {}", value);
}

fn add(a: i32, b: i32) -> i32 {
    a + b  // 自动返回,无需return
}

实例2:参数传递与所有权

fn main() {
    // 移动语义
    let s1 = String::from("Hello");
    let s2 = take_ownership(s1);
    // println!("{}", s1);  // 错误!s1已移动
    
    // 借用语义
    let s3 = String::from("World");
    let len = calculate_length(&s3);
    println!("'{}'长度: {}", s3, len);  // s3仍可用
    
    // 可变借用
    let mut s4 = String::from("Hello");
    append_world(&mut s4);
}

fn take_ownership(s: String) -> String {
    println!("获得: {}", s);
    s
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

fn append_world(s: &mut String) {
    s.push_str(" World");
}

实例3:高阶函数与函数指针

fn main() {
    // 函数作为参数
    let result = apply_operation(5, double);
    println!("double(5) = {}", result);
    
    let result = apply_operation(5, square);
    println!("square(5) = {}", result);
    
    // 函数作为返回值
    let multiplier = create_multiplier(3);
    println!("3 * 7 = {}", multiplier(7));
    
    // 闭包作为参数
    let numbers = vec![1, 2, 3, 4, 5];
    let sum = reduce(&numbers, |acc, x| acc + x, 0);
    println!("sum = {}", sum);
}

// 高阶函数:接收函数作为参数
fn apply_operation(x: i32, operation: fn(i32) -> i32) -> i32 {
    operation(x)
}

fn double(x: i32) -> i32 { x * 2 }
fn square(x: i32) -> i32 { x * x }

// 返回函数的函数
fn create_multiplier(factor: i32) -> impl Fn(i32) -> i32 {
    move |x| x * factor
}

// 泛型高阶函数
fn reduce<T>(items: &[T], operation: fn(T, T) -> T, initial: T) -> T
where
    T: Copy,
{
    let mut result = initial;
    for &item in items {
        result = operation(result, item);
    }
    result
}

实例4:泛型函数与Trait Bounds

fn main() {
    let numbers = vec![3, 1, 4, 1, 5];
    println!("最大值: {:?}", find_max(&numbers));
    
    let floats = vec![3.14, 2.71, 1.41];
    println!("最大值: {:?}", find_max(&floats));
}

// 泛型函数:查找最大值
fn find_max<T>(list: &[T]) -> Option<T>
where
    T: PartialOrd + Copy,
{
    if list.is_empty() {
        return None;
    }
    
    let mut max = list[0];
    for &item in list {
        if item > max {
            max = item;
        }
    }
    Some(max)
}

最佳实践

1. API设计原则

// ✅ 优先使用引用
fn process(text: &str)  // 接受字符串切片

// ❌ 避免不必要的所有权转移
fn process_bad(text: String)  // 强制转移所有权

// ✅ 返回Result处理错误
fn parse_config(path: &str) -> Result<Config, ParseError>

// ✅ 使用impl Trait简化返回类型
fn create_iterator() -> impl Iterator<Item = i32>

2. 性能优化

// #[inline] - 提示编译器内联小函数
#[inline]
fn small_function(x: i32) -> i32 {
    x * 2
}

// #[inline(always)] - 强制内联
#[inline(always)]
fn critical_path(x: i32) -> i32 {
    x + 1
}

// 泛型单态化 - 编译时生成具体代码
fn generic_id<T>(x: T) -> T { x }
// 编译后生成: generic_id_i32, generic_id_f64等

常见陷阱

陷阱1:表达式后加分号

fn wrong_add(a: i32, b: i32) -> i32 {
    a + b;  // ❌ 分号使其成为语句,返回()
}

fn correct_add(a: i32, b: i32) -> i32 {
    a + b   // ✅ 表达式,返回i32
}

陷阱2:借用规则冲突

fn main() {
    let mut s = String::from("hello");
    
    let r1 = &s;  // 不可变借用
    let r2 = &s;  // 可以多个不可变借用
    // let r3 = &mut s;  // ❌ 错误!不能同时有可变和不可变借用
    
    println!("{} {}", r1, r2);
    // r1, r2 在这里之后不再使用
    
    let r3 = &mut s;  // ✅ 现在可以了
}

练习题

练习1:实现通用过滤器

要求: 实现一个泛型过滤函数,接收切片和谓词函数,返回满足条件的元素。

参考答案
fn filter<T>(items: &[T], predicate: fn(&T) -> bool) -> Vec<T>
where
    T: Clone,
{
    items
        .iter()
        .filter(|&x| predicate(x))
        .cloned()
        .collect()
}

fn main() {
    let numbers = vec![1, 2, 3, 4, 5, 6];
    let evens = filter(&numbers, |&x| x % 2 == 0);
    println!("{:?}", evens);  // [2, 4, 6]
}

练习2:实现函数组合

要求: 实现函数组合,将两个函数组合成一个新函数。

参考答案
fn compose<F, G, T, U, V>(f: F, g: G) -> impl Fn(T) -> V
where
    F: Fn(U) -> V,
    G: Fn(T) -> U,
{
    move |x| f(g(x))
}

fn main() {
    let add_one = |x: i32| x + 1;
    let double = |x: i32| x * 2;
    
    let add_then_double = compose(double, add_one);
    println!("{}", add_then_double(5));  // 12
}

权威引用

官方文档

性能优化


自我评估

  • 理解函数作为所有权契约边界的设计
  • 掌握参数传递的三种模式
  • 区分表达式和语句
  • 能够编写高阶函数
  • 理解泛型函数的单态化

学习记录: 2026-04-04 Day 3: 函数