Rust是什么
Rust 是一个系统编程语言,它注重三个方面:安全,速度和并发性。
特征:
1.没有垃圾回收机制,没有运行时,效率超过c++,直逼c语言
2.内存安全,并发安全,没有空指针
3.详细的入门指南和及其丰富的生态 https://github.com/rust-lang/rust ,https://crates.io/
它是如何做到的?
编译时保证和对内存生命周期的明确控制。
让我们来谈谈Rust中最重要的概念:“所有权”,以及它对并发编程(对程序员来讲通常是非常困难的任务)的启发。
所有权
所有权是Rust的核心概念,也是其独特的功能之一。 “所有权”是指允许哪部分的代码修改内存。
让我们从一些C++代码开始理解这个概念:
int *foo(void) { int i = 1234; return &i; } int bar(void) { int *num = foo(); return *num + 1; }
foo函数在栈上分配了一个整型,然后保存给一个变量i,最后返回了这个变量i的引用。这里有一个问题:当函数返回时栈内存变成失效。意味着在函数add_one第二行,指针num指向了垃圾值,我们将无法得到想要的结果。虽然这个一个简单的例子,但是在C++的代码里会经常发生。当堆上的内存使用malloc(或new)分配,然后使用free(或delete)释放时,会出现类似的问题,但是您的代码会尝试使用指向该内存的指针执行某些操作。 更现代的C ++使用RAII和构造函数/析构函数,但它们无法完全避免“野指针”。
Rust就可以不出现这种情况。 我们试试吧:
fn foo() -> &int { let i = 1234; return &i; } fn bar() -> int { let num = foo(); return *num + 1; }
当你尝试编译这个程序时,你会得到一个非常长的错误信息:
temp.rs:3:11: 3:13 error: borrowed value does not live long enough temp.rs:3 return &i; temp.rs:1:22: 4:1 note: borrowed pointer must be valid for the anonymous lifetime #1 defined on the block at 1:22... temp.rs:1 fn foo() -> &int { temp.rs:2 let i = 1234; temp.rs:3 return &i; temp.rs:4 } temp.rs:1:22: 4:1 note: ...but borrowed value is only valid for the block at 1:22 temp.rs:1 fn foo() -> &int { temp.rs:2 let i = 1234; temp.rs:3 return &i; temp.rs:4 } error: aborting due to previous error
为了完全理解这个错误信息,我们需要谈谈“拥有”某些东西意味着什么。 所以现在,让我们接受Rust不允许我们用野指针编写代码,一旦我们理解了所有权,我们再回头看这块代码。
我们先打个比方。 我喜欢读书,有时候我真的很喜欢一本书,会推荐给我的朋友们来读。 当我读我的书时,我拥有它:这本书是我所拥有的。 当我把书”借“给你,在特定的一段时间它是属于你的,然后你把它还给我,我又拥有它了。 对吗?
这个概念也直接应用于Rust代码:一些代码“拥有”一个指向内存的特定指针。 它是该指针的唯一所有者。 它还可以暂时将该内存借给其他代码:这些代码“借”了它。 借用它的这一段时间,称为“生命周期”。
这是关于所有权的所有。 那似乎并不那么难,对吧? 让我们回到那条错误信息:error: borrowed value does not live long enough。 我们试图使用Rust的借用指针&,借出一个特定的变量i。 但Rust知道函数返回后该变量无效,因此它告诉我们:
borrowed pointer must be valid for the anonymous lifetime #1 ... but borrowed value is only valid for the block。
完美!
这是栈内存的一个很好的例子,但堆内存呢? Rust有第二种指针,一个‘唯一性‘指针,你可以用?创建。 像这样:
fn foo() -> ~int { let i = ~1234; return i; } fn bar() -> int { let num = foo(); return *num + 1; }
此代码将成功编译。 请注意,我们使用指针指向该值而不是将1234分配给栈:~1234。 你可以大致比较这两行:
// rust let i = ~1234; // C++ int *i = new int; *i = 1234;
Rust能够推断出类型的大小,然后分配正确的内存大小并将其设置为您要求的值。 这意味着无法分配未初始化的内存:Rust没有null的概念。万岁!
Rust和C ++之间还有另外一个区别:Rust编译器还计算了i的生命周期,然后在它无效后插入相应的free调用,就像C ++中的析构函数一样。 您可以获得手动分配堆内存的所有好处,而无需自己完成所有工作。 此外,所有这些检查都是在编译时完成的,因此没有运行时开销。这意味着如果你编写了正确的C ++代码,你将编写出与C++代码一样的Rust代码,而且由于编译器的帮忙,编写错误的代码在编译时就不会通过。
你已经看到了一种情况,所有权和生命周期有利于防止在别的语言中通常会出现的危险代码。
现在让我们谈谈另一种情况:并发。
并发
并发是当前软件世界中一个令人头疼的热门话题。 对于计算机科学家来说,它一直是一个有趣的研究领域。随着互联网的使用爆炸式增长,人们希望现有的条件可以服务更多的用户数量。 并发是实现这一目标的方式。 但并发代码有一个很大的缺点:它很难调试,因为它是非确定性的。 编写好的并发代码有几种不同的方法,但让我们来谈谈Rust的所有权和生命周期的概念如何帮助实现正确并且并发的代码。
首先,让我们看一下Rust中的简单并发示例。 Rust允许你启动task,这是轻量级的“绿色”线程(协程)。 这些任务没有任何共享内存,因此,我们使用“通道”在task之间进行通信。 像这样:
fn main() { let numbers = [1,2,3]; let (port, chan) = Chan::new(); chan.send(numbers); do spawn { let numbers = port.recv(); println!("{:d}", numbers[0]); } }
在这个例子中,我们创建了一个数字的vector。 然后我们创建一个新的Chan,这是Rust实现通道的包名。 这将返回通道的两个不同端:通道(channel)和端口(port)。 您将数据发送到通道端(channel),它从端口端(port)读出。 spawn函数可以启动一个task。 正如你在代码中看到的那样,我们在task中调用port.recv(),我们在外面调用chan.send(),传入vector。 然后打印vector的第一个元素。
默认情况下,Rust在通过channel发送时会copy vector, 这样即使它是可变的,也不会出现竞争。 但是,如果我们启动了很多task,而我们的数据非常庞大,那么为每个任务都copy副本会使我们的内存使用量膨胀而没有任何实际好处。
引入Arc。 Arc代表“原子引用计数”,它是一种在多个task之间共享不可变数据的方法。 看代码:
extern mod extra; use extra::arc::Arc; fn main() { let numbers = [1,2,3]; let numbers_arc = Arc::new(numbers); for num in range(0, 3) { let (port, chan) = Chan::new(); chan.send(numbers_arc.clone()); do spawn { let local_arc = port.recv(); let task_numbers = local_arc.get(); println!("{:d}", task_numbers[0]); } } }
这与我们之前的代码非常相似,除了现在我们循环三次,启动三个task,并在它们之间发送一个Arc。 Arc :: new创建一个新的Arc,.clone()返回Arc的新的引用,而.get()从Arc中获取该值。 因此,我们为每个task创建一个新的引用,将该引用发送到通道,然后使用引用打印出一个数字。 现在我们不copy vector。
Arcs非常适合不可变数据,但可变数据呢? 共享可变状态是并发程序的祸根。 您可以使用互斥锁(mutex)来保护共享的可变状态,但是如果您忘记获取互斥锁(mutex),则可能会发生错误。
Rust为共享可变状态提供了一个工具:RWArc。 Arc的这个变种允许Arc的内容发生变更。 看代码:
extern mod extra; use extra::arc::RWArc; fn main() { let numbers = [1,2,3]; let numbers_arc = RWArc::new(numbers); for num in range(0, 3) { let (port, chan) = Chan::new(); chan.send(numbers_arc.clone()); do spawn { let local_arc = port.recv(); local_arc.write(|nums| { nums[num] += 1 }); local_arc.read(|nums| { println!("{:d}", nums[num]); }) } } }
我们现在使用RWArc包来获取读/写Arc。 RWArc的API与Arc略有不同:有read和write方法允许您读取和写入数据。 这两个方法都将闭包作为参数,并且在写入的情况下,RWArc将获取互斥锁,然后将数据传递给此闭包。 闭包完成后,互斥锁被释放。
你可以看到Rust在你不记得获取锁的情况下是不可能让你改变共享变量的。 我们获得了共享可变状态的便利,同时保持不允许共享可变状态的安全性。
unsafe
虽然Rust编译器非常聪明,并且可以避免你通常犯的错误,但它不是人工智能,我们比编译器更聪明,在某些代码场景中,我们需要打破这种安全检查。 为此,Rust有一个unsafe关键字。 在一个unsafe的代码块里,Rust关闭了许多安全检查。 如果您的程序出现问题,您只需要审核您在不安全范围内所做的事情,而不是整个程序。
如果Rust的主要目标之一是安全,为什么要关闭安全? 嗯,实际上只有三个主要原因:与外部代码连接,例如将FFI写入C库;性能,(在某些情况下)围绕通常不安全的操作提供安全抽象。 我们的Arcs就是后一个目的的而使用unsafe关键字。
我们可以安全地分发对Arc的多个引用,因为我们确信数据是不可变的,因此可以安全地共享。 我们可以分发对RWArc的多个引用,因为我们知道我们已经将数据包装在互斥锁中,因此可以安全地共享。
但Rust编译器无法知道我们已经做出了这些处理,所以在Arc/RWArc的实现中,我们使用unsafe的块来做了(通常来说)一些危险的事情。但是我们暴露了一个安全的接口,这意味着Arc/RWArc不可能被错误地使用。
这就是Rust的类型系统如何让你不会犯一些使并发编程变得不可控的错误,同时也能获得像C ++等语言一样的效率。
原文:https://www.cnblogs.com/mignet/p/do_you_need_rust.html