Rust const、static使用详解

在软件开发过程中,如果一个变量总是保持不变,我们可以声明为常量,如果一个变量全局唯一,可以使用静态变量,如果既是常量又是全局变量,则可以同时声明这2种特性,例如java可以这样  public static final int ID = 123。

Rust语言中使用const, static来实现这2个场景,但与其他语言稍有不同,rust中的const 和 static不能一起使用。

首先,我们先看下const, const有以下特点

  1. 每个使用const常量的地方,相当于拷贝了一份const常量,所以如果有 3 个地方用到常量,则会调用3次该类型的drop函数, 但如果没有地方用到

  2. 如果是定义常量引用,则不会有drop函数调用,因为使用他的地方是拷贝引用,没有生成对象

  3. 常量自定义结构体裸指针,不能实现Drop trait,否则编译不过

 我们先看下使用常量对象

:   FOO  =  FOO {};
 {}
() {
    test_const();
    test_const();
    test_const_ref(&);
}
(foo:FOO){}
(foo:&FOO){}
 FOO {
    (&) {
        ();
    }
}

输出如下,我们调用了3次函数,drop也被调用了次,说明 每个函数调用的地方,都生成了一个FOO对象

Finished dev [unoptimized + debuginfo] target(s) in 0.87s
Running `target\debug\rtest.exe`
FOO drop
FOO drop
FOO drop

再看下使用常量引用

:   &FOO  =  &FOO {};
 {}
() {
   test_const_ref();
    ()
}
(foo:&FOO){}
 FOO {
    (&) {
        ();
    }
}

运行结果如下,可以看到,使用常量引用,没有drop函数调用

   ()  0
      `\\`

再看下常量裸指针

:  *FOO  =  &FOO {} *FOO;
 {}
() {
    ()
}
 FOO {
    (&) {
        ();
    }
}

运行会编译出错

error: untyped pointers are not allowed  constant
  --> src\main.rs::
   |
 |  FOO_PTR:  * FOO  =  &FOO {}  * FOO;
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to previous error;  warning emitted

去掉Drop impl后

:  *FOO  =  &FOO {} *FOO;
 {}
() {
    ()
}


编译成功
    Finished dev [unoptimized + debuginfo] target(s)  s
     Running `target\debug\rtest.exe`
ok

下面再看看static, static有以下特点

  1. static变量全局唯一,生命周期在整个应用中,所有权不能转移,drop函数不会被调用

  2. 不可变static变量必须实现Sync trait,以实现跨线程访问

  3. 可变static 变量可以不实现 sync特质,对可变static的访问需要unsafe块或unsafe函数中使用。该类型变量在多线程访问时,必须确保安全性,比如加锁, 可变static 必须在unsafe中访问

先看下 1,所有权不能转移

:FOO = FOO {} ;
 {}
() {
    test();
    ()
}
(foo:FOO) {
}

test函数需要FOO的所有权,可以看到编译会出错

error[E0507]: cannot move out of  item 
  --> src\main.rs::
   |
 |     test(FOO);
   |          ^^^ move occurs because  has  , which does not implement the  trait

error: aborting due to previous error;  warning emitted

再看看析构函数方面

:FOO = FOO {} ;
 {}
() {
    test_ref(&);
    test_ref(&);
    ()
}
(foo:&FOO) {
}
 FOO {
    (&) {
        ();
    }
}
输出,没有调用析构函数 
    Finished dev [unoptimized + debuginfo] target(s)  s
     Running `target\debug\rtest.exe`
ok

对于2,跨线程访问,需要实现Sync trait. 由于 Rust中,只要Struct/Enum中的所有成员是Sync的,则该Struct/Enum也是Sync

以下FOO,i32自动是Sync,所以编译成功

:FOO = FOO{: &} ;
 {:  &,
}
() {}

改成下面

:FOO = FOO{
    : &*} ;
 {
     : *,
}
() {}

编译出错,原因是 裸指针不是Sync的,所以报错

error[E0277]: `* ` cannot be shared between threads safely
  --> src\main.rs::
   |
 | /    FOO:FOO = FOO{
 | |     id: &  * 
 | | } ;
   | |___^ `* ` cannot be shared between threads safely

对于3,可变static变量, 非Sync的类型也可以声明,如以下例子

:FOO = FOO{
    : &*} ;
 {
     :  *,
}
() {}

最后,需要注意的是,unsafe是程序员自己保证安全性。以下例子演示 static mut在多线程下,数据不一致的问题

use std::collections::HashSet;

static mut clock:i32 = 0;
fn main() {
   let mut res = HashSet::new();
   for i in 0 .. 200 { //测试200次,次数够多,出现数据不一致的几率才会大
       res.insert(test_mut_static_in_threads());//test_mut_static_in_threads函数,多线程对全局变量clock递增,返回递增后的结果,存入set中去重
       unsafe {
           clock = 0;//将全局变量清零
       }
   }
   for i in res {
       println!("num is .... {}", i);
   }
}

fn test_mut_static_in_threads() -> i32 {
   let mut vv = vec![];
   for i in 0 .. 1000  {
       let v = std::thread::spawn(||{
           unsafe {
               clock = clock + 1;
           }
       });
       vv.push(v);
   }
   for v in vv {
       v.join().unwrap();
   }
   unsafe {
       clock
   }
}

看下运行结果,可以看到多线程下,全局变量自增不一致,如果需要保证一致性,需要加锁或者使用并发原语

: 3 warnings emitted


上一篇:二进制日志配置和运维管理


下一篇:Go sync.Pool 浅析