Rust——智能指针

声明:本文假设你有一定的程序设计基础,假设你学习过一些智能指针方面的内容,所以关于智能指针的介绍,作者在这里不多说了。

目录

 最简单的智能指针Box

Deref trait

简单的解引用

Box的解引用

关于println!的说明

区分引用和变量本身并了解编译器为我们添加了哪些代码

不要将引用和借用的概念分离开

Rust需要编译时确定类型大小

递归类型无法在编译时确定大小

 实现一个自己的智能指针

定义

关联函数new 

实现Deref trait

了解编译器又为我们实现了哪些代码

 实现Drop trait

函数和方法的隐式解引用强制转换

Box的实战训练

引用型智能指针Rc

Rc的克隆行为是增加计数

Rc同样实现了Deref和Drop trait

内部可变性模式

替身测试

RefCell

RefCell的基本使用

 RefCell失败的情况下

RefCell是如何检查借用规则的

回看例子

结合Rc和RefCell来拥有多个可变数据所有者

不可能通过代码分析来检测隐藏的错误

总结


最简单的智能指针Box<T>

允许使用一个Box来指向堆区上的数据,就像这样

fn work_1() {
    let p = Box::new(10);
    
}

就像C中的指针一样,智能指针也允许我们使用解引用操作符来解引用指针指向的数据

fn work_1() {
    let p = Box::new(10);

    let val = *p;
    println!("*p={}",*p);
    println!("val={}",val);
}

运行

jan@7X:~/Code/Rust/smart_pointer$ cargo run
   Compiling smart_pointer v0.1.0 (/home/jan/Code/Rust/smart_pointer)
    Finished dev [unoptimized + debuginfo] target(s) in 0.20s
     Running `target/debug/smart_pointer`
*p=10
val=10

p本身是一个栈上的指针,指向堆区的数据。当p离开作用域的时候,Box的对对象,也就是存储在栈上的数据会被回收,这是rust的语言层面提供的,并且其指向的堆内存也会随其回收,着不是rust语言层面提供的(具体的实现方法,下面会将)。这就是所谓智能。

Deref trait

所说的deref trait,仅仅是一个trait,实现 Deref trait 允许我们重载 解引用运算符,就像是C++中的operator*,通过这种方式实现 Deref trait 的智能指针可以被当作常规引用来对待,可以编写操作引用的代码并用于智能指针。

简单的解引用

引用,或者借用,就是一种指针类型。所以,适用于C中的规则,在rust中也可以看见。就像这样。

#[allow(unused)]
fn work_2() {
    let a = 10;
    let ra = &a;
    println!("a={},*ra={}",a,*ra);
}

Box的解引用

因为Box实现了上面所说的Deref trait所以可以使用其*来像普通指针一样访问数据。

#[allow(unused)]
fn work_3() {
    let a = Box::new(5);
    println!("{}",*a);
}

关于println!的说明

实际上,在使用prinln!宏的时候,编译器会为我们的引用(就是指针)自动的加上解引用符号。

就像这样,他们输出的值都是一样的,但也仅仅就是输出的值一样而已。

#[allow(unused)]
fn work_2() {
    let a = 10;
    let ra = &a;
    println!("a={},ra={},*ra={}",a,ra,*ra);
}

对于Box来也是可行的,但是原因是因为Box实现了Displat这个trait

#[allow(unused)]
fn work_3() {
    let a = Box::new(5);
    println!("{}",*a);
    println!("{}",a); //error
}

区分引用和变量本身并了解编译器为我们添加了哪些代码

他们并不能相比较,因为rust是强类型语言,比如,你不能用用一个i32类型和一个&i32来比较,就像这样。

fn work_4() {
    let i = 32;
    let ref ri = i;
    assert_eq!(i,ri);
}

我们会得到一个这样的编译错误

error[E0277]: can't compare `{integer}` with `&{integer}`
   --> src/main.rs:108:5
    |
