在 Rust 中创建 C/C++ API

图片来自pexels.com

Rust 是一种神奇的语言,有着更好的生态系统。许多 Rust 的设计决策都非常适合向现有的C/C++系统添加新功能,或者逐步替换这些系统的部分!

当我尝试为 Rust 创建 C++ API 时,我发现,从 C/C++ 到 Rust 的绑定被更好地记录下来,并且比从 Rust 到 C/C++ 的结合有更平滑的体验。

正如我所发现的,事实并非如此!有一些很棒的工具可以帮助你创建C/C++ API。 这篇文章介绍了我在使用这些工具方面的一点经验,希望能帮助有同样追求的人 🙂

继续阅读

在 Rust 中调用 Java 代码

图片来自pexels.com

j4rs 代表 “Java for Rust” ,可以在 Rust 中轻松调用 Java 代码。


不久前,我需要在 Rust 中调用 Java 代码,就启动了 j4rs 这个项目。 其主要思想是实现一个 crate,让用户能够轻松调用 Java,这样他们就可以从庞大的 Java 生态系统中受益。

我说的“容易”,是指:

  • 注意 JNI 所以需要的配置(例如 jvm 包含/链接本地共享库)。
  • 创建一个直观、简单的 API 进行 Java 调用(Rust -> Java 方向)。
  • 允许 Java -> Rust 回调。
  • 无缝在在 Linux 或者 Windows 上使用 crate(当然,前提是安装了 Java)。
  • 遵循 “Rust-first” 方式: Rust 代码可以创建和管理 JVM,而不是反过来。

在此过程中,我还发现一些 other crates 可以调用 Java 代码,但是似乎可以尽量减少程序员对 “JNI 细节” 的干预。

我们中的许多人,Rust 程序员们, 都知道如何编写 Java,但是可能没有多少人知道(或者愿意去处理)JNI的特性和陷阱。

和往常一样,安慰伴随着一些牺牲。隐藏 JNI 细节意味着使用反射和对象序列化。这带来了一些性能损失。

j4rs 是为我们这些愿意付出这个代价的人准备的。

继续阅读

Rust 标准库学习:LinkedList

图片来自pexels.com

继续 Rust 标准库 学习系列, 这次是 LinkedList<T>。注意,实现来自 Rust stable v1.33.0.

拥有节点的双向列表。
LinkedList 永许在常量时间内从两端 pushpop 元素。
大多数情况最好用 VecVecDeque 替代 LinkedList。通常,基于数组的容器更快,内存效率更高,并且更好的利用 CPU 缓存。

Rust std doc

Vec<T> 不同的是

  1. 通过索引访问一个元素需要对列表进行线性迭代。
  2. append is O(1)。
  3. 有趣的是 linked_list::Iterlinked_list::IterMut 的不同之处。IterMut的不可变性用 &mutPhantomData<&'a Node<T>> 确保健全性 (更多关于 PhantomData,稍后说明)。

这儿有 一本书 (如果你没读过这本书,我强烈建议你去阅读它的细节) 向读者解释了为什么单链表难以实现,而且对于 Rust 新用户来说不是个好主意。

由于 Rust 的类型系统/所有权,实际上很难实现双向列表。 主要原因是一个节点似乎需要来自相邻节点的两个所有者。然而, 不过,这对于 NonNull<T> 是可能的,我们在 Vec<T> 学习 中讨论过。

继续阅读

Rust 标准库学习:VecDeque

继续 Rust 标准库学习系列, 这次是 VecDeque<T>。 由于它与 Vec相似,没有太多说的。

双端队列使用可扩张的 环形缓冲器 实现。这种类型的默认使用方法是,用 push_back 将元素添加到队列, 用 pop_front 从队列中移除元素。 extendappend 和以这种方式将元素追加到后面,并在VecDeque 上迭代。

Rust std doc
  • Vec 类似, VecDeque 对容器的两端进行 amortized O(1) 插入, 与Vec 不同的是, 它对容器的两端进行 amortized O(1) 移除。 (从 Vec 学习中回顾,删除严格为 O(1),不涉及回收因子)
  • Vec 类似,索引是 O(1)

Vec<T> study 类似,这里是精简的 VecDeque<T> 定义:

继续阅读

Rust 标准库学习:Vec

图片来自pexels.com

接下来的系列博客文章将包含我对Rust 标准库的研究。我已经在分散的地方编写了本系列的部分内容,希望将它们集中在一个地方,以便更好、更容易地访问。每当我发现一些有趣/重要的事情需要记住时,我就会更新。

我指的是Rust stable v1.33.0中的实现。

这篇文章涵盖了我认为重要的std::vec的一些细节。

Vec<T>

Vec<T> 是一个动态数组,它只会自动增长而不会自动收缩。

