当前位置:首页 » 编程软件 » rust编译用时分析

rust编译用时分析

发布时间: 2022-10-16 00:27:49

java和Rust在实现多线程编程时的异同

Java的实现
打开Follower.java里的这个函数

这里的Follower.this.invitations就是我们的消息队列,定义是:private LinkedList<Invitation> invitations;LinkedList不是线性安全的集合,需要我们加同步。具体的同步方法就是函数里写的,通过Java常见的用wait,notify和notifyall给对象加锁。
处理并发有wait、notify和notiyall,有兴趣的朋友可以去这里了解一下:http://www.importnew.com/16453.html。Follower就是一个等待leader发送invitation,处理并返回结果的过程。
Leader.java
这么一段代码:

里面就是Leader发送邀请inv,并等待follower返回结果的大概逻辑,通过对消息体加锁,是Java传统的实现多线程并发的方式。还有消费者的消息队列也会加锁,在Java里,有个对象叫LinkedBlockingQueue,是不用加锁就可以put和take的,但在例子里,我们选用了更简单的LinkedList,也是为了表现一下加锁的逻辑。
Rust的实现
Leader的结构为:

Follower的结构为:

对于其他语言转过来的同学,这里的Vec,i32,bool都很好理解,不过里面出现的Arc和Mutex,Sender,Receiver就是新东西了,上面这4个都是Rust标准库的东西,也是这次分享要介绍的重点对象,是这4个东西共同实现了消息的生产,传递和消费。
下面简单介绍一下分别是做什么用的:
Arc<T>实现了sync接口。Sync接口是做什么呢?权威资料是这么说的:当一个类型T实现了Sync,它向编译器表明这个类型在多线程并发时没有导致内存不安全的可能性。
如果看不懂不要紧,我们先看看实际中是怎么用的:

在这个例子里,我们关注这几句:
let data = Arc::new(Mutex::new(vec![1u32, 2, 3]));
let data = data.clone();
let mut data = data.lock().unwrap();
下面分别解释一下是做什么的:
简单的说Arc::new表明了这是通过clone()方法来使用的,每clone,都会给该对象原子计数+1,通过引用计数的方法来保证对象只要还被其中任何一个线程引用就不会被释放掉,从而保证了前面说的:这个类型在多线程并发时没有导致内存不安全的可能性。
如果我们不定义为Arc<>就传到其他线程使用,编译器会报:
error: capture of moved value: `data`
data[i] += 1;
我们可以记住clone()就是Arc的用法。
接下来我们看Mutex:
Mutex实现了send接口。同样,在权威资料里是这么描述的:这个类型的所有权可以在线程间安全的转移
那我们又是怎么用Mutex的呢?就是用lock().unwrap()。lock()的作用是获取对象,如果当前有其他线程正在使用Mutex<T>里面的T对象时,本线程就会阻塞,从而保证同时只有一个线程来访问对象,mutex也另外提供了try_lock()的方法,是不阻塞的,只要其他线程被占用,就返回err,通常Arc和Mutex都是一起使用的。
回到我最原始的题目,Mutex和Arc实现了对象本身的线程共享,但是在线程间如何传递这个对象呢?就是靠channel,channel通常是这么定义的let (tx, rx) = mpsc::channel();它会返回两个对象tx和rx,就是之前我提到的sender和receiver。
在我的Rust实现里,关键的语句是以下几个:
let leaders = (0..leader_cnt).map(|i|
Arc::new(Mutex::new(Leader::new(i,dance_types.len() as i32)))
).collect::<Vec<_>>();
这一句是new一堆leader出来,Arc和Mutex表明leader是可以多线程共享和访问的。
同样Follower也是:
let followers = (0..follower_cnt).map(|i|
Arc::new(Mutex::new(Follower::new(i,dance_types.len() as i32,leader_cnt)))
).collect::<Vec<_>>();
接下来这几句就有点不好理解了。

