Rust教程03:函数,控制流与所有权

第 1 章 函数与控制流

1.1 函数基础

rust 复制代码
fn main() {
    greet("小明", 25);          // 调用
}

fn greet(name: &str, age: u32) {   // 参数必须标注类型
    println!("{} 今年 {} 岁", name, age);
}
  • 关键字 fn,命名惯例是蛇形命名 (snake_case):calculate_area
  • 参数类型必须标注 ,返回值类型写在 -> 后面
  • 函数可以定义在任何位置,顺序无所谓(不用先声明)

1.2 语句与表达式(重要!)

Rust 是一门基于表达式的语言,区分两个概念:

  • 语句(Statement) :做事情,不返回值,以 ; 结尾
  • 表达式(Expression) :计算并产生一个值
rust 复制代码
fn main() {
    let y = 6;          // 这是语句,不返回值

    // ❌ 错误:let 语句没有返回值,不能赋值给 x
    // let x = (let y = 6);

    // ✅ 代码块也是表达式,最后一个表达式(不带分号)是块的值
    let x = {
        let a = 3;
        a + 1           // 注意:没有分号!这就是块的值
    };
    println!("x = {}", x);   // 4
}

分号决定了表达式还是语句

rust 复制代码
fn plus_one(x: i32) -> i32 {
    x + 1        // ✅ 表达式,作为返回值
    // x + 1;    // ❌ 加了分号变成语句,函数返回 (),类型不匹配
}

1.3 返回值

rust 复制代码
// 方式一:隐式返回最后一个表达式(推荐)
fn add(a: i32, b: i32) -> i32 {
    a + b
}

// 方式二:return 显式返回(用于提前返回)
fn abs(x: i32) -> i32 {
    if x < 0 {
        return -x;    // 提前返回必须带分号
    }
    x
}

// 不返回值的函数,返回类型是单元类型 (),可以省略
fn say_hi() {           // 等价于 fn say_hi() -> ()
    println!("hi");
}

// 永不返回的发散函数:返回类型是 !
fn crash() -> ! {
    panic!("程序崩溃");
}

1.4 if 表达式

rust 复制代码
let number = 7;

// if 是表达式,可以赋值
let desc = if number < 10 { "个位数" } else { "多位数" };

if number % 4 == 0 {
    println!("能被 4 整除");
} else if number % 3 == 0 {
    println!("能被 3 整除");
} else {
    println!("不能被 3 或 4 整除");
}

两个要点:

rust 复制代码
// 1. 条件必须是 bool,Rust 不会自动把数字转成布尔值
// if number { }        // ❌ 编译错误!不像 C/JavaScript

// 2. if 各分支的类型必须一致
// let n = if true { 5 } else { "six" };  // ❌ i32 和 &str 不匹配

1.5 三种循环

loop:无限循环

rust 复制代码
let mut count = 0;
let result = loop {
    count += 1;
    if count == 10 {
        break count * 2;   // break 可以带一个值,作为整个 loop 的值!
    }
};
println!("result = {}", result);   // 20

while:条件循环

rust 复制代码
let mut n = 3;
while n != 0 {
    println!("{}!", n);
    n -= 1;
}
println!("发射!");

for:遍历循环(最常用)

rust 复制代码
fn main() {
    let a = [10, 20, 30, 40, 50];

    // 遍历数组
    for element in a {
        println!("值是 {}", element);
    }

    // 遍历区间(Range)
    for i in 1..4 {
        // 1, 2, 3(不含 4)
        println!("{}", i);
    }
    for i in 1..=4 {
        // 1, 2, 3, 4(含 4)
        println!("{}", i);
    }
    for i in (1..=4).rev() {
        // 反向:4, 3, 2, 1
        println!("{}", i);
    }
}

forwhile + 索引 更好:没有越界风险,而且更快(省去每次边界检查)。

循环标签:嵌套循环中精确 break/continue