108 |     assert_eq!(i,ri);
    |     ^^^^^^^^^^^^^^^^ no implementation for `{integer} == &{integer}`

但是引用在一些情况却又像变量本身一样,类似C++的引用一样。比如调用结构体的方法。

#[allow(unused)]
fn get_len(s: &str) -> usize {
    s.len()
    // 等价于(*s).len() 
    // 注意区别于*s.len
}

同样的,在调用方法的时候编译器会为我们自动进行解引用。

不要将引用和借用的概念分离开

看到网上有人说借用是借用,引用是引用,让人一头雾水。其实引用和借用不是区分开来说的。我们可以看一下官方的文档是如何解释借用和引用的。

我们将创建一个引用的行为称为 借用borrowing)。正如现实生活中,如果一个人拥有某样东西,你可以从他那里借来。当你使用完毕,必须还回去。

从中可以看出,官方把借用描述成一种行为,为不是某个数据类型。就好比这样。

#[allow(unused)]
fn work_5() {
    let i = 10; //让i绑定10,就是说现在这个10的所有权是i
    let ref r1 = i; // 把i借给一个不可变的引用r1,发生借用这个行为
    let r2 = &i; // 把i借给一个不可变课引用r2,发生借用这个行为
}

两种借用写法本质是相同的,只不过是语法不同,请不要将他们的概念分离开。

通常我们说一个变量的借用,是指的这个变量的引用,不指这个借用的行为。

就像我可以说人r1是i的一个借用,也可以说r1是i的引用。引用和借用在这里是同义词。 

有了这些基础,我们就可以进行进一步的探索。

Rust需要编译时确定类型大小

Rust中,分配在栈上的数据总是要在编译时确定大小。

指针类型毫无疑问是一个在编译时可确定大小的类型。

递归类型无法在编译时确定大小

有关这部分,请查阅Rust官方文档,官方教程在这里讲的很好。

使用 Box<T> 指向堆上的数据 - Rust 程序设计语言 中文版

 实现一个自己的智能指针

我们将编写一个自己的只能指针,确保其在堆区开辟数据,在离开作用域的时候drop。

定义

为了保证不分散注意力,这里的Box我们仅仅定义为一个简单包装了T类型的一个元组结构体

struct Box<T> (T);

 否则我们还要使用全局的分配器,那也是一个很大的话题,不在本篇讨论范围内。
 

关联函数new 

我们仅仅需要返回一个元组结构体,不再堆区开辟任何数据

struct MyBox<T> (T);

impl<T> MyBox<T> {
    fn new(val: T) -> Self {
        MyBox(val)
    }
}

实现Deref trait

显而易见的,返回&self.0即可

impl<T> Deref for MyBox<T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

现在这就是一个具备解引用功能的智能指针了。

如果你还不知道什么是trait的关联类型(type Target = T),请查阅有关文章或者书籍。

了解编译器又为我们实现了哪些代码

你可能会奇怪上Deref trait的返回值,解引用操作应当返回最原始的数据,而非引用。

其实,如果一个T类型实现Deref这个trait,那么在解引用的时候,编译器就会自动的为我们生成这样的代码。

let t = T::new(some_val);
*t 等价于 *(t.deref())

至于编译器为什么这样做官方有着很好的解释

Rust 将 * 运算符替换为先调用 deref 方法再进行普通解引用的操作,如此我们便不用担心是否还需手动调用 deref 方法了。Rust 的这个特性可以让我们写出行为一致的代码,无论是面对的是常规引用还是实现了 Deref 的类型。

deref 方法返回了一个值的引用,而 *(y.deref()) 括号外边的普通解引用仍然必须存在的原因是因为所有权。如果 deref 方法直接返回值而不是值的引用,其值(的所有权)将被移出 self。在这里以及大部分使用解引用运算符的情况下,我们并不希望获取 MyBox<T> 内部值的所有权。