这里定义了一堆的sender和receiver,其中把他们都作为leader和follower的成员变量存起来。大概意思就是每一个leader都通过sender列表可以发送invitation给所有follower,同时又有单个receiver来接受所有follower发给自己的处理结果inviresult。
同样follower也是这么做。这样在之后每一个follower和leader作为一个线程跑起来之后,都能在相互之间建立了一条通信的通道。
这个是和Java实现多线程并发最大的不同之处!Java是通过给对象加锁,Rust是通过channel转移对象的所有权,在代码里,leader发送inv给folloer是下面这一句
match self.senders[*follower_id as usize].lock().unwrap().send(inv){,其中的lock().unwrap()是获得该leader对该follower的发送通道的所有权,send(inv)就是转移具体的发送对象invitation所有权了。
这个转移按照我的理解,应该是内存拷贝。就是在follower接收的时候,let inv = match self.receiver.recv() { ,原来leader里面的inv在send之后已经是不可访问了,如果你之后再次访问了inv,会报use of moved value错误,而follower里面的inv则是在follower的栈里新生成的对象,所以,在Java里面我只定义了invitation对象,但是在Rust里面,我要再定义一个InviResult,因为我即使在follower线程里面填了result字段,leader线程也不能继续访问inv了。所以需要依靠follower再次发送一个invresult给leader,所以整个Rust程序大概就是这么一个思路。
实践总结
之前我测试比较Java和Rust实现的性能时,由于没有把调试信息去掉,导致Java比Rust慢很多,特别是那些调试信息都是调用String.format,这是比几个string相加慢上10倍的方法,两者都去掉调试信息后,leader和follower都会2000的时候,在我低端外星人笔记本里,性能差别大概是2倍吧,没我想象中大,Rust的程序整个写下来比较费力,一方面是对ownership机制不熟,思维没有转变过来,另一方面Rust的确需要开发者分部分精力到语法细节上。
编者注:冯总也有一些其它的实践体会,请参见CSDN对冯耀明的专访,请戳这里。也可以查看他的个人博客里的总结。
下面摘录采访中关于Rust的内容过来:
首先Rust里面的ownership和lifetime概念真的很酷,就因为这个概念实现无内存泄露,野指针和安全并发。
其次,Rust的语法不简单,也是有不少坑的,据说Rust的潜在用户应该是现在的C和C++程序员,他们可能会觉得比较习惯,说不定还 觉得更简单。由于ownership机制,一些在其他语言能够跑通的程序在Rust下就要调整实现了,它会改变你写程序的思维方式。据说一些写Rust超 过半年的程序员已经爱上它了!
我对Rust感受较深的是下面几点:
初学者不熟悉ownership机制,会无数次编译失败。但一旦编译成功,那么程序只剩下逻辑错误了。同样,由于ownership机制,将来在项目里修改Rust代码将可能是痛苦的过程,因为原来编译通过的代码可能加入新功能就编译不过了,这是我的猜测。
Rust编译速度慢,不过据说最近每一个Rust新发布的版本编译速度都比之前的版本提高了30%。
Rust没有类,有的是结构体加方法,我喜欢这种简单的概念。
Rust没有类继承,只有接口,虽然接口可以提供默认的实现。这样一来,在大型项目里原来类继承来重用代码的效果是否就要用成员变量实例来完成呢?
Rust没有null,取而代之的是None和Option<T>,也因此,结构体在初始化的时候必须初始化所有字段。
Rust有我一直很想要的错误值返回机制,而不必通过抛异常或者需要每每定义包含结果和错误体实现。
Rust用send和sync两个接口来处理多线程并发,其中Arc<T>和Mutex<T>分别实现了这两个接口,简单易用。
Rust目前没有一个强大的IDE,支持断点调试,变量监控等。
它跟现在动态语言是两个截然不同的方向,它适合一些资深的程序员,我倒是觉得有必要有这么一本书,叫《从C++到Rust,你需要改善的20个编程 习惯》,能从实践上告诉开发者Rust里我们应该遵从什么样的编程习惯。Rust未来是否像C那样流行开来成为新一代的主流语言没有人能够知道,但它绝对 是值得你去了解和关注的语言。
进一步的思考:反转链表 - Java和Rust的不同实现
Rust的list应该怎么定义,譬如反转列表又是怎么做呢?
由于ownership的机制和不存在空指针的情况,很多在其他带GC的语言能够跑起来的程序在Rust下面就要换一种做法。最近试用Rust的基础数据结构时,更加加强了我的看法。下面以最原始的链表list为例。
在Java中,考虑最基本的链表定义
class ListNode {
int val;
ListNode next;

ListNode(int x) {
val = x;
}

@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[");
sb.append(val);
ListNode pNext = this.next;
while (pNext != null) {
sb.append(",");
sb.append(pNext.val);
pNext = pNext.next;
}
sb.append("]");
return String.format("%s", sb.toString());
}
}
如果我们要反转链表,可以这么做:
public ListNode reverseList(ListNode head) {
if (head == null) {
return null;
}
ListNode pNext = head.next;
ListNode pPrevious = null;
while (head != null) {
pNext = head.next;
head.next = pPrevious;
pPrevious = head;
head = pNext;
}
return pPrevious;
}
那如果我们按照一般思维,在Rust里对应的实现就是这样子的:
struct ListNode{
id :i32,
next :Option<Box<ListNode>>
}
反转链表:
fn reverseList2(head :&mut Option<Box<ListNode>>) -> Option<Box<ListNode>> {
match *head{
None => None,
Some(head) => {
let mut head = Some(head);
let mut pNext = head.unwrap().next;
let mut pPrevious:Option<Box<ListNode>> = None;
while true {
match head {
None =>{break;}
_ =>{}
}
pNext = head.unwrap().next;
head.unwrap().next = pPrevious;
pPrevious = head;
head = pNext;
}
pPrevious
}
}
}
然后编译,报了以下错误:
=》match *head{

ERROR:cannot move out of borrowed content
=》 pNext = head.unwrap().next;
ERROR:cuse of moved value: `head`

这些错误就是因为Rust的ownership机制,让我们无法像Java或者C++里保存临时变量,特别是在循环里。反复试过各种写法,都行不通。
最后,换成这么来做
链表定义:
use List::*;

enum List {
Cons1(i32, Box<List>),
Nil,
}

// Methods can be attached to an enum
impl List {
#[inline]
fn new() -> List {
Nil
}

#[inline]
fn prepend(self, elem: i32) -> List {
Cons1(elem, Box::new(self))
}

fn len(&self) -> i32 {
match *self {
Cons1(_, ref tail) => 1 + tail.len(),
Nil => 0
}
}

fn stringify(&self) -> String {
match *self {
Cons1(head, ref tail) => {
format!("{}, {}", head, tail.stringify())
},
Nil => {
format!("Nil")
},
}
}
}

fn reverseList(list:List, acc:List ) -> List{
match list{
Cons1(val,tail) => {
reverseList(*tail,acc.prepend(val))
}
Nil => acc
}
}

fn main() {
let mut head = List::new();
let mut i=0;
while i < 10 {
i+=1;
head = head.prepend(i);
}
println!("{:30}",head.stringify());
let result = List::new();
let result = reverseList(head,result);
<span style="white-space:pre"> </span>println!("{:30}",result.stringify());
}
从结果可以看到,链表已经实现反转了。所以在Rust下面,很多做法都要换一下。有人说这就是Rust函数式编程的思维。我但愿这种递归式的做法不会有溢出。

⑵ Rust VS Python:为什么越来越流行,取代榜一 Python

2021 年,Python 又获得了 TIOBE 年度编程语言,排名已经是第一。而 Rust 依然在 20 名以外。但依然有人认为,Rust 甚至可能取代 Python。不过这不重要,认清两者的优缺点,进而合适的地方使用合适的语言,这才最重要。

在这个指南中,我们将比较 Rust 和 Python 这两门语言,同时将讨论它们各自的应用场景,回顾使用 Rust vs. Python 的优缺点,并解释 Rust 为什么越来越受欢迎(甚至可能取代 Python)。

Rust [1] 是一门系统编程语言,专注于安全,尤其是并发安全,支持函数式和命令式以及泛型等编程范式的多范式语言。Rust 在语法上和 C++ 类似,但是设计者想要在保证性能的同时提供更好的内存安全。Rust 最初是由 Mozilla 研究院的 Graydon Hoare 设计创造,然后在 Dave Herman, Brendan Eich 以及很多其他人的贡献下逐步完善的。Rust 的设计者们通过在研发 Servo 网站浏览器布局引擎过程中积累的经验优化了 Rust 语言和 Rust 编译器。

Rust 拥有 优秀的文档 [2] 、友好的编译器和有用的错误消息,以及顶级工具,包括集成包管理器、构建工具、支持自动完成和类型检查的智能多编辑器、自动格式化程序等等。

Rust 发布于 2010 年。虽然和 Python 相比,Rust 是一门年轻的语言,但是它的社区正在稳步增长。事实上,Rust 已经连续五年(2016,2017,2018,2019,2020)在 Stack Overflow 开发者调查的“最受喜爱编程语言”评选项目中摘取桂冠。

乍一看,Rust 的静态化和强类型化可能看起来有点极端。但从长远来看,这有助于防止意外的代码行为。

Python [3] 是一门旨在帮助开发人员更有效地工作和更有效地集成系统的编程语言。Python 提供了高效的高级数据结构,还能简单有效地面向对象编程。Python 语法和动态类型,以及解释型语言的本质,使它成为多数平台上写脚本和快速开发应用的编程语言,随着版本的不断更新和语言新功能的添加,逐渐被用于独立的、大型项目的开发。如果速度是最重要的,可以使用较低级别的 API 调用,如 CPython [4] 。

1991 年 Guido van Rossum 推出了 Python,以其代码的可读性、无分号和花括号而着称。

除了可扩展性之外,Python 还是一门解释型语言,这使得它比大多数编译型语言要慢。正如您可能期望的那样,Python 拥有一个庞大的库生态系统和一个庞大的专业社区。

Rust 被应用于系统开发、操作系统、企业系统、微控制器应用、嵌入式系统、文件系统、浏览器组件、虚拟现实的仿真引擎等。

当性能很重要的时候,Rust 是一种常用的语言,因为它能很好地处理大量数据。它可以处理 CPU 密集型的操作,如执行算法,这就是为什么 Rust 比 Python 更适合系统开发的原因。

Rust 保证了内存的安全性,让你可以控制线程行为和线程之间的资源分配方式。这使你能够构建复杂的系统,也使得 Rust 比 Python 更有优势。

总而言之,你应在以下情况下使用 Rust:

Python 可以用于许多应用领域,从 Web 开发,到数据科学和分析,到 AI 和机器学习,再到软件开发。

Python 被广泛用于机器学习,数据科学和 AI,因为它:

在以下情况下,你应该使用 Python:

考虑到 Rust 的迅速普及、受欢迎程度和广泛的使用案例,它几乎不可避免地会在不久的将来超越 Python,以下是一些原因。

Rust 超越 Python 的一个主要原因是性能。因为 Rust 是直接编译成机器代码的,所以在你的代码和计算机之间没有虚拟机或解释器。

与 Python 相比,另一个关键优势是 Rust 的线程和内存管理。虽然 Rust 不像 Python 那样有垃圾回收机制,但 Rust 中的编译器会强制检查无效的内存引用泄漏和其他危险或不规则行为。

编译语言通常比解释语言要快。但是,使 Rust 处于不同水平的是,它几乎与 C 和 C ++一样快,而且没有额外开销。

让我们看一个用 Python 编写的 O(log n) 程序的示例,并使用迭代方法计算完成任务所需的时间:

输出:

现在,让我们来看一下使用迭代方法用 Rust 编写的定时 O(log n) 程序:

输出

在没有使用任何优化技术的情况下,Rust 和 Python 在同一台机器上执行类似的操作分别需要 4.6 微秒和 8.6 微秒。这意味着 Python 花费的时间几乎是 Rust 的两倍。

Python 和大多数现代编程语言一样,被设计成内存安全的。然而,即使没有垃圾回收。Rust 在内存安全方面却让 Python 望尘莫及。

Rust 采用了一种独特的方式来确保内存安全,其中涉及所有权系统和借用检查器(borrow checker)。Rust 的借用检查器确保引用和指针不会超过它们所指向的数据。

Python 和其他语言一样,提供了错误检查和日志机制。但是在让开发者知道哪里出了什么问题的时候,Rust 和 Python 之间有一些差异。

举一个 Python 变量错误的典型例子:

Python 输出

Rust 中的类似示例:

Rust 输出

在这里,Rust 推荐了可能的变量,这些变量可能是你想输入的。Python 只会抛出错误,而不会给出如何修复的建议。

再举个例子:

此代码引发错误,因为默认情况下 Rust 中的变量是不可变的。除非它具有关键字 mut ,否则无法更改。

错误:

修正错误:

如你所见,现在它不会引发任何错误。除此之外,Rust 不允许不同的数据类型相互操作,除非将它们转换为相同的类型。

因此,维护 Rust 代码库通常很容易。除非指定,否则 Rust 不允许更改。Python 是允许这种性质的更改的。

与大多数编译语言相比,Rust 因其速度快、内存安全有保证、超强的可靠性、一致性和用户友好性而备受青睐。在编程中,我们已经到了速度开始变得毫不费力的地步。

随着技术的发展,它变得越来越快,试图在更短的时间内做更多的事情,而不需要那么多的权衡。Rust 帮助实现了这一点,同时又不妨碍开发者的工作。当技术试图推动可以实现的边界时,它也会考虑系统的安全性和可靠性,这是 Rust 背后的主要思想。

除了速度外,Python 在并行计算方面也有局限性。

Python 使用全局解释器锁(GIL),它鼓励只有一个线程同时执行,以提高单线程的性能。这是一大局限,因为它意味着你不能使用多个 CPU 核进行密集计算。

如前所述,Stack Overflow 的“ 2020 开发人员调查”中有 86%的受访者将 Rust 称为 2020 年最喜欢的编程语言。

同样,“ 2020 HackerRank 开发人员技能报告”的受访者将 Rust 列为他们计划下一步学习的十大编程语言:

相比之下,2019 年的调查将 Rust 排在列表的底部,这表明 Rust 开发人员社区正在迅速增长。

这些数据表明,Rust 正在成为主流开发者社区的一部分。许多大公司都在使用 Rust,一些开发者甚至用它来构建其他编程语言使用的库。着名的 Rust 用户包括 Mozilla、Dropbox、Atlassian、npm 和 Cloudflare 等等。

Amazon Web Service 还对 Lambda,EC2 和 S3 中的性能敏感组件采用了 Rust。在 2019 年,AWS 宣布赞助 Rust 项目,此后为 Rust 提供了 AWS 开发工具包。

公司正越来越多地用更高效的编程语言(如 Rust)取代速度较慢的编程语言。没有其他语言能像 Rust 一样在简单和速度之间做出平衡。

Rust 已经发展成为一门易于使用的编程语言,因此它的使用率有所提高。尽管 Python 在机器学习/数据科学社区中占有坚实的地位,但 Rust 在未来很可能被用作 Python 库更有效的后端。

Rust 具有取代 Python 的巨大潜力。目前的趋势是,在应用程序、性能和速度方面,Rust 不仅仅是一种编程语言,它还是一种思维方式。

各位看官你们觉得呢?评论区留下你的看法!

⑶ 2020-09-26:请问rust中的&和c++中的&有哪些区别

RUST中的&表示引用

  • Rust 有编译时变量引用检查

  • &引用的变量默认情况下不可以直接修改(可以使用unsafe{})

  • &mut 引用的变量可以在生命周期内部仅能同时存在至多一个,可以当C/CPP中指针用

  • &/&mut引用的变量会在作用域结束后释放

  • 如果必须要有多个引用或多个变量实例,可以使用clone()方法

  • 以上特性均为编译时特性,不影响运行时性能

C/CPP中 &表示变量的内存地址,是偏向底层的,C/CPP没有编译时变量检查,所以比较自由

⑷ 大家如何评价Rust语言

我用rust正在写一个区块链项目。

如果不熟悉它的机制,很可能会写得非常啰嗦。
举个例子Mutex<RefCell<Rc>>> 这种类型多了会让人崩溃。

c++很多东西被简化了, 比如拷贝构造函数变成了Copy trait,移动构造函数自带。 RAII被rust强推(连lock都是).. 想要用内存不安全的操作需要加unsafe。c++那几个智能指针变成rust的基本类型了。所有的变量都会有一个所有权,不用智能指针的话,只能用引用(rust叫借用),增加了很多限制。指针什么的很难看到了(写起来啰嗦)

恶心的生命周期标注,没有ide很容易被这个烦死。

没了容器类, 这个习惯c++的要吐槽。

加了很多函数编程的概念。 比如: arr.to_iter().filter(|x| x.age > 20), 还有模式匹配,高阶枚举,但总体没有scala ocaml这类强大。

完全编译时, 极少运行时(有类似c++的typeid),要想用类似java的反射机制就不要想了。 泛型和c++一样, 基本就是一个文本替换(宏)

常用的功能, 如多线程,日志,文件,网络等都比c++ std和boost好用很多, 但是功能也没有那么强大, 不少功能和c一样直接在系统内核上封装了一下,写起来跟c有点像。

完全抛弃面向对象,和go很像,全是struct。这点真心比c++半吊子面向对象强。

比c++方便最多的地方是有一个模块管理系统,项目的结构都是订死的(和sbt有点像),灵活性不强。

目前社区不完善,基本上找不到什么有用的论坛。debug比较痛苦。

总体觉得是c++的阉割版,写起来很难像c++一样放得开。小项目会快那么一点,毕竟不用写makefile。

⑸ Rust 和 C++ 有哪些优劣

Rust 野心勃勃的想要取代 C++,别人问你们对 Go 怎么看的时候他们就直接回答我们的目标是 C++,Go 也是个很好的语言balabala。
GitHub Wiki 页面上有一个简单的比较 Rust for CXX programmers · rust-lang/rust Wiki · GitHub

最直观的区别就是 Rust 没有 C++ 的历史包袱和 C 包袱,所以一切都能更 clear。还有现代的模块系统。但如果仅仅如此就仅仅是一个 Better C++。

但是 Rust 有更精细的编译时检查,把 C++ 的 RAII 模式进行严格的编译时检查,做到了编译时的隐式确定性析构。同时区分了 mut 和非 mut,保护数据的不变性的同时能更适合并发。将类型安全执行到可以达到的极致。(匿名用户的答案非常棒!)

并且,虽然 C++ 也在不断吸取函数式特性,但是 Rust 做得更彻底,模式匹配和代数数据类型结合起来的威力谁用谁知道。错误处理就用的这种方法,没有异常,也不像 Go 有额外的返回值。

对泛型的支持很好,而且泛型出错了编译器的错误信息也很友好,不像 C++ 的模板编程……实际上所有的错误信息都很友好,用人话给你说清楚了你遇到了什么问题,有的时候还能帮你提供修改建议。

同时有模式匹配样式的宏,在代码生成的同时保证安全。这个我是听 @权循真 (upsuper) 说的……他说声明被一堆宏穿插过来穿插过去,他去 IRC 抱怨别人回复说你去 Servo 组用 Rust 吧,没有这个问题……

尽管生命期的概念有点费解,但是语言本身的元素并没有那么多,不需要学习太多的概念。C++ 中构造函数都有各种细节各种坑我简直难以想象。只有类似接口的东西,没有继承可能对一些人来说很不习惯——但是你真的需要继承吗?

Rust 的参与者很多都是资深的 C++ 程序员,是一个很对 C++ 程序员胃口的语言。而且上手也很简单,常用指针基本都有对应版本。名字空间的 :: 操作符更是熟悉。

⑹ 对比 Go 语言,Rust 有什么优势和劣势

我并没有什么编程的经验,觉得编程实在是太复杂了,不喜欢去研究太多,对这个也不怎么懂,只能说自己是个半吊子,就是所掌握的知识,也是东拼西凑的,朋友和我说点儿,自己去书上看一点儿,只能说根据自己的体验给出一些体会吧。


其实我觉得什么代码啊编程啊这些东西还是比较适合理工的学生去研究,我一看脑袋就大,完全不明白在讲什么。我大概了解的就是这些,语言的话大家可以多方面的去了解,也不是说有缺点就是不好,看配置看个人吧,每个人习惯不一样,也许有的人用不稳定的还觉得挺好呢,有的人就喜欢比较完美的,在我看来编程这个东西真的是很复杂,会有很多的代码,这些代码弄得我自己头都大了,有的时候还得去恶补一下。

⑺ 对比 Go 语言,Rust 有什么优势和劣势

Go语言是谷歌2009发布的第二款开源编程语言。Go语言专门针对多处理器系统应用程序的编程进行了优化,使用Go编译的程序可以媲美C或C++代码的速度,而且更加安全、支持并行进程。

Rust是Mozilla开发的注重安全、性能和并发性的编程语言。"Rust",由web语言的领军人物Brendan Eich(js之父),Dave Herman以及Mozilla公司的Graydon Hoare 合力开发。Rust是针对多核体系提出的语言,并且吸收一些其他动态语言的重要特性,比如不需要管理内存,比如不会出现Null指针等等。


不管是GO语言还是ruts都是各有各的长处,各有各的缺点的,每个都有自己存在的意义和用处,可以互不打扰的,选择适合自己的语言去使用,让他发挥到自己的用处才是他所存在的意义,也不能太过于可以的去比较他们之间互相的好与坏。

⑻ [from js to rust 系列][宏-01][官网文档 19.5]高级特性:宏[译文]

原文链接:The Rust Programming Language

作者:rust 团队

译文首发链接:zhuanlan.hu.com/p/516660154

着作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

中间加了一些对于 JavaScript 开发者有帮助的注解。在学习 Rust 的代码时,尤其是有一些经验的开发者,一般都会去看一些相对简单的库,或者项目去学习。Rust 的原生语法本身并不复杂,有一定的 TypeScript 经验的开发者,应该通过看一些教程,都能开始熟悉基本的语法。反而是宏相关内容,虽然就像本文写的,大部分开发者不需要自己去开发宏,但是大家使用宏,和看懂别人代码的主体逻辑,几乎是绕不开宏的。虽然宏在 rust 里属于“高级”内容,但是因为其和 rust 本身语法的正交性,Hugo 认为,反而应该早一些学习。并且,如果一个 JavaScript 开发者之前接触过代码生成器、babel,对这一章的内容反而会比较亲切。

Derive 宏、attribute 宏特别像 rust 里的装饰器。从作用上,和一般库提供的接口来看,也特别像。所以如果之前有装饰器的经验的开发者,对这一章节应该也会比较亲切。

我们一个个来讨论这些内容,但是首先,我们看既然我们已经有了函数,我们为什么需要这些特性。

基本上,宏是指一些代码可以生成另一些代码,这一块的技术一般称为元编程(Hugo 注:代码生成器也属于这一类技术)。在附录 C,我们讨论了 derive 属性,可以帮助你生成一系列的 trait。整本书我们也在用 println! 和 vec! 宏。这些宏在编译时,都会展开成为代码,这样你就不需要手写这些代码。

元编程可以帮助你减少手写和维护的代码量,当然,函数也能帮助你实现类似的功能。但是,宏有函数没有的威力。

宏不好的地方在于,宏很复杂,因为你要用 rust 代码写 rust 代码(Hugo 注:任何元编程都不是简单的事儿,包括 JS 里的。)。因为这种间接性,宏的代码要更难读、难理解、难维护。(Hugo 注:个人学 rust,感觉最难不是生命周期,因为生命周期的问题,可以通过用一些库绕过去,或者无脑 clone,如果是应用程序,则可以通过使用 orm 和数据库来绕过很多生命周期的问题。反而是宏,因为稍微有点规模的代码的,都有一大堆宏。宏最难的不是语法,而是作者的意图,因为本质是他造了一套 DSL)

另一个和函数不一样的地方是,宏需要先定义或者引入作用域,而函数可以在任何地方定义和使用。

我们可以通过 vec! 宏来创建任意类型的 vector,例如 2 个 integer,或者五个 string slice.

一个简化的 vec! 宏:

#[macro_export] 标注指明了这个宏在 crate 的作用域里可用。没有这个标注,宏不会被带入到作用域里。

macro_rules! 后面就是宏的名字。这里只有一种模式匹配的边(arm):( (( (x:expr ),* ) ,=> 后面是这个模式对应要生成的代码。如果这个模式匹配成功,对应的代码和输入的参数组成的代码就会生成在最终的代码中。因为这里只有一种边,所以只有这一种可以匹配的条件。不符合这个条件的输入,都会报错。一般复杂的宏,都会有多个边。

这里匹配的规则和 match 是不一样的,因为这里的语法匹配的是 rust 的语法,而不是 rust 的类型,或者值。更全的宏匹配语法,见文档。

对于 宏的输入条件 ( (( (x:expr ),* ),()内部是匹配的语法,expr表示所有Rust的表达式。() 内部是匹配的语法,expr 表示所有 Rust 的表达式。()内部是匹配的语法,expr表示所有Rust的表达式。() 后面的都喊表示这个变量后面有可能有逗号,* 表示前面的模式会出现一次或者多次。(Hugo 注:像不像正则?宏语法其实挺简单的,不要被高级唬住了。当然,宏还是难的,宏要考虑的问题本身是一个复杂的问题。)

当我们调用:vec![1, 2, 3]; 时,$x 模式会匹配 3 个表达式 1 , 2 和 3。

现在我们看一下和这个边匹配的生成代码的部分:

在 ()里的tempvec.push(() 里的 temp_vec.push(()里的tempvec.push(x); 就是生成的代码的部分。* 号仍然表示生成零个和多个,这个匹配的具体个数,要看匹配条件命中的个数。

你传任意参数,最后就生成符合上面条件的代码。

虽然过程宏有三种:custom derive、attribute-like 和 function-like,但是原理都是一样的。

如果要创建过程宏,定义的部分需要在自己的 crate 里,并且要定义特殊的 crate 类型。(Hugo 注:相当于定义了一个 babel 插件,只不过有一套 rust 自己的体系。这些宏会在编译的时候,按照书写的规则,转成对应的代码。所有的宏,都是代码生成的手段,输入是代码,输入是代码。)这种设计,我们有可能会在未来消除。

下面是一个过程宏的例子:

过程宏接收一个 TokenStream,输出一个 TokenStream。TokenStream 类型定义在 proc_macro 里,表示一系列的 tokens。这个就是这种宏的核心机制,输入的代码(会被 rust) 转成 TokenStream,然后做一些按照业务逻辑的操作,最后生成 TokenStream。这个函数也可以叠加其他的属性宏(#[some_attribute], 看起来像装饰器的逻辑,也可以理解为一种链式调用),可以在一个 crate 里定义多个过程。(Hugo 注:搞过 babel 的同学肯定很熟悉,一样的味道。没搞过的同学,强烈建议先学学 babel。)

下面我们来看看不同类型的过程宏。首先从自定义 derive 宏开始,然后我们介绍这种宏和其他几种的区别。

我们创建一个 crate 名字叫 hello_macro,定义一个 HelloMacro 的 trait,关联的函数名字叫 hello_macro。通过使用这个宏,用户的结构可以直接获得默认定义的 hello_macro 函数,而不需要实现这个 trait。默认的 hello_macro 可以打印 Hello, Macro! My name is TypeName!,其中 TypeName 是实现这个 derive 宏的结构的类型名称。

创建这个宏的过程如下,首先

然后定义 HelloMacro trait

这样我们就有了一个 trait,和这个triat 的函数。用户可以通过这个 trait 直接实现对应的函数。

但是,用户需要每次都实现一遍 hello_macro。如果 hello_macro 的实现都差不多,就可以通过 derive 宏来是实现。

因为 Rust 没有反射机制,我们不可以在执行时知道对应类型的名字。我们需要在编译时生成对应的代码。

下一步,定义过程宏。在这个文章编写时,过程宏需要在自己的 crates 里。最终,这个设计可能改变。关于 宏 crate 的约定是:对于一个名为 foo 的 crate,自定义 drive 宏的crate 名字为 foo_derive。我们在 hello_macro 项目中创建 hello_macro_derive crate。

我们的两个的 crate 关联紧密,所以我们在 hello_macro crate 里创建这个 crate。如果我们要改变 hello_macro 的定义,我们同样也要更改 hello_macro_derive 的定义。这两个 crates 要隔离发布。当用户使用时,要同时添加这两个依赖。为了简化依赖,我们可以让 hello_macro 使用 hello_macro_derive 作为依赖,然后导出这个依赖。但是,这样,如果用户不想使用 hello_macro_derive,也会自动添加上这个依赖。

下面开始创建 hello_macro_derive,作为一个过程宏 crate。需要添加依赖 syn 和 quote。下面是这个 crate 的 Cargo.toml。

在 lib.rs 里添加下述代码。注意,这个代码如果不增加 impl_hello_macro 的实现是通不过编译的。

注意,这里把代码分散成两部分,一部分在 hello_macro_derive 函数里,这个函数主要负责处理 TokenStream,另一部分在 impl_hello_macro,这里负责转换语法树:这样编写过程宏可以简单一些。在绝大部分过程宏立,对于前者的过程一般都是一样的。一般来说,真正的区别在 impl_hello_macro,这里的逻辑一般是一个过程宏的业务决定的。

我们引入了三个 crates: proc_macro, syn 和 quote。proc_macro 内置在 rust 立,不需要在 Cargo.toml 中引入。proc_macro 实际是 rust 编译器的一个接口,用来读取和操作 Rust 代码。

syn crate 把 Rust 代码从字符串转换为可以操作的结构体。quote crate 把 syn 数据在转回 Rust 代码。这些 Crate 可以极大简化过程宏的编写:写一个 Rust 代码的 full parser 可不是容易的事儿!

当在一个类型上标注 [derive(HelloMacro)] 时,会调用 hello_macro_derive 函数。之所以会有这样的行为,是因为在定义 hello_macro_derive 时,标注了 #[proc_macro_derive(HelloMacro)] 在函数前面。

hello_macro_derive 会把输入从 TokenStream 转换为一个我们可以操作的数据结构。这就是为什么需要引入 syn 。sync 的 parse 函数会把 TokenStream 转换为 DeriveInput。

上述这个结构的意思是:正在处理的是 ident(identifier, 意味着名字)为 Pancakes 的 unit struct。其他的字段表示其余的 Rust 代码。如果想了解更详细的内容,请参考。

接下来,我们就要开始定义 impl_hello_macro。这个函数实现了添加到 Rust 代码上的函数。在我们做之前,注意 derive macro 的输出也是 TokenStream。返回的 TokenStream 就是添加完代码以后的代码。当编译 crate 时,最终的代码,就是处理完成的代码了。

你也许也会发现,这里调用 syn::parse 时使用了 unwrap,如果报错就中断。这里必须这么做,因为最终返回的是 TokenStream,而不是 Result。这里是为了简化代码说明这个问题。在生产代码,你应该处理好报错,提供更详细的报错信息,例如使用 panic! 或者 expect。

下面是代码:

通过上面的代码,cargo build 就可以正常工作了。如果要使用这个代码,需要把两个依赖都加上。

现在执行下面的代码,就可以看到 Hello, Macro! My name is Pancakes!

下一步,我们来 探索 其他类型的过程宏。

属性宏和 derive 宏类似,但是可以创造除了 derive 意外的属性。derive 只能作用于 structs 和 enums,属性宏可以作用于其他的东西,比如函数。下面是一个属性宏的例子:例如你制作了一个名为 route 的属性宏来在一个web 框架中标注函数。

#[route] 是框架定义的过程宏。定义这个宏的函数类似:

这里,有两个参数,类型都是 TokenStream。第一个是属性的内容,GET, "/" 部分,第二个是标注属性宏传入的语法部分,在这个例子里,就是剩下的 fn index() {}。

工作原理和 derive 宏是一样的。

函数宏的使用比较像调用一个 rust 函数。函数宏有点像 macro_rules! ,能提供比函数更高的灵活性。例如,可以接受未知个数的参数。但是,macro_rules! 只能使用在上述章节的匹配型的语法。而函数宏接受 TokenStream 参数作为入参,和其他过程宏一样,可以做任何变换,然后返回 TokenStream。下面是一个函数宏 sql

这个宏接受 SQL 语句,可以检查这个 SQL 的语法是否正确,这种功能比 macro_rules! 提供的要复杂的多。这个 sql! 的宏可以定义为:

这个定义和自定义 derive 宏类似:接受括号内的 tokens,返回生成的代码。

好了,现在你有了一些可能不常用的 Rust 新工具,但是你要知道的是,在需要的场合,他们的运行原理是什么。我们介绍了一些复杂的话题,当你在错误处理或者别人的代码里看到这些宏时,可以认出这些概念和语法。可以使用这一章的内容作为解决这些问题的索引。

⑼ rust性能到底有多好

这个和c++大同小异。 因为把大量的运行时放到了编译时。 只不过编译器优化不够还达不到c++性能。
其实rust和c++的关系很想scala和java的关系。 rust和c++很多概念甚至库都是通用的,抽象化方式,比如泛性,多态,可变性,拷贝构造,移动构造,都是一样的。 你要是写过c++再来写rust,就会吐槽原来这个功能也有啊...

运行速度上rust c++ java都是大同小异,io处理上 java甚至比rust还要快。内存消耗rust和c++是一个级别的,都非常低。 另外很多人写rust喜欢用arc cellref这些只能指针, 其实是给这些变量增加了动态性会导致额外开销,所以这些特性用多了后,rust性能也不会太高:

⑽ Rust编程语言的自动特性

特性对象中有一部分特性是自动特性,rust里的自动特性有Send Sync Unpin UnwindSafe RefUnwindSafe。

自动特性的自动就是说当代码在需要任何这五个特性的时候如果没有写上,编译器会根据一些规格给加上。其实也就是一些推导的过程,并不复杂。

当引用一个类型,引用可变类型,解构常量类型,解构可变类型,数组类型重复表达式中的类型,切片类型等这些类型实现了任意以上五个特性,这些类型就会自动具有这些特性。

函数项类型包括函数名称,类型参数,早期生命时间参数等能够标识这个函数的所有部分,以及泛型参数。

结构,枚举,联合,元祖类型只有所有的字段都实现了自动特性它们就具有自动特性。

闭包只有所有值和共享引用的类型实现了自动特性它就具有自动特性。

对于泛型如果类型参数实现Sync特性,编译器会为这个引用类型实现Send,如果类型是Send则编译器不会去实现Send。

自动特性可以有负实现,就是实现的时候在特性前面加上!号。意思就是类型实现了自动特性引用或者解构的可变及不可变反而是去除了这个自动特性。

热点内容
c编译时多态 发布:2025-07-17 05:56:00 浏览:843
软件服务器超时是什么意思 发布:2025-07-17 05:55:59 浏览:349
c语言期末试卷 发布:2025-07-17 05:49:58 浏览:404
64位access数据库 发布:2025-07-17 05:35:58 浏览:374
php文件的相对路径 发布:2025-07-17 05:34:22 浏览:711
矢量的叉乘运算法则 发布:2025-07-17 05:29:41 浏览:661
dell云存储服务器 发布:2025-07-17 05:21:06 浏览:255
铣床怎么编程 发布:2025-07-17 05:20:29 浏览:776
sql11oracle 发布:2025-07-17 05:15:39 浏览:744
全国各地移动dns服务器ip地址 发布:2025-07-17 05:07:47 浏览:312