Rust 所有权转移后变量不可用为什么报错 borrow of moved value

文章导读
Rust 所有权转移后变量不可用报错 borrow of moved value 的根本原因在于 Rust 的所有权机制。在 Rust 中,每个值都有且仅有一个所有者,当值被赋值给新变量或传递给函数时,所有权会发生移动(Move),原变量随即失效。若再次使用原变量,编译器会检测到该值已被移动,从而抛出错误以防止内存安全问题。解决方案包括使用引用传递参数、调用 clone 方法复制数据或调整代码逻辑
📋 目录
  1. 结论与解决方案
  2. Rust 所有权及其编译错误解析
  3. rust 怎么搞的,这么简单的代码也报"borrow of moved value"?
  4. rust 三个方法解决 error: use of moved value
  5. Rust 所有权转移的常见案例_mb690cc2a30bc41 的技术博客_51CTO 博客
  6. FAQ
A A

结论与解决方案

Rust 所有权转移后变量不可用报错 borrow of moved value 的根本原因在于 Rust 的所有权机制。在 Rust 中,每个值都有且仅有一个所有者,当值被赋值给新变量或传递给函数时,所有权会发生移动(Move),原变量随即失效。若再次使用原变量,编译器会检测到该值已被移动,从而抛出错误以防止内存安全问题。解决方案包括使用引用传递参数、调用 clone 方法复制数据或调整代码逻辑避免重复使用已移动的值。

Rust 所有权及其编译错误解析

Each value in Rust has a variable that's called its owner. (Rust 中每一个变量都有一个所有者。) There can only be one owner at a time.(在任一时刻,所有者有且仅有一个。) When the owner goes out of scope, the value will be dropped.(当所有者离开其 作用域 后,它所拥有的数据会被释放。) 错误 1 代码段 1: lets1=String::from("hello");lets2=s1;println!("{}, world!",s1); 编译结果:Compiling ownership v0.1.0 (file:///projects/ownership) error[E0382]: borrow of moved value: `s1` --> src/main.rs:5:28 | 2 | let s1 = String::from("hello"); | -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait 3 | let s2 = s1; | -- value moved here 4 | 5 | println!("{}, world!", s1); | ^^ value borrowed here after move error: aborting due to previous error For more information about this error, try `rustc --explain E0382`. error: could not compile `ownership` 解析:String 的所有权从 s1 转移到 s2 后,不能再使用 s1 访问数据。否则违反原则 2。然而,对于下面这段代码,似乎产生了与代码段 1 矛盾的编译结果。代码段 2: fnmain(){letu1:u32=20;letu2=u1;println!("{}, world!",u1);} 如果按代码段 1 的逻辑,u2=u1 时,数字 20 的所有权传移给 u2,u1 应该不能再被访问。然而这段代码可以被正确地编译,这是为什么呢?原因是:针对 u32 类型的数据,rust 赋予其 Copy 特性 (trait)。凡是拥有 Copy trait 的数据类型,"="都表示数据的复制而非传有权的转移。因此在 u2=u1 之后,u2 和 u1 里,都保存有 20 这一整型数据。以下数据类型有 Copy trait: 所有整型:u32 u64 等 布尔型 所有 浮点型 :f32 f64 等 字符型:char 元组 (Tuples):如果组成元组的每个成员都有 Copy trait,那么此元组也有 Copy trait。代码段 3 fnmain(){letx=5;print_int(x);println!("{}",x);lets=String::from("hello");print_string(s);println!("{}",s);}fnprint_string(some_string: String){println!("{}",some_string);}fnprint_int(some_integer: i32){println!("{}",some_integer);} 编译结果:

rust 怎么搞的,这么简单的代码也报"borrow of moved value"?

lethello=String::from("hello");letworld=hello;hello.push_str("world");// error[E0382]: borrow of moved value:`hello` 一键获取完整项目代码 bash 1 2 3 看下完整报错 error[E0382]: borrow of moved value:`hello`-->src/main.rs:16:5|14|lethello=String::from("hello");|----- move occurs because`hello`hastype`String`,whichdoes not implement the`Copy`trait15|letworld=hello;|----- value moved here16|hello.push_str("world");|^^^^^^^^^^^^^^^^^^^^^^^ value borrowed here after move 一键获取完整项目代码 bash 1 2 3 4 5 6 7 8 9 10"hello 借用一个已经被移动的值。"借用?移动?又是什么意思呢?别急,想要理解这个问题,得先看看什么事所有权!! Rust 的核心功能 (之一) 是 所有权 (ownership)。虽然该功能很容易解释,但它对语言的其他部分有着深刻的影响。所有程序都必须管理其运行时使用计算机内存的方式。一些语言中具有垃圾回收机制,在程序运行时有规律地寻找不再使用的内存;在另一些语言中,程序员必须亲自分配和释放内存。Rust 则选择了第三种方式:通过所有权系统管理内存,编译器在编译时会根据一系列的规则进行检查。如果违反了任何这些规则,程序都不能编译。在运行时,所有权系统的任何功能都不会减慢程序。因为所有权对很多程序员来说都是一个新概念,需要一些时间来适应。