注意,每次当我们在代码中使用 * 时, * 运算符都被替换成了先调用 deref 方法再接着使用 * 解引用的操作,且只会发生一次,不会对 * 操作符无限递归替换

 实现Drop trait

实现了drop的struct,就像实现的析构函数一样,虽然rust中没有析构函数一说,但是其行为却和析构函数类似。

impl<T> Drop for MyBox<T> {
    fn drop(&mut self) {
        println!("析构一些资源");
    }
}

测试一下

#[allow(unused)]
fn work_6() {
    let a = MyBox::new(10);
}

结果正确

jan@7X:~/Code/Rust/smart_pointer$ cargo run
   Compiling smart_pointer v0.1.0 (/home/jan/Code/Rust/smart_pointer)
    Finished dev [unoptimized + debuginfo] target(s) in 0.24s
     Running `target/debug/smart_pointer`
析构一些资源

函数和方法的隐式解引用强制转换

或许这个标题看起来有些令人害怕,其实本质上就是特殊情况下的隐式类型转换而已。

简单的来说如果一个类型T实现了Deref trait,并且这个T的关联类型为U,那么就可以实现隐式的从T到&U的转换,因为编译器可以自动帮助我们调用deref这个方法。

最经典的例子就是&String到&str的转换,也就是字符串到字符串切片的转换。

我们来看一下这个函数,他做的功能很简单,接受一个字符串切片,并打印其长度和字符串本身。

#[allow(unused)]
fn print_str_and_len(s: &str) {
    println!("str={},len={}",s,s.len());
}
fn main() {
    let s = "hello";
    let s1 = String::from("hello");
    print_str_and_len(s);
    print_str_and_len(&s1);
}

运行结果

jan@7X:~/Code/Rust/smart_pointer$ cargo run
   Compiling smart_pointer v0.1.0 (/home/jan/Code/Rust/smart_pointer)
    Finished dev [unoptimized + debuginfo] target(s) in 0.23s
     Running `target/debug/smart_pointer`
str=hello,len=5
str=hello,len=5

为什会这样,Rust是强类型语言,一个&str怎么能接受一个&String类型?

其实这里就发生了我们所说的隐式的解引用强制转换。

我们来看一下String的源代码

#[stable(feature = "rust1", since = "1.0.0")]
impl ops::Deref for String {
    type Target = str;

    #[inline]
    fn deref(&self) -> &str {
        unsafe { str::from_utf8_unchecked(&self.vec) }
    }
}

你会发现其关联类型为str,返回一个&str类型,正好符合我们上述所说的规则。于是,&String就转换为了&str。

有趣的是,这个强制转换可以递归的进行,直到不能再解引用为止。

我们上面实现的MyBox struct实现了一个Targe为T的deref方法,所以,一个MyBox<T>就可以进行上述的转换。并且如果这个T也实现了deref的话,T又能变为&U,一直这样的递归下去。

我们可以尝试一下。

#[allow(unused)]
fn work_7() {
    let s = MyBox::new(String::from("hello"));
    print_str_and_len(&s);
}

运行

jan@7X:~/Code/Rust/smart_pointer$ cargo run
   Compiling smart_pointer v0.1.0 (/home/jan/Code/Rust/smart_pointer)
    Finished dev [unoptimized + debuginfo] target(s) in 0.21s
     Running `target/debug/smart_pointer`
str=hello,len=5
析构一些资源

 很好。

Box<T>的实战训练

你可以尝试着用Box在Rust中实现链表

使用Box<T>,Rust实现链表是一件非常折磨的事情,作者在这里提供一份简单的链表供读者参考。

#[derive(Debug)]
struct ListNode<T: fmt::Display> {
    data: T,
    next: Option<Box<ListNode<T>>>,
}

impl<T: fmt::Display> ListNode<T> {
    fn new(val: T) -> ListNode<T> {
        ListNode { data: val ,next: None }
    }
}



