Rust中文翻译10

我们的第三个工程,会秀一下Rust语言的其中一个最棒的优点:没有实际上的运行时环境.

随着组织的增加,他们依赖于等多的编程语言.不同的编程语言有各自的有点和缺点,一个全语言栈的可以使你使用一种语言的优点的同时,用另一种语言来代替它的缺点.

许多编程语言的一个通病就是运行时环境下的性能很差.通常来说,使用一种慢速的语言,代价换来的是生产力的提高.为了缓和这个问题,有一种方法是使用C语言来编写你的系统,然后调用这些C语言代码就像它是由高层次语言写的.这被称为"外部函数接口(foreign function interface)",缩写为FFI.

Rust在两个方向上都支持FFI:它可以方便的调用C语言,并且更为关键的是,他可以像C语言一样被方便的调用.Rust语言没有垃圾回收机制,同时运行时消耗又很小,这使得Rust成为了被嵌入其他语言中的完美候选人.

本章专门讲述FFI,它的一些特性全书中也都有描述,但是在这里,我们使用3个例子来说明FFI特性:Ruby,Python和JavaScript.

3.3.1 问题

我们本来有很多可以选择的例子,但是我们选择了一个Rust语言相较于其他语言的一个明显的优势领域:数值计算和线程.

Page 56

许多编程语言为了一致性的考虑,将数值存放在堆上,而不是栈上.尤其是那些面向对象的语言并且使用垃圾回收的语言,堆是他们的默认选择.有时候会有优化,将特定的数值放在栈上,但是这是基于一个优化器来完成的,那就是我们需要保证我们使用的是基本数值类型而不是对象类型.

第二,许多语言都有一个"全局解释器锁(global interpreter lock)",这会限制多线程的运行.好的方面看这样更安全,但是坏的方面就是本来可以多线程的任务无法多线程执行.

为了强调这两个问题,我们来创建一个重度使用这两个问题的小程序.因为我们关注的焦点是将Rust语言嵌入其他语言中,而不是问题本身,我们使用一个玩具例子:

启动10个线程,其中,每个线程从1数到500万.当是个线程结束的时候打印"done!".

我在我的电脑上选择500万.下面是Ruby的例子:

threads = []

10.times do

threads << Thread.new do

count = 0

5_000_000.times do

count += 1

end

end

end

threads.each {|t| t.join }

puts "done!"

试着运行这个例子,然后选择一个数字可以让你的机器跑上几秒钟.这取决于你的硬件,你可以增加这个数字.

在我的机器上,运行这个程序耗时2.156秒.如果我使用了某些进程监控工具,例如top,我可以发现它仅使用了一个cpu核心.这就是GIL在起作用.

这虽然是一个虚拟的程序,但是在真实世界中你可以想象出一个和他类似的问题.我们的目的是,使用切换繁忙线程来模拟并发.

Page 57

3.3.2 一个Rust库

让我们用Rust重写这个问题.首先用Cargo创建一个新的工程:

$ cargo new embed

$ cd embed

这个问题用Rust写很简单:

use std::thread;

#[no_mangle]

pub extern fn process() {

let handles: Vec<_> = (0..10).map(|_| {

thread::spawn(|| {

let mut _x = 0;

for _ in (0..5_000_001) {

_x += 1;

}

})

}).collect();

for h in handles {

h.join().ok().expect("Could not join a thread!");

}

}

部分代码看起来有些眼熟.我们循环切换了10个线程,将它们收集到一个handles的向量中.在每个线程里面,我们循环500万个次,每次给_x变量加1.为什么是下划线?如果去掉就会有这个结果:

Page 58

第一个警告是因为我们在构建一个库.如果我们添加了一个测试方法,这个警告就会消失.但是现在没有测试方法.

第二个和x于_x有关.因为我们并没有真正让x做任何事情,我们得到了这个警告.在此例中,这是正常的,我们就是要浪费CPU循环.在x前面加上下划线就会去掉这个警告.

最后,我们join每一个线程.

