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 跟踪运行程序可以看到更多的运行期细节。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。