rust 复制代码
let mut count = 0;
'outer: loop {
    let mut remaining = 10;
    loop {
        if remaining == 9 {
            break;              // 只跳出内层
        }
        if count == 2 {
            break 'outer;       // 直接跳出外层循环
        }
        remaining -= 1;
    }
    count += 1;
}
println!("count = {}", count);  // 2

1.6 综合示例:摄氏华氏互转

rust 复制代码
fn celsius_to_fahrenheit(c: f64) -> f64 {
    c * 9.0 / 5.0 + 32.0
}

fn fahrenheit_to_celsius(f: f64) -> f64 {
    (f - 32.0) * 5.0 / 9.0
}

fn main() {
    for c in 0..=100 {
        if c % 20 == 0 {
            let f = celsius_to_fahrenheit(c as f64);
            println!("{}°C = {:.1}°F", c, f);   // {:.1} 保留 1 位小数
        }
    }
    println!("98.6°F = {:.2}°C", fahrenheit_to_celsius(98.6));
}

小结

  • 函数参数必须标注类型;蛇形命名;-> 声明返回类型
  • Rust 是表达式语言:代码块、ifloop 都能产生值,末尾不带分号的表达式就是值
  • loop(无限,可 break 带值)、while(条件)、for(遍历,首选)
  • 嵌套循环用 '标签 精确控制跳出层级

第 2 章 所有权(Rust 最核心的概念)

如果说 Rust 只教会你一件事,那就是所有权。它是 Rust 无需垃圾回收却能保证内存安全的根基。这一章请慢读。

2.1 先补背景:栈与堆

程序运行时的内存主要分两块:

栈(Stack) 堆(Heap)
存什么 大小固定、编译期已知的数据 大小可变/编译期未知的数据
特点 后进先出,分配极快 需向操作系统申请,较慢
例子 i32、定长数组、元组 StringVec 的数据部分

其他语言怎么管理堆内存?

  • C/C++ :程序员手动 malloc/freenew/delete → 容易泄漏或释放两次
  • Java/Go/Python:垃圾回收器(GC)定期扫描 → 有运行时开销和停顿

Rust 走第三条路:编译器在编译期根据所有权规则,自动插入释放代码。 零运行时开销,又不会出错。

2.2 所有权三条规则

  1. Rust 中每一个值 都有一个所有者(owner)。
  2. 同一时刻,一个值只能有一个所有者。
  3. 当所有者离开作用域 ,值会被自动丢弃(drop)。

2.3 作用域与 String

rust 复制代码
{                            // s 还没出生
    let s = String::from("hello");  // s 出生,是这段字符串的所有者
    println!("{}", s);
}                            // s 离开作用域 → Rust 自动调用 drop 释放内存
// println!("{}", s);       // ❌ 编译错误:s 已经不存在

String 的数据存在堆上,s 本身是栈上的一个"指针 + 长度 + 容量"结构。s 死了,堆上的数据跟着被释放------这就是规则 3。

2.4 移动(Move):Rust 最反直觉的地方

看两个对比实验:

rust 复制代码
// 实验一:整数
fn main() {
    let x = 5;
    let y = x;
    println!("x={}, y={}", x, y);// ✅ 正常,x 和 y 都能用
}
rust 复制代码
// 实验二:字符串
fn main(){
    let s1 = String::from("hello");
    let s2 = s1;
    println!("{}", s1);   // ❌ 编译错误:value borrowed here after move
    println!("{}", s2);      // ✅ 只有 s2 能用
}

为什么? 如果 let s2 = s1 是简单复制指针,那么 s1 和 s2 都指向同一块堆内存,两人离开作用域时会释放两次(double free)------这是 C++ 的经典灾难。

Rust 的解法:let s2 = s1 时,所有权从 s1 转移给 s2,s1 立即作废。同一时刻只有一个所有者,只会释放一次。

直觉记忆法= 对堆数据是"过户",不是"复印"。过户完原主就没钥匙了。

i32 这类简单类型实现了 Copy trait,赋值时做的是真正的拷贝,所以 x 依然有效。

2.5 Clone:真的想要一份拷贝

