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::*,除了直接删除那部分代码外,几乎什么都不用做。目前的代码:

继续阅读

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

图片来自pexels.com

虽然标准库已经封装好了 TcpListenerTcpStream 等基础api,但作为Rust 的爱好者,我们可以去一探究竟。本文假设你已经对 Rust 和 Linux 操作系统有了一定了解。

在 Linux 上 Rust 默认会链接的系统的 libc 以及一些其他的库,这就意味着,你可以直接使用libc中的函数。比如,你可以使用 gethostname 获取你电脑的 “hostname”:

use std::os::raw::c_char;
use std::ffi::CStr;

extern {
    pub fn gethostname(name: *mut c_char, len: usize) -> i32;
}

fn main() {
    let len = 255;
    let mut buf = Vec::<u8>::with_capacity(len);
    let ptr = buf.as_mut_ptr() as *mut c_char;

    unsafe {
        gethostname(ptr, len);
        println!("{:?}", CStr::from_ptr(ptr));
    }
}

解释一下上面的代码。

extren 表示“外部块(External blocks)”,用来申明外部非 Rust 库中的符号。我们需要使用 Rust 以外的函数,比如 libc ,就需要在 extren 中将需要用到的函数定义出来,然后就可以像使用本地函数一样使用外部函数,编译器会负责帮我们转换,是不是很方便呢。但是,调用一个外部函数是unsafe的,编译器不能提供足够的保证,所以要放到unsafe块中。

继续阅读