具有堆分配内容的连续可增长数组类型。

Rust std doc

注意其对应的堆栈分配的固定大小数组[T; N]之间的差异 (此时N需要是一个指定的非负整数。常量泛型有望很快出现)。

好的!让我们开始吧。Vec<T> 包含(指针容量长度)。

继续阅读

Rust 机器学习的现状

图片来自pexels.com

每隔一段时间,这个话题就出现在社交媒体或Rust用户频道上。 我想简要介绍一下我所看到的事情的历史,以及有关机器学习/深度学习框架的现有变化以及最近的主要趋势的一些信息。

Brief history and where are we now?

现有的 ML/DL 生态系统非常庞大,因为它们是高性能计算、数学优化、系统和编译器工程等的组合。 因此,为了简单起见,如果我们将ML划分为传统ML和DL(包括重叠),然后我们可以看到 rusty-machine, rustlearnleaf 。他们做了非常有趣和大胆的发展, 特别是当时的leaf 。最终,他们大多放弃了,因为创建一个完整的开源ML/DL框架需要大量的工作:

  • 语言和库的支持 (稍后会介绍)
  • 基本成熟的线性代数和统计学方面的库
  • 一个由ML专家组成的社区,他们碰巧知道Rust并愿意做出贡献

主流的现有ML库(主要使用Python/Cython或c++)都是在这些支持下开发的,Rust也不例外。

继续阅读

Go 实战 – gRPC 3: 流式 RPC

图片来自pexels.com

上一节我介绍了创建服务端和客户端,不过还有三个涉及到流式RPC的函数,SayHelloFromStreamSayHelloIntoStreamSayHelloStream还未实现。这一节,我将讲解这三个函数的实现,这三个函数分别代表“服务端流式 RPC”,“客户端流式 RPC”和“双向流 RPC”。

服务器端流式 RPC

我们可以从hello.pb.go文件中服务端的SayHelloFromStream的函数签名:

SayHelloFromStream(*HelloRequest, Hello_SayHelloFromStreamServer) error

SayHelloFromStream函数有2个参数,第一个参数是客户端发送过来的请求。第二个是一个interface:

type Hello_SayHelloFromStreamServer interface {
	Send(*HelloReply) error
	grpc.ServerStream
}

interface包含了一个Send函数,并且嵌入了grpc.ServerStream这个interface:

继续阅读

Go 实战 – gRPC 2:创建服务端和客户端

图片来自pexels.com

上一节我们安装了 gRPC 和 protocol buffers,编写了一个简单的.proto文件,并生成了客户端和服务端的接口代码。如果你还没有阅读上一节,建议先去阅读。接下来我将介绍如何启动和调用服务。

创建服务端

经过上一节,我们已经有了如下目录结构:

study_grpc
├── go.mod
├── go.sum
├── protoc
    ├── codegen.sh
    ├── hello.pb.go
    └── hello.proto

要注意,study_grpc 目录应当在 $GOPATH 内。我将其放在了 $GOPATH/src/github.com/danclive/目录下。

我们在 study_grpc下新建一目录server,并创建文件server.go

$ mkdir server && cd server
$ touch server.go
继续阅读

Go 实战 – gRPC 1:安装

图片来自pexels.com

RPC(Remote Procedure Call Protocol)–远程过程调用协议,是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC 使得开发包括网络分布式多程序在内的应用程序更加容易。

RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息的到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。

接下来将介绍 gRPC 的使用。

继续阅读

Rust 实战 – 使用套接字联网API 2

图片来自pexels.com

上一节,我们已经实现了一个最小可运行版本。之所以使用Rust而不是C,是因为Rust具备了必要的抽象能力,还能获得跟C差不多的性能。这一节,我们对上一节的代码做必要的封装,顺便还能把unsafe的代码包装成safe的API。

我将上一节的源码放到了这里,你可以去查看。

还记得上一节,我们把使用到的libc中的函数socketbindconnect和结构体sockaddrsockaddr_inin_addr等,在Rust这边定义了出来。实际上,几乎libc中的函数,libc这个crate都帮我们定义好了。你可以去这里查看。编译器和标准库本身也使用了这个crate,我们也使用这个。

首先在Cargo.toml文件的[dependencies]下面加入libc = "0.2":

[dependencies]
libc = "0.2"

接着在main.rs文件上方加入use libc;,也可以use libc as c;。或者你直接简单粗暴use libc::*,并不推荐这样,除非你明确知道你使用的函数来自哪里。并将我们定义的与libc中对用的常量、函数、结构体删除。再添加libc::c::到我们使用那些常量、结构体、函数的地方。如果你是直接use libc::*,除了直接删除那部分代码外,几乎什么都不用做。目前的代码:

继续阅读