#[derive(Debug)]
struct ForwardList<T: fmt::Display> {
    size: u32,
    head: Option<Box<ListNode<T>>>
}

impl<T: fmt::Display> ForwardList<T> {
    fn new() -> ForwardList<T> {
        ForwardList { size: 0, head: None }
    }

    
    fn push_front(&mut self, val: T) {  
        let mut node = ListNode::new(val);
        if let Some(box_) = self.head.take() {
            node.next = Some(box_);
        }
        self.head = Some(Box::new(node));
        self.size += 1;
    }
    
    fn display(&self) {
        print!("[");
        let mut now = &self.head;
        let mut first = true;
        while let Some(v) = now {
            match first {
                true => {
                    print!("{}",v.data);
                    first = false;
                },
                false => print!(", {}",v.data),
            }
            match &v.next {
                None => {
                    break;
                },
                node => {
                    now = &node;
                }
            }
        }
        println!("]");
    }
    #[allow(unused)]
    fn front(&self) -> Option<&T> {
        match &self.head {  
            None => None,
            Some(node) => Some(&node.data),
        }
    }

    fn clear(&mut self) {
        self.head = None;
    }
}

引用型智能指针Rc<T>

关于什么是引用计数,作者这里就不说了。现在这个环境来说,能够学习rust的,想必都有一些其他语言的基础。如果你来自C++就像作者一样,肯定不会陌生shared_ptr等系列的智能指针,Rc就和shared_ptr的概念一样,只是写法不同。

使用Rc需要use std::rc::Rc,Rc并没有被预导入。

在学习Rc前,我们应当做一些准备。

struct Foo(i32);

impl Drop for Foo {
    fn drop(&mut self) {
        println!("drop!");
    }
}

impl Foo {
    fn new(x: i32) -> Self {
        Foo(x)
    }
}

很简单,这是在这个struct析构的时候进行一条打印消息。

Rc的克隆行为是增加计数

#[allow(unused)]
fn work_9() {
    let r = Rc::new(Foo::new(10));
    let r1 = r.clone();
    let r3 = Rc::clone(&r);
    println!("{},{},{}",Rc::strong_count(&r),Rc::strong_count(&r1),Rc::strong_count(&r3));
}

运行结果

jan@7X:~/Code/Rust/smart_pointer$ cargo run
   Compiling smart_pointer v0.1.0 (/home/jan/Code/Rust/smart_pointer)
    Finished dev [unoptimized + debuginfo] target(s) in 0.22s
     Running `target/debug/smart_pointer`
3,3,3
drop!

Rc同样实现了Deref和Drop trait

上面的代码我们验证了Rc实现了Drop trait,同样的Rc也实现了Deref trait

#[allow(unused)]
fn work_10() {
    let r = Rc::new(10);
    println!("{}",b);
}

对于其他的智能指针,也都同样的实现了这两个trait,下面不再说明了。

内部可变性模式

如果说Rust中的很多知识都能和C++类比的话,那么这个内部可变性模式很难找到很好的类比。因为借用规则是Rust独有的,这个模式有何借用规则有关。

声明:此部分作者节后官方文档来说明 

你可以查看官方文档

RefCell<T> 与内部可变性模式 - Rust 程序设计语言 中文版 (rustwiki.org)

作者在这里基于官方文档来进行这里的讲述 


我们来看一下官方对于这个模式是如何描述的

内部可变性Interior mutability)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时也可以改变数据,这通常是借用规则所不允许的。为了改变数据,该模式在数据结构中使用 unsafe 代码来模糊 Rust 通常的可变性和借用规则。

提取出主干就是能在unsafe中让不可变的引用也可以改变数据,类似C++中的mutable关键字?不过二者还是有区别的。

替身测试

测试替身test double)是一个通用编程概念,它代表一个在测试中替代某个类型的类型。mock 对象 是特定类型的测试替身,它们记录测试过程中发生了什么以便可以断言操作是正确的。