现在,我们有了一个Rust库,但是没有任何C语言程序调用它.如果我现在把它接入其他程序中它还不能工作.我们仅需做两点小改动就可以了.第一点是代码的开头部分:

#[no_mangle]

pub extern fn process() {

我们需要增加一个新的attribute, no_mangle.当你创建一个Rust库的时候,它会在编译时改变函数的名字.这么做的原因不在本书的讨论范围内,但是为了使其他语言能够调用我们的库,我们不能改变函数的名字.这个attribute就是关闭这个行为的.

另一个改动是pub extern.pub意味着这个函数将被从本模块外部调用,extern是说它会被C语言调用.就这样!并没有改变很多代码.

第二件事是我们需要修改Cargo.toml文件.在底部增加这个:

[lib]

name = "embed"

crate-type = ["dylib"]

这样就会告诉Rust我们想要将我们的库编译成一个标准动态库.默认的,Rust会编译成一个"rlib",一个rust格式的库.

现在我们编译一下:

我们选择用cargo build --release,这样会有编译优化.我们想尽可能的执行越快越好.你可以在target/release目录找到输出文件.

这个embed.dll就是我们的"共享对象"库.我们可以和使用其他C语言写的共享对象一样使用它!

现在我们已经有了Rust库,让我们在Ruby中使用它.

Page 59

3.3.3 Ruby

打开embed.rb文件,输入下述代码:

require ‘ffi‘

require ‘Time‘

start = Time.now

module Hello

extend FFI::Library

ffi_lib ‘target/release/embed.dll‘

attach_function :process, [], :void

end

Hello.process

diff = Time.now - start

puts diff

puts "done!"

在正常运行前我们需要安装ffi gem:

$gem install ffi

最后,运行这个ruby脚本:

$ ruby embed.rb

0.002

done!

WOW,真快!在我的系统上只用了0.002秒.ruby脚本需要运行2秒多.我们来看一下代码:

require ‘ffi‘

我们需要首先引入ffi的gem包.这可以让我们的Rust代码像C语言的库一样被使用.

Page 60

module Hello

extend FFI::Library

ffi_lib ‘target/release/embed.dll‘

ffi的作者强烈建议使用一个module来包含我们从共享对象引入的代码.其中,我们需要使用FFI::Library模块,然后调用ffi_lib来加载我们的共享对象.我们只需传递路径就可以了,这里是target/release/embed.dll

attach_function :process, [], :void

attach_function方法是FFI包提供的.他的功能是将Rust中的process方法和ruby中的同名方法进行关联.因为prcess方法没有参数,所以第二个参数是空数组,又因为它没有返回值,所以最后一个传入的参数是:void.

Hello.process

这就是真正调用Rust代码的地方.通过调用attach_function和以及之前和Rust模块绑定来实现这一点.这看起来像是一个Ruby方法,但实际上调用了Rust方法!

puts "done!"

最后,和我们之前的需求一样,我们打印"done!".

就是这么简单!两种语言之间的桥梁非常简单的实现了,并且带给我们很多性能提升.

接下来,我们来看看Python!

时间: 2024-10-06 20:21:41

Rust中文翻译10的相关文章

Rust中文翻译29

5.10 生命期 本节是Rust三处描述所有权系统的其中之一.所有权是Rust最独特和引人注目的特性,这也是Rust程序员必须熟悉的一个特性.所有权使Rust得以实现它最大的设计目标,内存安全.这里有一些不同的概念,每一个都有自己的章节: 所有权,你正在读的 借用(borrowing, 5.9), 以及它的关联特性'引用' 生命期(5.10),以及borrowing的高级特性 这三者是相关的,也是循序渐进的.你必须要完全理解这个三个部分. 5.10.1 元 在我们讨论细节之前,有两个关于所有权系

Rust中文翻译28

5.9 引用和借用 本节是Rust三处描述所有权系统的其中之一.所有权是Rust最独特和引人注目的特性,这也是Rust程序员必须熟悉的一个特性.所有权使Rust得以实现它最大的设计目标,内存安全.这里有一些不同的概念,每一个都有自己的章节: 所有权,你正在读的 借用(borrowing, 5.9), 以及它的关联特性'引用' 生命期(5.10),以及borrowing的高级特性 这三者是相关的,也是循序渐进的.你必须要完全理解这个三个部分. 5.9.1 元 在我们讨论细节之前,有两个关于所有权系

Rust中文翻译20

Page 100 并发和并行在计算机科学中是非常重要的主题.在工业领域也很火.计算机如今有越来越多的核心,然而很多程序员还没有准备好使用它们. Rust安全的内存特性同样适用于并发存储.并发的Rust代码也是内存安全的,没有数据竞争.Rust的类型系统会保证这一点,给你提供了有利的帮助在编译时实现并发代码. 在我们开始讨论Rust的并发之前,我们需要理解一个很重要的事情:Rust是一个低级别语言,它的所有功能都是通过库来提供的,而不是语言自身.也就是说你不喜欢Rust处理并发的方式,你完全可以实

Rust中文翻译31

5.12 结构体 结构体是一个表示复杂数据类型的方式.例如,你可以计算二维坐标,我们需要x和y的值: let origin_x = 0; let origin_y = 0; 一个结构体就可以让你将两个值合并成一个独立的值: struct Point { x: i32, y: i32, } fn main() { let origin = Point {x: 0, y: 0}; // origin: Point println!("The origin is at ({}, {})",

Rust中文翻译30

5.11 可变性 可变性,就是改变某些值的能力,Rust语言和其他语言有很大的区别.第一点就是Rust默认是不可变的: let x = 5; x = 6; // error! 我们可以引入mut关键字来增加可变性: let mut x = 5; x = 6; // no problem! 这是一个可变绑定.当一个绑定时可变的时候,你可以改变绑定指向的值.上例中,x的值没有太大变化,但是绑定从一个i32转移到了另一个i32上. 如果你需要改变绑定的值,你需要一个一个可变引用: let mut x

Rust中文翻译19

我们来讨论一下迭代器. 还记得Rust的for循环么?有一个例子: for x in 0..10 { println!("{}", x); } 现在你更了解Rust了,我们可以讨论它的工作细节了.区间(Ranges)(0..10)就是迭代器.一个迭代器可以重复的调用.next()方法,然后给我们返回一个序列. 像这样: let mut range = 0..10; loop { match range.next() { Some(x) => { println!("{}

Rust中文翻译27

5.8 所有权 本节是Rust三处描述所有权系统的其中之一.所有权是Rust最独特和引人注目的特性,这也是Rust程序员必须熟悉的一个特性.所有权使Rust得以实现它最大的设计目标,内存安全.这里有一些不同的概念,每一个都有自己的章节: 所有权,你正在读的 借用(borrowing, 5.9), 以及它的关联特性'引用' 生命期(5.10),以及borrowing的高级特性 这三者是相关的,也是循序渐进的.你必须要完全理解这个三个部分. 5.8.1 元 在我们讨论细节之前,有两个关于所有权系统的

Rust中文翻译26

5.5 if Rust 对if的操作并不复杂,但是你会发现它更像一个动态类型语言,而不是一个传统的系统语言.让我们看看你是否知道这其中的微妙之处. if是一个非常常见的概念的一个特定表述,"分支".这个名字来自于树的分支:一个决策点,多条路径可供选择. 在if的分支,是两条路的一种选择: let x = 5; if x == 5 { println!("x is five!"); } 如果我们改变了x的值,这行字就不会被打印.更特别的,如果if后面的表达式是true

Rust中文翻译23

4.9 Borrow 和 AsRef Borrow和AsRef特性特性很相似,但是不一样.这里有一个快速的关于两者的回顾. 4.9.1 Borrow Borrow特性是当你写了一个数据结构,然后你想让一个它的所有者或者借用者类型作为同义词对象来使用. 例如,HashMap就有一个get方法使用了Borrow: fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V> where K: Borrow<Q>,