首页 > 代码库 > 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 的移动语义