在Rust中,有两种可能性可以引用

  1. Borrow,即获取一个引用,但不允许对引用目的地进行变异.&运营商从价值中borrow 所有权.

  2. Borrow mutably,也就是说,取一个引用来改变目的地.&mut运算符可变地从值中borrow 所有权.

Rust documentation about borrowing rules人说:

首先,任何借贷的持续时间不得超过

  • 对资源的一个或多个引用(&T),
  • 正好一个可变引用(&mut T).

我相信引用就是创建一个指向该值的指针,并通过指针访问该值.如果有更简单的等效实现,编译器可以对此进行优化.

然而,我不明白move意味着什么,以及它是如何实现的.

对于实现Copy特征的类型,它意味着复制,例如,从源或memcpy()中按顺序分配 struct 成员.对于小型 struct 或原语,此副本是有效的.

move美元呢?

这个问题不是What are move semantics?的重复,因为Rust和C++是不同的语言,移动语义在两个语言之间是不同的.

推荐答案

Semantics

Rust实现了所谓的Affine Type System:

仿射类型是施加较弱约束的线性类型的一个版本,对应于仿射逻辑.An affine resource can only be used once,而线性的必须使用一次.

不是Copy并且因此被移动的类型是仿射类型:可以使用它们一次,也可以不使用,其他什么都不用.

Rust在其以所有权为中心的世界观(*)中将其列为transfer of ownership.

(*)一些研究Rust 的人比我在CS中更合格,他们故意实施了仿射类型系统;然而,与Haskell揭露math-y/cs-y概念相反,Rust倾向于揭露更务实的概念.

Note: it could be argued that Affine Types returned from a function tagged with 100 are actually Linear Types from my reading.


Implementation

视情况而定.请记住,Rust是一种为速度而构建的语言,这里有许多优化过程,这取决于所使用的compiler个优化过程(在我们的例子中,是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.虽然它可能看起来效率很低,但在发布模式下判断相同的IR时,您会意识到LLVM已完全删除了副本(意识到s未使用).

调用函数时也会出现同样的情况:理论上,您将对象"移动"到函数堆栈框架中,但是在实践中,如果对象很大,rustc编译器可能会转而传递指针.

另一种情况是函数的返回值为returning,但即使如此,编译器也可能应用"返回值优化",并直接在调用方的堆栈框架中构建——也就是说,调用方传递一个指针,将返回值写入其中,该指针在没有中间存储的情况下使用.

Road的所有权/borrow 约束使得优化难以在C++中实现(也有RVO,但在很多情况下不能应用).

因此,摘要版:

  • 移动大型对象效率很低,但有许多优化措施可能会完全避免移动
  • 移动涉及std::mem::size_of::<T>()个字节中的memcpy个字节,因此移动一个大的String是有效的,因为它只复制几个字节,而不管它们持有的分配缓冲区大小如何

Rust相关问答推荐

为什么父作用域中的变量超出了子作用域

如何使用字符串迭代器执行查找?

在Rust中,有没有一种方法让我定义两个 struct ,其中两个都遵循标准 struct ?

当rust中不存在文件或目录时,std::FS::File::Create().unwire()会抛出错误

从Type::new()调用函数

捕获FnMut闭包的时间不够长

无法定义名为&new&的关联函数,该函数的第一个参数不是self

Trait bound i8:来自u8的不满意

在复制类型中使用std::ptr::WRITE_VILAR进行内部可变性的安全性(即没有UnSafeCell)

了解Rust';s特征对象和不同函数签名中的生存期注释

Rust 中什么时候可以返回函数生成的字符串切片&str?

Rust,如何从 Rc> 复制内部值并返回它?

在 Rust 中查找向量中 dyn struct 的索引

为什么 File::read_to_end 缓冲区容量越大越慢?

从现有系列和 map 值创建新系列

如何使用 rust bindgen 生成的 std_vector

如何在 Rust 中创建最后一个元素是可变长度数组的 struct ?

用逗号分隔字符串,但在标记中使用逗号

Rust:如果我知道只有一个实例,那么将可变borrow 转换为指针并返回(以安抚borrow 判断器)是否安全?

当引用不再被borrow 时,Rust 不会得到它