rust - Rust中的 move 语义是什么?

标签 rust move-semantics ownership

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

  • 借用,即获取引用,但不允许更改引用目标。 &运算符从值中借用所有权。
  • 可变地借入,即,获取引用以突变目标。 &mut运算符从值中可变地借用所有权。

  • Rust documentation about borrowing rules说:

    First, any borrow must last for a scope no greater than that of the owner. Second, you may have one or the other of these two kinds of borrows, but not both at the same time:

    • one or more references (&T) to a resource,
    • exactly one mutable reference (&mut T).


    我相信引用是创建指向该值的指针并通过该指针访问该值。如果存在更简单的等效实现,则可以由编译器对其进行优化。

    但是,我不了解 move 的含义以及它的实现方式。

    对于实现Copy特征的类型,它意味着复制例如通过从源代码或memcpy()逐级分配struct。对于小型结构或基元,此副本非常有效。

    对于,请 move 吗?

    这个问题不是What are move semantics?的重复,因为Rust和C++是不同的语言,并且两者之间的 move 语义也不同。

    最佳答案

    语义

    Rust实现了所谓的Affine Type System:

    Affine types are a version of linear types imposing weaker constraints, corresponding to affine logic. An affine resource can only be used once, while a linear one must be used once.



    不是Copy并因此被 move 的类型是仿射类型:您可以使用一次,也可以永不使用它们,别无其他。

    在以所有权为中心的世界观(*)中,Rust将其视为所有权转移。

    (*)一些从事Rust的人员比我在CS方面的资格高得多,他们有意识地实现了仿射类型系统;然而,与Haskell揭露数学-y/cs-y概念相反,Rust倾向于揭露更多实用的概念。

    注意:从我的阅读中可以看出,从用#[must_use]标记的函数返回的仿射类型实际上是线性类型。

    实现

    这取决于。请记住,Rust是一种为速度而设计的语言,这里有许多优化途径在起作用,这取决于所使用的编译器(在我们的示例中为Rustc + LLVM)。

    在函数体内(playground):
    fn main() {
        let s = "Hello, World!".to_string();
        let t = s;
        println!("{}", t);
    }
    

    如果检查LLVM IR(在Debug中),则会看到:
    %_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从memcpy的结果调用"Hello, World!".to_string()s,然后再调用t。尽管看起来效率低下,但在Release模式下检查相同的IR,您会发现LLVM已完全删除了副本(意识到s未被使用)。

    调用函数时会发生相同的情况:理论上,您将对象“move ”到函数堆栈框架中,但是实际上,如果对象较大,则rustc编译器可能会改为传递指针。

    另一种情况是从函数返回,但是即使这样,编译器仍可能会应用“返回值优化”并直接在调用者的堆栈框架中进行构建-也就是说,调用者传递了一个用于写入返回值的指针,中间存储。

    Rust的所有权/借用限制使优化难以在C++中实现(C++也具有RVO,但在很多情况下无法应用)。

    因此,摘要版本:
  • move 大型对象效率低下,但是有许多优化措施可能会完全消除此 move
  • move 涉及一个memcpy字节的std::mem::size_of::<T>()字节,因此 move 较大的String是有效的,因为它仅复制了几个字节,无论它们保存在
  • 上的已分配缓冲区的大小如何

    关于rust - Rust中的 move 语义是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60527138/

    相关文章:

    data-structures - 一个模块中的 Rc<T>s 和另一个模块中的 Rc<RefCell<T>>s 引用相同的数据

    http - 你如何在 Rust 中发出 GET 请求?

    c++ - 为什么需要 move 语义来消除临时拷贝?

    c++ - 你如何实例化智能指针类成员?

    rust - 如何解决 "use of moved value"和 "which does not implement the ` Copy` trait”?

    c++ - C++11 中返回 const 值类型对 move 语义的影响

    c - Rust 析构函数和所有权

    c++ - 如何使 C++ 类以 const 方式访问所有另一个类的私有(private)成员(只读不写)

    casting - 了解类型推断

    rust - 什么是重新借用,它如何影响编译器生成的代码?