首页 > 代码库 > Rust 的移动语义

Rust 的移动语义

 新接触 Rust 时你很容易在编译代码时看到编译器给出的各种类似于“值已移动到此处”的报错,这就涉及到 Rust 的移动语义,是语言中的重要概念。

 

那么移动语义意味着什么呢?

Rust 的移动语义是为 Rust 的所有权这个概念服务的。没有移动语义所有权就无从谈起;Rust 的生命期概念又完全是为所有权服务的,否则生命期的概念就毫无意义了;没有了所有权,Rust 对并发开发的支持也就完全无从下手了。由此可以看出移动语义对 Rust 是多么重要的概念。

 在 C++11 中也有“移动语义”的说法,但是它与 Rust 的移动语义不同。

如上所述,与 C++ 不同,Rust 的移动语义是为它的整个概念服务的,属于无法削减的内容。但是与 C++ 殊途同归的是,Rust 的移动语义也导致渐少拷贝以及由此带来的性能提升。

在一个 Rust 变量的整个生存期内,它可以拥有对一个值的所有权,也可以给其他变量借出一个不可变的引用。除了那些实现了 Copy trait 的类型(它们都是系统预定义的基本类型),通过函数传参或者变量赋值会导致所有权的转移,导致变量失去对值的所有权,在这一点以后就无法再使用这个变量。与此同时,变量若共享出一个可变的引用,那么在此期间变量的值也变得不可读。

以上这些导致一个结果:在值的整个生命周期内,实际不需要做任何拷贝!比 C++ 的移动语义更加经济!

让我们看看这对运行期有怎样的影响。

给出以下的示范代码:

1 fn foo<T :std::fmt::Show>(v :T)->T{ println!("{}", v); v }2 fn main(){3     foo(Vec::<int>::new());4 }

 我们用 nightly-build 版本的 Rust 编译器,使用 rustc --emit ir 命令将其编译为 LLVM IR 码。

1 define internal void @_ZN4main20hd1a04040006a384aXaaE() unnamed_addr #0 {2 entry-block:3   %0 = alloca %"struct.collections::vec::Vec<[int]>[#6]"4   %1 = alloca %"struct.collections::vec::Vec<[int]>[#6]"5   call void @"_ZN3vec12Vec$LT$T$GT$3new19h704216336887053096E"(%"struct.collections::vec::Vec<[int]>[#6]"* noalias nocapture sret dereferenceable(12) %1)6   call void @_ZN3foo20h7141834345437085968E(%"struct.collections::vec::Vec<[int]>[#6]"* noalias nocapture sret dereferenceable(12) %0, %"struct.collections::vec::Vec<[int]>[#6]"* noalias nocapture dereferenceable(12) %1)7   call void @"_ZN32collections..vec..Vec$LT$int$GT$14glue_drop.149617h9e49ff34e685aeaeE"(%"struct.collections::vec::Vec<[int]>[#6]"* %0)8   ret void9 }

