语义学
Rust实现了一种被称为线性类型系统的东西:
线性类型是一种弱约束的线性类型,对应于线性逻辑。 线性资源最多只能使用一次,而线性资源必须恰好使用一次。
不是Copy
类型的类型,因此被移动的类型是线性类型:您可以将它们使用一次或者永不使用,没有其他选择。
Rust将这称为其以所有权为中心的世界观中的所有权转移。
(*) Rust的一些开发人员在计算机科学方面比我更有资格,并且他们有意实现了线性类型系统;然而,与Haskell不同,Haskell暴露了更多数学和计算机科学的概念,Rust倾向于暴露更多实用的概念。
注意:可以争论的是,从带有#[must_use]
标记的函数返回的线性类型实际上是线性类型。
实施
这要看情况。请记住,Rust是一种为速度而构建的语言,在这里有许多优化步骤,这将取决于所使用的编译器(在我们的情况下是rustc + LLVM)。
在函数体内(playground):
fn main() {
let s = "Hello, World!".to_string();
let t = s;
println!("{}", t);
}
如果你在调试模式下检查LLVM IR,你会看到:
%_5 = alloca %"alloc::string::String", align 8
%t = alloca %"alloc::string::String", align 8
%s = alloca %"alloc::string::String", align 8
%0 = bitcast %"alloc::string::String"* %s to i8*
%1 = bitcast %"alloc::string::String"* %_5 to i8*
call void @llvm.memcpy.p0i8.p0i8.i64(i8* %1, i8* %0, i64 24, i32 8, i1 false)
%2 = bitcast %"alloc::string::String"* %_5 to i8*
%3 = bitcast %"alloc::string::String"* %t to i8*
call void @llvm.memcpy.p0i8.p0i8.i64(i8* %3, i8* %2, i64 24, i32 8, i1 false)
在床单下面,rustc调用了一个从
"Hello, World!".to_string()
的结果到
s
,然后到
t
的
memcpy
。虽然看起来效率低下,但在Release模式下检查相同的IR,你会意识到LLVM已经完全省略了这些拷贝(意识到
s
没有被使用)。
当调用函数时,同样的情况也会发生:理论上,你将对象"移动"到函数的堆栈帧中,但实际上,如果对象很大,rustc编译器可能会切换到传递指针的方式。
另一种情况是从函数中
返回,但即使如此,编译器也可能应用"返回值优化",直接在调用者的堆栈帧中构建--也就是说,调用者传递一个指针来写入返回值,而不需要中间存储。
Rust的所有权/借用约束使得一些在C++中难以实现的优化成为可能(虽然C++也有返回值优化,但不能在那么多情况下应用)。
所以,简而言之:
搬运大物体效率低下,但有一些优化方法可以避免搬运操作。
搬运涉及到对std::mem::size_of::<T>()字节的memcpy操作,所以搬运大的String对象是高效的,因为它只复制了几个字节,无论分配的缓冲区大小如何。