rustlings 学习笔记 -- exercises/06_move_semantics

11 阅读4分钟

exercises/06_move_semantics

move_semantics1.rs - 基础移动语义

问题代码

// TODO: Fix the compiler error in this function.
fn fill_vec(vec: Vec<i32>) -> Vec<i32> {
    let vec = vec;

    vec.push(88);

    vec
}

fn main() {
    // You can optionally experiment here.
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn move_semantics1() {
        let vec0 = vec![22, 44, 66];
        let vec1 = fill_vec(vec0);
        assert_eq!(vec1, vec![22, 44, 66, 88]);
    }
}

解题要点

  • 错误原因: 函数参数 vec 已经移动到函数内部,不需要重新赋值
  • 解决方法: 直接使用参数 vec,去掉多余的重新赋值

正确写法

fn fill_vec(vec: Vec<i32>) -> Vec<i32> {
    vec.push(88);

    vec
}

fn main() {
    // You can optionally experiment here.
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn move_semantics1() {
        let vec0 = vec![22, 44, 66];
        let vec1 = fill_vec(vec0);
        assert_eq!(vec1, vec![22, 44, 66, 88]);
    }
}

move_semantics2.rs - 所有权保留

问题代码

fn fill_vec(vec: Vec<i32>) -> Vec<i32> {
    let mut vec = vec;

    vec.push(88);

    vec
}

fn main() {
    // You can optionally experiment here.
}

#[cfg(test)]
mod tests {
    use super::*;

    // TODO: Make both vectors `vec0` and `vec1` accessible at the same time to
    // fix the compiler error in the test.
    #[test]
    fn move_semantics2() {
        let vec0 = vec![22, 44, 66];

        let vec1 = fill_vec(vec0);

        assert_eq!(vec0, [22, 44, 66]);
        assert_eq!(vec1, [22, 44, 66, 88]);
    }
}

解题要点

  • 错误原因: vec0 被移动到 fill_vec 函数后,在测试中无法再使用
  • 解决方法: 传递 vec0 的引用而不是所有权,或者克隆向量

正确写法

fn fill_vec(vec: Vec<i32>) -> Vec<i32> {
    let mut vec = vec;

    vec.push(88);

    vec
}

fn main() {
    // You can optionally experiment here.
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn move_semantics2() {
        let vec0 = vec![22, 44, 66];

        let vec1 = fill_vec(vec0.clone());

        assert_eq!(vec0, [22, 44, 66]);
        assert_eq!(vec1, [22, 44, 66, 88]);
    }
}

move_semantics3.rs - 可变绑定

问题代码

// TODO: Fix the compiler error in the function without adding any new line.
fn fill_vec(vec: Vec<i32>) -> Vec<i32> {
    vec.push(88);

    vec
}

fn main() {
    // You can optionally experiment here.
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn move_semantics3() {
        let vec0 = vec![22, 44, 66];
        let vec1 = fill_vec(vec0);
        assert_eq!(vec1, [22, 44, 66, 88]);
    }
}

解题要点

  • 错误原因: 函数参数 vec 是不可变的,但 push 方法需要可变引用
  • 解决方法: 将参数声明为可变的 let mut vec = vec

正确写法

fn fill_vec(mut vec: Vec<i32>) -> Vec<i32> {
    vec.push(88);

    vec
}

fn main() {
    // You can optionally experiment here.
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn move_semantics3() {
        let vec0 = vec![22, 44, 66];
        let vec1 = fill_vec(vec0);
        assert_eq!(vec1, [22, 44, 66, 88]);
    }
}

move_semantics4.rs - 可变引用冲突

问题代码

fn main() {
    // You can optionally experiment here.
}

#[cfg(test)]
mod tests {
    // TODO: Fix the compiler errors only by reordering the lines in the test.
    // Don't add, change or remove any line.
    #[test]
    fn move_semantics4() {
        let mut x = Vec::new();
        let y = &mut x;
        let z = &mut x;
        y.push(42);
        z.push(13);
        assert_eq!(x, [42, 13]);
    }
}

解题要点

  • 错误原因: 同时存在两个可变引用 yz 指向同一个数据,违反借用规则
  • 解决方法: 重新排列代码,确保同一时间只有一个可变引用

正确写法

fn main() {
    // You can optionally experiment here.
}

#[cfg(test)]
mod tests {
    #[test]
    fn move_semantics4() {
        let mut x = Vec::new();
        let y = &mut x;
        y.push(42);
        let z = &mut x;
        z.push(13);
        assert_eq!(x, [42, 13]);
    }
}

move_semantics5.rs - 引用与所有权

问题代码

#![allow(clippy::ptr_arg)]

// TODO: Fix the compiler errors without changing anything except adding or
// removing references (the character `&`).

// Shouldn't take ownership
fn get_char(data: String) -> char {
    data.chars().last().unwrap()
}

// Should take ownership
fn string_uppercase(mut data: &String) {
    data = data.to_uppercase();

    println!("{data}");
}

fn main() {
    let data = "Rust is great!".to_string();

    get_char(data);

    string_uppercase(&data);
}

解题要点

  • 错误原因: get_char 不应该获取所有权,应该使用引用;string_uppercase 的参数类型错误
  • 解决方法: 调整函数参数的引用类型

正确写法

#![allow(clippy::ptr_arg)]

// Shouldn't take ownership
fn get_char(data: &String) -> char {
    data.chars().last().unwrap()
}

// Should take ownership
fn string_uppercase(mut data: String) {
    data = data.to_uppercase();

    println!("{data}");
}

fn main() {
    let data = "Rust is great!".to_string();

    get_char(&data);

    string_uppercase(data);
}

知识点汇总

移动语义基础

  • 所有权转移: 当值赋给另一个变量时,所有权会发生移动
  • 函数参数: 传递给函数的值会被移动,除非使用引用
  • 返回值: 函数返回值可以转移所有权给调用者

可变性与借用

  • 可变绑定: 使用 mut 关键字声明可变变量
  • 可变引用: 使用 &mut 创建可变引用,用于修改数据
  • 借用规则: 同一时间只能有一个可变引用或多个不可变引用

引用类型

  • 不可变引用: 使用 &T 创建不可变引用,不转移所有权
  • 可变引用: 使用 &mut T 创建可变引用,用于修改数据
  • 克隆: 使用 .clone() 方法创建值的深拷贝,保留原始所有权