虽然 Rust 中的对象与其他语言中的对象并不是一回事,Rust 也没有像其他语言那样在标准库中内建 mock 对象功能,不过我们确实可以创建一个与 mock 对象有着相同功能的结构体。

如下是一个我们想要测试的场景:我们在编写一个记录某个值与最大值的差距的库,并根据当前值与最大值的差距来发送消息。例如,这个库可以用于记录用户所允许的 API 调用数量限额。

该库只提供记录与最大值的差距,以及何种情况发送什么消息的功能。使用此库的程序则期望提供实际发送消息的机制:程序可以选择记录一条消息、发送 email、发送短信等等。库本身无需知道这些细节;只需实现其提供的 Messenger trait 即可。

我将官方文档中的代码加上注释,然后呈现在这里,可以看得更清晰一点。

//一个用来记录信息的trait
pub trait Messenger {
    fn send(&self,msg: &str);
}


//检查磁盘容量的一个结构体,T是任何实现了Messenger的类型,用于send消息
#[allow(unused)]
pub struct LimitTracker<'a, T:Messenger> {
    messenger: &'a T, //这是一个T类型的引用,就像一个指针一样,策略模式?
    value: usize, //当前值
    max: usize,   //最大值
}

impl<'a,T> LimitTracker<'a,T> 
    where T: Messenger
{
    //new 方法,自己看一下即可
    pub fn new(messenger: &T, max: usize) -> LimitTracker<T> {
        LimitTracker {
            messenger,
            value: 0,
            max,
        }
    }

    //这个方法能够设置val,并且在超过对应限度的时候使用messenger的send方法,发送一条信息。
    //由于这个方法是默认返回类型(),如果想对这个功能进行测试,需要一些手法,下面再说
    pub fn set_value(&mut self, value: usize) {
        self.value = value;
        let percentage_of_max = self.value as f64 / self.max as f64;
        if percentage_of_max >= 1.0 {
            self.messenger.send("Error: You are over your quata!");
        } else if percentage_of_max >= 0.9 {
             self.messenger.send("Urgent warning: You've used up over 90% of your quota!");
        } else if percentage_of_max >= 0.75 {
            self.messenger.send("Warning: You've used up over 75% of your quota!");
        }
    }


}

#[cfg(test)]
mod tests {
    use super::*;
    //简单的一个结构体,仅仅对Vec进行了一次包装
    struct MockMessenger {
        sent_messenger: Vec<String>,
    }

    //关联函数new
    impl MockMessenger {
        fn new() -> Self {
            MockMessenger { sent_messenger: vec![] }
        }
    }

    //实现Messenger trait
    impl Messenger for MockMessenger {
        fn send(&self,msg: &str) {
            //当接受到了一个消息,将其放在sent_messenger(type: Vec<String>)中
            self.sent_messenger.push(String::from(msg)); //这里会出现问题
        }
    }

    #[test]
    //用于断言测试超过%75,是否发送一个消息
    fn it_sends_an_over_75_percent_waring_message() {
        let mock_messenger = MockMessenger::new();
        //这里将messanger设置为上面的MockMessenger的一个变量,这样在每次send消息
        //Vec中就会push一次,我们就可以使用len方法来断言是否成功发送消息
        //这种手法就是替身测试
        let mut limit_tracker = LimitTracker::new(&mock_messenger,100);

        limit_tracker.set_value(100);
        assert_eq!(mock_messenger.sent_messenger.len(),1);
    }
}

 上述的代码通过不了编译

jan@7X:~/Code/Rust/smart_pointer$ cargo test
   Compiling smart_pointer v0.1.0 (/home/jan/Code/Rust/smart_pointer)
error[E0596]: cannot borrow `self.sent_messenger` as mutable, as it is behind a `&` reference
  --> src/lib.rs:63:13
   |
