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 void 9 }
以上是我们的 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 0 15 store %"struct.collections::vec::Vec<[int]>[#6]"* %1, %"struct.collections::vec::Vec<[int]>[#6]"** %6 16 %7 = load { %"struct.collections::vec::Vec<[int]>[#6]"* }* %2 17 store { %"struct.collections::vec::Vec<[int]>[#6]"* } %7, { %"struct.collections::vec::Vec<[int]>[#6]"* }* %match 18 %8 = getelementptr inbounds { %"struct.collections::vec::Vec<[int]>[#6]"* }* %match, i32 0, i32 0 19 store %"struct.collections::vec::Vec<[int]>[#6]"** %8, %"struct.collections::vec::Vec<[int]>[#6]"*** %__llmatch 20 br label %case_body 21 22 case_body: ; preds = %entry-block 23 %9 = load %"struct.collections::vec::Vec<[int]>[#6]"*** %__llmatch 24 %10 = load %"struct.collections::vec::Vec<[int]>[#6]"** %9 25 store %"struct.collections::vec::Vec<[int]>[#6]"* %10, %"struct.collections::vec::Vec<[int]>[#6]"** %__arg0 26 %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 0 30 %14 = load %"struct.collections::vec::Vec<[int]>[#6]"** %__arg0 31 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_body 35 store [1 x %"struct.core::fmt::Argument[#3]"]* %12, [1 x %"struct.core::fmt::Argument[#3]"]** %auto_deref 36 %15 = load [1 x %"struct.core::fmt::Argument[#3]"]** %auto_deref 37 %16 = getelementptr inbounds [1 x %"struct.core::fmt::Argument[#3]"]* %15, i32 0, i32 0 38 %17 = getelementptr inbounds { %"struct.core::fmt::Argument[#3]"*, i32 }* %__fat_ptr, i32 0, i32 0 39 store %"struct.core::fmt::Argument[#3]"* %16, %"struct.core::fmt::Argument[#3]"** %17 40 %18 = getelementptr inbounds { %"struct.core::fmt::Argument[#3]"*, i32 }* %__fat_ptr, i32 0, i32 1 41 store i32 1, i32* %18 42 %19 = getelementptr inbounds { %"struct.core::fmt::Argument[#3]"*, i32 }* %__fat_ptr, i32 0, i32 0 43 %20 = load %"struct.core::fmt::Argument[#3]"** %19 44 %21 = getelementptr inbounds { %"struct.core::fmt::Argument[#3]"*, i32 }* %__fat_ptr, i32 0, i32 1 45 %22 = load i32* %21 46 %23 = getelementptr inbounds { %"struct.core::fmt::Argument[#3]"*, i32 }* %__fat_ptr1, i32 0, i32 0 47 store %"struct.core::fmt::Argument[#3]"* %20, %"struct.core::fmt::Argument[#3]"** %23 48 %24 = getelementptr inbounds { %"struct.core::fmt::Argument[#3]"*, i32 }* %__fat_ptr1, i32 0, i32 1 49 store i32 %22, i32* %24 50 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_body 54 %25 = landingpad { i8*, i32 } personality i32 (i32, i32, i64, %"struct.rustrt::libunwind::_Unwind_Exception[#8]"*, %"enum.rustrt::libunwind::_Unwind_Context[#8]"*)* @rust_eh_personality 55 cleanup 56 store { i8*, i32 } %25, { i8*, i32 }* %5 57 br label %clean_custom_ 58 59 resume: ; preds = %clean_custom_ 60 %26 = load { i8*, i32 }* %5 61 resume { i8*, i32 } %26 62 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 %resume 66 67 normal-return2: ; preds = %normal-return 68 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-return2 72 br label %join 73 74 join: ; preds = %normal-return3 75 %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 void 82 }
以上是 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 跟踪运行程序可以看到更多的运行期细节。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。