内存安全

一个类型的所有成员都具有Clone trait,我们就可以使用这种方法来让编译器帮我们实现 Clone trait了。

示例如下:

#[derive(Copy,Clone)]
struct  Foo {
  data:i32
}
fn main(){
  let v1 =Foo{data    :0    };
  let v2 =v1;
  println!("{:?}",v1.data);
}

Box类型

Box 类型是Rust 中一种常用的指针类型。它代表“拥有所有权的指针”,类似于C++ 里 面的unique_ptr (严格来说,unique_ptr 更像Option<Box>) 。 用法如下:

struct   T{
   value:i32
}
fn main(){
let p=Box::new(T{value:1});
println!("{}",p.value);
}

Box 类型永远执行的是move 语义,不能是copy 语义。原因大家想想就可以明白,Rust 中的copy 语义就是浅复制。对于Box 这样的类型而言,浅复制必然会造成二次释放问题。

对于Rust 里面的所有变量,在使用前一定要合理初始化,否则会出现编译错误。对于 Box&T &mut T这样的类型,合理初始化意味着它一定指向了某个具体的对象, 不可能是空。如果用户确实需要“可能为空的”指针,必须使用类型Option<Box>。

Rust 里面还有一个保留关键字box (注意是小写)。它可以用于把变量“装箱”到堆上。
目前这个语法依然是unstable 状态,需要打开feature gate 才能使用,示例如下:

#![feature(box_syntax)]
struct   T{
  value:i32
}
fn main(){
let p:Box<T>=box T{value:1};
println!("{}",p.value);
}

这种写法和 Box::new() 函数调用并没有本质区别。将来box 关键字可能会同样支持各种智能指针,从而根据上下文信息自动判断执行。比如let p:RC=box T{value:1};就可以构造一个Rc 指针。


Clone VS.Copy

Rust中的Copy 是一个特殊的 trait,它给类型提供了“复制”语义。在Rust标准库里 面,还有一个跟它很相近的trait, 叫作Clone 。 很多人容易把这两者混淆,本节专门来谈一 谈这两个trait。


Copy 的 含 义

Copy的全名是std::marker::Copy。请大家注意,std::marker模块里面所有的trait 都是特殊的trait。目前稳定的有四个,它们是Copy 、Send 、Sized 、Sync。

它们的特 殊之处在于:它们是跟编译器密切绑定的,impl 这些 trait 对编译器的行为有重要影响。在编 译器眼里,它们与其他的trait 不一样。这几个trait 内部都没有方法,它们的唯一任务是给类 型打一个“标记”,表明它符合某种约定——这些约定会影响编译器的静态检查以及代码生成。

Copy 这个trait 在编译器的眼里代表的是什么意思呢?简单点总结就是,如果一个类型 impl了 Copy trait,意味着任何时候,我们都可以通过简单的内存复制(在C 语言里按字节 复制memcpy) 实现该类型的复制,并且不会产生任何内存安全问题。

一旦一个类型实现了Copy trait,那么它在变量绑定、函数参数传递、函数返回值传递 等场景下,都是copy 语义,而不再是默认的move 语义。

下面用最简单的赋值语句x=y来说明move 语义和 copy语义的根本区别。move 语义 是“剪切、粘贴”操作,变量y 把所有权递交给了x 之后,y 就彻底失效了,后面继续使用 y 就会出编译错误。而copy 语义是“复制、粘贴”操作,变量y 把所有权递交给了x 之后, 它自己还留了一个副本,在这句赋值语句之后,x 和y 依然都可以继续使用。

  • 在 Rust 里 ,move 语义和 copy语义具体执行的操作,是不允许由程序员自定义的,这 是它和C++ 的巨大区别。
  • 这里没有赋值构造函数或者赋值运算符重载。
  • move 语义或者copy 语义都是执行的memcpy, 无法更改,这个过程中绝对不存在其他副作用。
  • 当然,这里一直 谈的是“语义”,而没有涉及编译器优化。

从语义的角度,我们要讲清楚,什么样的代码在 编译器看来是合法的,什么样的代码是非法的。如果考虑后端优化,在许多情况下,不必要 的内存复制实际上已经彻底优化掉了,大家不必担心执行效率的问题。也没有必要每次都把 move 或者copy 操作与具体的汇编代码联系起来,因为场景不同,优化结果不同,生成的代 码也是不同的。大家只需记住的是语义。


Copy的实现条件

并不是所有的类型都可以实现Copy trait。Rust规定,对于自定义类型,只有所有成员都实现了Copy trait, 这个类型才有资格实现 Copy trait。

常见的数字类型、bool 类型、共享借用指针&,都是具有Copy 属性的类型。而Box、 Vec、 可写借用指针&mut 等类型都是不具备Copy 属性的类型。

  • 对于数组类型,如果它内部的元素类型是Copy, 那么这个数组也是Copy 类型。

  • 对于元组tuple 类型,如果它的每一个元素都是Copy 类型,那么这个tuple 也是Copy 类型。

struct 和 enum 类型不会自动实现Copy trait。只有当struct 和enum 内部的每个元素都是 Copy类型时,编译器才允许我们针对此类型实现Copy trait。比如下面这个类型,虽然它的 成员是Copy 类型,但它本身不是Copy 类型:

struct T(i32);
fn main(){
  let t1 =T(1);
  let t2 =t1;
  println!("{}{}",t1.0,t2.0);
}

编译可见编译错误。原因是在let t2 =t1;这条语句中执行的是move 语义。但是 我们可以手动为它 impl Copy trait,这样它就具备了copy 语义。

我们可以认为,Rust 中只有POD(C++ 语言中的Plain Old Data) 类型才有资格实现 Copy trait。在 Rust中,如果一个类型只包含POD 数据类型的成员,并且没有自定义析构函 数,那它就是POD 类型。比如:整数、浮点数、只包含POD 类型的数组等,都属于POD 类型;而Box String Vec等不能按字节复制的类型,都不属于POD 类型。但是,反过来 讲,也并不是所有满足POD 的类型都应该实现Copy trait,是否实现Copy 取决于业务需求。


Clone的 含 义

Clone的全名是std::clone::Clone。 它的完整声明如下:

pub trait Clone:Sized {
  fn clone(&self)->Self;
  fn clone_from(&mut self,source:&Self){
    *self =source.clone()
  }
}

它有两个关联方法,分别是clone_from和 clone,clone_from是有默认实现的,依赖于 clone 方法的实现。clone 方法没有默认实现,需要手动实现。

clone方法一般用于“基于语义的复制”操作。所以,它做什么事情,跟具体类型的作用 息息相关。比如,对于Box 类型,clone执行的是“深复制”;而对于Rc 类型,clone做的事 情就是把引用计数值加1。

虽 然Rust 中的clone 方法一般是用来执行复制操作的,但是如果在自定义的clone函数中做点别的什么工作,编译器也没办法禁止。你可以根据需要在clone 函数中编写任意的 逻 辑 。

但是有一条规则需要注意:对于实现了copy 的类型,它的clone 方法应该跟copy 语 义 相容,等同于按字节复制。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