3  |     fn send(&self,msg: &str);
   |             ----- help: consider changing that to be a mutable reference: `&mut self`
...
63 |             self.sent_messenger.push(String::from(msg)); //这里会出现问题
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `self` is a `&` reference, so the data it refers to cannot be borrowed as mutable

For more information about this error, try `rustc --explain E0596`.
error: could not compile `smart_pointer` due to previous error

 正如官方所言

不能修改 MockMessenger 来记录消息,因为 send 方法获取了 self 的不可变引用。我们也不能参考错误文本的建议使用 &mut self 替代,因为这样 send 的签名就不符合 Messenger trait 定义中的签名了(可以试着这么改,看看会出现什么错误信息)。

这正是内部可变性的用武之地!我们将通过 RefCell 来储存 sent_messages,然后 send 将能够修改 sent_messages 并储存消息。

 在介绍解决办法之前,先放松一下,看看RefCell智能指针

RefCell<T>

 不同于 Rc<T>RefCell<T> 代表其数据的唯一的所有权。这点和Box<T>很像,但是RefCell和Box是有很大的区别的。RefCell<T> 是用于当你确信代码遵守借用规则,而编译器不能理解和确定的时候。

Box对于借用规则是在编译时期检查的,如果不符合借用规则,会发生编译器的错误。

RefCell对于借用规则是在运行时期检查的,需要使用unsafe代码块,如果不符合借用规则,程序会panic。

回忆一下借用规则

  1. 在任意给定时刻,只能拥有一个可变引用或任意数量的不可变引用 之一(而不是两者)。
  2. 引用必须总是有效的。

RefCell的基本使用

使用RefCell和其他的智能指针有些不同。

首先RefCell没有实现Dispaly,没有实现Deref,但是其创建方法和其他的智能指针一样,并且同样实现了Drop。虽然说其没有实现Deref trait,但是提供了另两个方法来代替。

#[allow(unused)]
fn work_11() {
    let r =  RefCell::new(Foo::new(10));
    println!("{}",r.borrow());
    println!("{}",r.borrow_mut());
}

 运行

10
10
drop!

 RefCell失败的情况下

违反了借用规则的情况下程序会panic

#[allow(unused)]
fn work_12() {
    let r = RefCell::new(Foo::new(10));
    let b1 = r.borrow_mut();
    let b2 = r.borrow_mut();
}

 b1和b2是可变借用,会违反借用规则,程序panic,至于work_11中为什么程序没有painc,是因为不可变借用在用于执行完println!随之销毁,因为其是一个无名的借用,生命周期就是其所在的那行代码。

jan@7X:~/Code/Rust/smart_pointer$ cargo run
   Compiling smart_pointer v0.1.0 (/home/jan/Code/Rust/smart_pointer)
    Finished dev [unoptimized + debuginfo] target(s) in 0.19s
     Running `target/debug/smart_pointer`
thread 'main' panicked at 'already borrowed: BorrowMutError', src/main.rs:223:16
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
drop!

RefCell是如何检查借用规则的

其有两个计数器,如果得到其一个不可变借用,相应的计数器加一,对于可变借用,也是同样。如果RefCell发现其计数器不符合借用规则,就会panic。 

关于更多的RefCell用法,请查看官方文档说明 

回看例子

 这正是内部可变性的用武之地!我们将通过 RefCell 来储存 sent_messages,然后 send 将能够修改 sent_messages 并储存消息。

 于是这样修改。

#[cfg(test)]
mod tests {
    use super::*;
    use std::{cell::RefCell, borrow::Borrow};
    //简单的一个结构体,仅仅对Vec进行了一次包装
    struct MockMessenger {
        sent_messenger: RefCell<Vec<String>>,
    }

    //关联函数new
    impl MockMessenger {
        fn new() -> Self {
            MockMessenger { sent_messenger: RefCell::new(vec![]) }
        }
    }