rust 复制代码
let s1 = String::from("hello");
let s2 = s1.clone();     // 深拷贝堆上的数据,两份完全独立
println!("s1={}, s2={}", s1, s2);   // ✅ 都能用

clone() 有性能成本(要复制堆数据),Rust 让你显式写出来,绝不偷偷复制。

Copy vs Clone

  • Copy:廉价的栈上复制(i32、f64、bool、char、全 Copy 类型的元组/数组),隐式发生
  • Clone:可能昂贵的深拷贝,必须显式调用 .clone()
  • 一个类型实现了 Copy 就不存在"移动",赋值永远是复制

2.6 函数与所有权

传参会转移所有权

rust 复制代码
fn main() {
    let s = String::from("hello");
    takes_ownership(s);          // s 的所有权"过户"给函数
    // println!("{}", s);        // ❌ s 已失效

    let x = 5;
    makes_copy(x);               // i32 是 Copy
    println!("x 还能用:{}", x); // ✅
}

fn takes_ownership(some_string: String) {
    println!("{}", some_string);
}   // some_string 离开作用域,堆内存被释放

fn makes_copy(some_integer: i32) {
    println!("{}", some_integer);
}

返回值也会转移所有权

rust 复制代码
fn main() {
    let s1 = gives_ownership();          // 函数把所有权"过户"出来
    let s2 = String::from("hi");
    let s3 = takes_and_gives_back(s2);   // 过户进去,又过户出来
    println!("s1={}, s3={}", s1, s3);
}

fn gives_ownership() -> String {
    String::from("来自函数")
}

fn takes_and_gives_back(a_string: String) -> String {
    a_string   // 原样返回
}

"给出去又拿回来"的窘境

rust 复制代码
fn calculate_length(s: String) -> (String, usize) {
    let length = s.len();
    (s, length)   // 不得不把 s 一起还回来,调用者才能继续用
}

fn main() {
    let s1 = String::from("hello");
    let (s2, len) = calculate_length(s1);  // 用元组绕一圈
    println!("'{}' 的长度是 {}", s2, len);
}

这种"为了用一下值就得过户再归还"的模式太笨拙了------下一章的引用就是为此而生的

2.7 为什么所有权设计是天才之举

编译器在所有者离开作用域时精确插入一次 drop

  • 不会忘记释放(内存泄漏 ❌)
  • 不会释放两次(double free ❌)
  • 不会用到已释放的内存(use after free ❌)
  • 这一切都发生在编译期,运行时零成本

代价是:你要习惯"值只有一个主人"的思维。这正是 Rust 学习曲线陡的原因,也是它可靠的来源。

小结

  • 三规则:每值一主、同时唯一、出域即弃
  • 堆数据赋值 = 移动(所有权过户),原变量作废
  • Copy 类型(简单栈上类型)赋值 = 复制,原变量仍可用
  • .clone() 显式深拷贝
  • 函数传参/返回值都会转移所有权
相关推荐
山荷枝8 小时前
04-框架--SpringBoot
java·spring boot·后端
linux修理工8 小时前
内存占用 99% 且 Java 程序较多的优化建议
java·开发语言
weixin_460443568 小时前
企业考试系统如何对接OA、钉钉和企业微信?SSO单点登录、组织同步与权限一致性设计
java·开发语言·数据库
nnerddboy8 小时前
Rust教程05:结构体,枚举与模式匹配
开发语言·网络·rust
小小尚@8 小时前
AE脚本-AE Actions v1.1.8 操作动作记录器
开发语言·前端·javascript·jupyter·postman
程序员雷欧8 小时前
Java 反射深度解析:从原理到源码的全面剖析
java·开发语言·python
登登登__9 小时前
春秋招笔试题总结
java·开发语言
Undoom9 小时前
用TextIn xParse搞定批量简历解析,WorkBuddy里一句话就能完成
后端
我的xiaodoujiao9 小时前
快速学习Python基础知识详细图文教程18--多线程
开发语言·python·学习·测试工具