rust 三个方法解决 error: use of moved value

本文聚焦 Rust 中"use of moved value"报错,该报错源于 Rust 不依赖运行期垃圾回收的安全内存管理特性。文章未展开 ownership 等概念,而是直接给出三种解决方法,即传引用、使用引用计数、实现 Trait Clone,最后还提供跟踪变量生命周期的方法。概述"error: use of moved value",相信最近开始玩 rust 的同学看到这个报错都能会心一笑了。rust 做到了不依赖运行期垃圾回收的安全内存管理,但这个特别爽快的特性也引入了一些不便,这报错就是常见的麻烦之一。这报错要是想展开说清楚,需要完整解释 rust 的 ownership、borrowing、lifetime 等概念,那是一篇太长的文章。我们先暂时放下概念,用三个不同的方法动手解决这个报错。错误 我们以下面这段程序为基础展开我们的讨论,这里面主要定义的就是一个 Info 结构体。struct Info { s: String, } fn fn_a(info: Info) { println!("in fn_a"); } fn main() { let foo = Info {s : "abc".to_string() }; fn_a(foo); } 首先,我们要制造出报错"use of moved value"。很简单,我们只需要以 foo 为参数再调用一次 fn_a() 就好。struct Info { s: String, } fn fn_a(info: Info) { println!("in fn_a"); } fn main() { let foo = Info {s : "abc".to_string() }; fn_a(foo); fn_a(foo); // 只有这行是新加入的 } 现在,我们得到了编译器报错。src/main.rs:12:10: 12:13 error: use of moved value: `foo` src/main.rs:12 fn_a(foo); ^~~ src/main.rs:11:10: 11:13 note: `foo` moved here because it has type `Info`, which is non-copyable src/main.rs:11 fn_a(foo); ^~~ 编译器说,我们新加入的行里用了"移动了的值"。啥叫"移动了的值"呢?说白了就是用过了的值,foo 已经给第一个 fn_a() 用过了,到了第二个 fn_a() 的时候就是 moved value 了。然后就不让用了。至于为什么要制定这样的规则,我另撰文解释。现在我们开始动手来解决这个问题。方法一:引用 因为我们传给函数的是 value,所以 value 被 move 了。我们可以通过传引用给函数来解决这问题。代码如下:struct Info { pub s: String, } fn fn_a(info: &mut Info) { info.s = "bbb".to_string(); } fn main() { let mut foo = Info {s : "abc".to_string() }; println!("1: {}", foo.s); fn_a(&mut foo); println!("2: {}", foo.s); fn_a(&mut foo); println!("3: {}", foo.s); } 运行结果如下:1: abc 2: bbb 3: bbb

Rust 所有权转移的常见案例_mb690cc2a30bc41 的技术博客_51CTO 博客

一、变量赋值中的所有权转移 在 Rust 中,"把一个变量赋值给另一个变量"很容易让初学者误以为是简单的"拷贝"。但对于堆数据 (如 String、Vec),这其实是所有权的移动。fn main() { let s1 = String::from("hello"); let s2 = s1; // 所有权从 s1 移动到 s2 // println!("{}", s1); // 编译错误:borrow of moved value: `s1` println!("{}", s2); // OK } 1. 2. 3. 4. 5. 6. 7. 要点说明:String 的数据在堆上,变量本身保存指向堆的指针、长度和容量。let s2 = s1;这行不是"深拷贝",只是把指针等元数据从 s1 复制到了 s2,堆上数据只有一份。编译器会让 s1 变为"已移动 (moved)"状态,后续不能再使用。对于实现了 Copytrait 的类型 (例如 i32),赋值是复制,不会发生所有权移动:fn main() { let x = 5; // i32 实现了 Copy let y = x; // 发生拷贝,而不是 move println!("x = {}, y = {}", x, y); // 都能正常使用 } 1. 2. 3. 4. 5. 6. 这正是因为简单标量类型在赋值时走的是 Copy 语义,而大部分堆数据类型采用 Move 语义。二、函数参数传递中的所有权转移 按值传参本质上就是一次"赋值",因此对堆数据类型来说,也会触发所有权移动。1. 按值传入:所有权被函数"拿走" fn take_ownership(s: String) { println!("in fn: {}", s); } // s 在这里被 drop,堆内存释放 fn main() { let s = String::from("hello"); take_ownership(s); // 所有权移动到形参 s // println!("{}", s); // 编译错误:value borrowed here after move }

Rust 所有权转移后变量不可用为什么报错 borrow of moved value

FAQ

为什么整型变量赋值后不会报错 borrow of moved value?

因为整型(如 i32、u32)等简单标量类型在 Rust 中实现了 Copy trait。对于实现了 Copy trait 的数据类型,赋值操作表示数据的复制而非所有权的转移,因此原变量和新变量都可以正常使用,不会发生所有权移动。

如何解决函数参数传递导致的 moved value 错误?

可以通过传递引用(&T 或 &mut T)而不是直接传递值来解决。这样函数借用数据而不是获取所有权,原变量在函数调用后依然有效。另外也可以实现 Clone trait 并调用 clone 方法显式复制数据。