    //实现Messenger trait
    impl Messenger for MockMessenger {
        fn send(&self,msg: &str) {
            //当接受到了一个消息,将其放在sent_messenger(type: Vec<String>)中
            self.sent_messenger.borrow_mut().push(String::from(msg));
        }
    }

    #[test]
    //用于断言测试超过%75,是否发送一个消息
    fn it_sends_an_over_75_percent_waring_message() {
        let mock_messenger = MockMessenger::new();
        //这里将messanger设置为上面的MockMessenger的一个变量,这样在每次send消息
        //Vec中就会push一次,我们就可以使用len方法来断言是否成功发送消息
        let mut limit_tracker = LimitTracker::new(&mock_messenger,100);

        limit_tracker.set_value(100);
        assert_eq!(mock_messenger.sent_messenger.borrow().len(),1);
    }
}

 运行这个测试没有发生断言,成功。

结合Rc和RefCell来拥有多个可变数据所有者

Rc允许对相同的数据有多个所有者,RefCell允许对不可变引用改变,结合二者,我们可以时间有多个所有者并且同时课改变其值的能力。

正如官方文档提供的代码。

#[allow(unused)]
use List::{Cons,Nil};
#[allow(unused)]
fn work_13() {
    let value = Rc::new(RefCell::new(5));
    //5 -> Nil
    let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));
    //6 -> 5 ->  Nil
    let b = Cons(Rc::new(RefCell::new(6)), Rc::clone(&a));
    //10 -> 5 -> Nil
    let c = Cons(Rc::new(RefCell::new(10)), Rc::clone(&a));

    //获取value的可变借用,将其加10,注意:现在有多个Rc指向value
    *value.borrow_mut() += 10;
    println!("a after = {:?}", a);
    println!("b after = {:?}", b);
    println!("c after = {:?}", c);
}

不可能通过代码分析来检测隐藏的错误

我们先抛出一个问题,是否有这样的一个程序,能够验证一个程序是否能够在有限的时间运行完?答案是不可以可。这就是大名鼎鼎的停机问题 ,如果读者对停机问题感兴趣,可以去翻看离散数学经典著作黑皮书,作者已经很好的为我们解释了。

最先解开这个问题的是图灵,就是我们的计算机鼻祖。这些都是题外话了。

主要想说的是,我们不可能通过对代码分析来计算程序的运行时(仅仅是字面意思,并非语言运行所需要的环境)的一些情况。如果一个代码拿来给人看,那人一定能看出来这个程序是否能够在有限的事件运行,但是机器不行,机器无法像人一样思考。所以,即使是Rust,也不可能在编译期间检查出所有的错误。Rust的检查器是保守的,着就意味着,Rust检查器不能证明你的程序完全正确,就不会给你编译通过,即便你的程序是正确的。这时候,我们就需要告诉编译器:虽然你不能看出来我的这个程序是完全正确的,但是我自己可以保证这个程序不会出问题(当然,你不可能有十分把握),然后绕过编译器的检,着正是智能指针和unsafe出现的一部分意义。

总结

通过智能指看见了Rust是如何进行指针的操作。Rust更倾向用户使用有安全保障的智能指针,而不是使用原生的裸指针。通过智能指针我们也能看见Rust语言层面的设计哲学。

说来也有意思,如果C++给人的形象是一个放荡不羁的少年,那么Rust就是朴素忠厚的农民,如果你要使用C++的自由,就必须要承受自由的代价,通过C++现代的变化我们可以看见,C++对内存这一块推出的智能指针,对自由度做出了一些限制。你要享受Rust带来安全性,就必须要承受安全的代价。但Rust却有unsafe代码和智能指针绕过安全规则。这些值得引发我们一些思考。


如果你发现了本文章的错误,请练习作者更正 

本文章并没有进行智能指针的所有说明,如果读者对这一部分感兴趣,可以查看Rust官方文档和Rust标准库

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值