以上是我们的 main 函数。

 1 define internal void @_ZN3foo20h7141834345437085968E(%"struct.collections::vec::Vec<[int]>[#6]"* noalias nocapture sret dereferenceable(12), %"struct.collections::vec::Vec<[int]>[#6]"* noalias nocapture dereferenceable(12)) unnamed_addr #0 { 2 entry-block: 3   %2 = alloca { %"struct.collections::vec::Vec<[int]>[#6]"* } 4   %match = alloca { %"struct.collections::vec::Vec<[int]>[#6]"* } 5   %__llmatch = alloca %"struct.collections::vec::Vec<[int]>[#6]"** 6   %__arg0 = alloca %"struct.collections::vec::Vec<[int]>[#6]"* 7   %3 = alloca %"struct.core::fmt::Arguments[#3]" 8   %arg = alloca { %str_slice*, i32 } 9   %4 = alloca %"struct.core::fmt::Argument[#3]"10   %5 = alloca { i8*, i32 }11   %auto_deref = alloca [1 x %"struct.core::fmt::Argument[#3]"]*12   %__fat_ptr = alloca { %"struct.core::fmt::Argument[#3]"*, i32 }13   %__fat_ptr1 = alloca { %"struct.core::fmt::Argument[#3]"*, i32 }14   %6 = getelementptr inbounds { %"struct.collections::vec::Vec<[int]>[#6]"* }* %2, i32 0, i32 015   store %"struct.collections::vec::Vec<[int]>[#6]"* %1, %"struct.collections::vec::Vec<[int]>[#6]"** %616   %7 = load { %"struct.collections::vec::Vec<[int]>[#6]"* }* %217   store { %"struct.collections::vec::Vec<[int]>[#6]"* } %7, { %"struct.collections::vec::Vec<[int]>[#6]"* }* %match18   %8 = getelementptr inbounds { %"struct.collections::vec::Vec<[int]>[#6]"* }* %match, i32 0, i32 019   store %"struct.collections::vec::Vec<[int]>[#6]"** %8, %"struct.collections::vec::Vec<[int]>[#6]"*** %__llmatch20   br label %case_body21 22 case_body:                                        ; preds = %entry-block23   %9 = load %"struct.collections::vec::Vec<[int]>[#6]"*** %__llmatch24   %10 = load %"struct.collections::vec::Vec<[int]>[#6]"** %925   store %"struct.collections::vec::Vec<[int]>[#6]"* %10, %"struct.collections::vec::Vec<[int]>[#6]"** %__arg026   %11 = bitcast { %str_slice*, i32 }* %arg to i8*27   call void @llvm.memcpy.p0i8.p0i8.i32(i8* %11, i8* bitcast ({ %str_slice*, i32 }* @_ZN3foo15__STATIC_FMTSTR20h9586b2dd8c54287ezaaE to i8*), i32 8, i32 4, i1 false)28   %12 = bitcast %"struct.core::fmt::Argument[#3]"* %4 to [1 x %"struct.core::fmt::Argument[#3]"]*29   %13 = getelementptr inbounds %"struct.core::fmt::Argument[#3]"* %4, i32 030   %14 = load %"struct.collections::vec::Vec<[int]>[#6]"** %__arg031   invoke void @_ZN3fmt8argument20h9585635072081308878E(%"struct.core::fmt::Argument[#3]"* noalias nocapture sret dereferenceable(8) %13, %"enum.core::result::Result<[(), core::fmt::Error]>[#3]" (%"struct.collections::vec::Vec<[int]>[#6]"*, %"struct.core::fmt::Formatter[#3]"*)* @"_ZN3vec22Vec$LT$T$GT$.fmt..Show3fmt19h167638290299852560E", %"struct.collections::vec::Vec<[int]>[#6]"* noalias readonly dereferenceable(12) %14)32           to label %normal-return unwind label %unwind_custom_33 34 normal-return:                                    ; preds = %case_body35   store [1 x %"struct.core::fmt::Argument[#3]"]* %12, [1 x %"struct.core::fmt::Argument[#3]"]** %auto_deref36   %15 = load [1 x %"struct.core::fmt::Argument[#3]"]** %auto_deref37   %16 = getelementptr inbounds [1 x %"struct.core::fmt::Argument[#3]"]* %15, i32 0, i32 038   %17 = getelementptr inbounds { %"struct.core::fmt::Argument[#3]"*, i32 }* %__fat_ptr, i32 0, i32 039   store %"struct.core::fmt::Argument[#3]"* %16, %"struct.core::fmt::Argument[#3]"** %1740   %18 = getelementptr inbounds { %"struct.core::fmt::Argument[#3]"*, i32 }* %__fat_ptr, i32 0, i32 141   store i32 1, i32* %1842   %19 = getelementptr inbounds { %"struct.core::fmt::Argument[#3]"*, i32 }* %__fat_ptr, i32 0, i32 043   %20 = load %"struct.core::fmt::Argument[#3]"** %1944   %21 = getelementptr inbounds { %"struct.core::fmt::Argument[#3]"*, i32 }* %__fat_ptr, i32 0, i32 145   %22 = load i32* %2146   %23 = getelementptr inbounds { %"struct.core::fmt::Argument[#3]"*, i32 }* %__fat_ptr1, i32 0, i32 047   store %"struct.core::fmt::Argument[#3]"* %20, %"struct.core::fmt::Argument[#3]"** %2348   %24 = getelementptr inbounds { %"struct.core::fmt::Argument[#3]"*, i32 }* %__fat_ptr1, i32 0, i32 149   store i32 %22, i32* %2450   invoke void @"_ZN3fmt22Arguments$LT$$x27a$GT$3new20h64b4f307e0f3973bRozE"(%"struct.core::fmt::Arguments[#3]"* noalias nocapture sret dereferenceable(24) %3, { %str_slice*, i32 }* noalias nocapture dereferenceable(8) %arg, { %"struct.core::fmt::Argument[#3]"*, i32 }* noalias nocapture dereferenceable(8) %__fat_ptr1)51           to label %normal-return2 unwind label %unwind_custom_52 53 unwind_custom_:                                   ; preds = %normal-return2, %normal-return, %case_body54   %25 = landingpad { i8*, i32 } personality i32 (i32, i32, i64, %"struct.rustrt::libunwind::_Unwind_Exception[#8]"*, %"enum.rustrt::libunwind::_Unwind_Context[#8]"*)* @rust_eh_personality55           cleanup56   store { i8*, i32 } %25, { i8*, i32 }* %557   br label %clean_custom_58 59 resume:                                           ; preds = %clean_custom_60   %26 = load { i8*, i32 }* %561   resume { i8*, i32 } %2662 63 clean_custom_:                                    ; preds = %unwind_custom_64   call void @"_ZN32collections..vec..Vec$LT$int$GT$14glue_drop.149617h9e49ff34e685aeaeE"(%"struct.collections::vec::Vec<[int]>[#6]"* %1)65   br label %resume66 67 normal-return2:                                   ; preds = %normal-return68   invoke void @_ZN2io5stdio12println_args20hdae450e1ddf22a14UDgE(%"struct.core::fmt::Arguments[#3]"* noalias nocapture readonly dereferenceable(24) %3)69           to label %normal-return3 unwind label %unwind_custom_70 71 normal-return3:                                   ; preds = %normal-return272   br label %join73 74 join:                                             ; preds = %normal-return375   %27 = bitcast %"struct.collections::vec::Vec<[int]>[#6]"* %1 to i8*76   %28 = bitcast %"struct.collections::vec::Vec<[int]>[#6]"* %0 to i8*77   call void @llvm.memcpy.p0i8.p0i8.i32(i8* %28, i8* %27, i32 12, i32 4, i1 false)78   %29 = bitcast %"struct.collections::vec::Vec<[int]>[#6]"* %1 to i8*79   call void @llvm.memset.p0i8.i32(i8* %29, i8 0, i32 12, i32 4, i1 false)80   call void @"_ZN32collections..vec..Vec$LT$int$GT$14glue_drop.149617h9e49ff34e685aeaeE"(%"struct.collections::vec::Vec<[int]>[#6]"* %1)81   ret void82 }


以上是 foo 函数。

我们着重看 foo 函数的参数列表, void @_ZN3foo20h7141834345437085968E(%"struct.collections::vec::Vec<[int]>[#6]"* noalias nocapture sret dereferenceable(12), %"struct.collections::vec::Vec<[int]>[#6]"* noalias nocapture dereferenceable(12)) ,可以看出,foo 函数的实现有两个参数。实际上,一个是传进来的参数的地址,一个是返回值的地址,它们都在 main 函数的栈帧上。不需要任何复制,foo 函数可以直接在 main 函数的栈帧上构造值并直接返回。同时可以看出 foo 函数收到的两个地址在 IR 码中标记为 noalias ,这允许 LLVM 编译出更高效的代码。

用 OllyDbg 跟踪运行程序可以看到更多的运行期细节。

Rust 的移动语义