标签 Rust 下的文章

跟随这个演示,学习如何在 Rust 中使用文件系统模块。

知道如何读写文件对各种用途都很有用。在 Rust 中,这项任务是通过标准库中的文件系统模块(std::fs)完成的。在这篇文章中,我将向你介绍如何使用这个模块。

为了演示这项任务,我准备了一些示例代码,也可以在 GitHub 上找到。

准备工作

在使用 Rust 时,失败的函数会返回 Result 类型。尤其是文件系统模块会返回专门的类型 std::io::Result<T, Error>。有了这些知识,你可以从 main() 函数中返回相同的类型:

fn  main() ->  std::io::Result<()> {
/* ...code comes here... */

Rust 文件写入

在 Rust 中执行文件的 I/O 操作是相对容易的。写入文件可以简化为一行:

use  std::fs;
fs::write("favorite_websites.txt", b"opensource.com")?;
Ok(())

使用错误传播操作符 (?),错误信息被传递到调用函数中,随后可以处理错误。由于 main() 是调用栈中唯一的其他函数,如果写操作失败,错误信息将被传递到控制台输出。

fs::write 函数的语法是非常先进的。第一个参数是文件路径,它必须是 std::path::Path 类型。第二个参数是内容,它实际上是一个字节切片([u8])。Rust 将传递的参数转换为正确的类型。幸运的是,这些类型基本上是下面的例子中所处理的唯一类型。

使用文件描述符类型 std::fs::File 可以实现对写操作更简洁的访问:

let mut file = fs::File::create("favorite_websites.txt")?;
file.write_all(b"opensource.com\n")?;
Ok(())

由于文件类型实现了 Write 特性,所以可以使用相关的方法来写入文件。然而,create 方法可以覆盖一个已经存在的文件。

为了获得对文件描述符的更多控制,必须使用 std::fs::OpenOptions 类型。这提供了类似于其他语言中的打开模式:

let mut file = fs::OpenOptions::new()
                            .append(true)
                            .open("favorite_websites.txt")?;
                            
file.write_all(b"sourceforge.net\n")?;

Rust 文件读取

适用于写的东西也适用于读。读取也可以通过简单的一行代码来完成:

let websites = fs::read_to_string("favorite_websites.txt")?;

以上一行读取文件的内容并返回一个字符串。除了读取字符串,还有 std::fs::read 函数,如果文件包含二进制数据,该函数会将数据读成一个字节向量。

下一个例子显示了如何将文件的内容读入内存,随后逐行打印到控制台:

let file = fs::File::open("favorite_websites.txt")?;
let lines = io::BufReader::new(file).lines();

for line in lines {
    if let Ok(_line) = line {
        println!(">>> {}", _line);
    }
}

总结

如果你已经熟悉了其他编程语言,你可能已经注意到没有 close- 函数(或类似的)来释放文件句柄。在 Rust 中,当相关变量超出作用域,文件句柄就会被释放。为了定义关闭行为,可以在文件表示的周围应用作用域 ({ })。我建议你熟悉 ReadWrite 特性,因为你可以在许多其他类型中找到这个特性的实现。


via: https://opensource.com/article/23/1/read-write-files-rust

作者:Stephan Avenwedde 选题:lkxed 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

数据库损坏导致全美停飞航班

周三,美国联邦航空局(FAA)的空中任务通知系统发生严重故障,一度导致全美停飞航班。FAA 初步判断问题根源是一个数据库文件受损,而没有证据表明这是一次网络攻击。周二晚间 FAA 的计算机系统就出现了问题,于是他们在周三凌晨航班最少的时候重启了系统。但在经过 90 分钟的重启之后,该系统的功能没有完全恢复,而备份系统也同样发现存在问题。FAA 于是下达了全美的停飞令,导致了大规模航班延误和机场瘫痪。

消息来源:CNN
老王点评:你真想不到一些非常重要的系统有多破旧。

因员工监控软件显示摸鱼,被勒令向公司返回工资

一名加拿大女子以远程方式从事会计工作,她最初声称自己去年被无故解雇,并要求获得赔偿。但该公司告诉法庭,该员工有 50 多个小时没有从事工作有关的任务。该公司说,在发现该员工分配的工作超出预算和进度后,该公司在其工作笔记本上安装了名为 TimeCampon 的员工追踪软件。该软件可以跟踪文件打开的时间,员工如何使用该文件,并将时间记录为工作时间。该软件可以将工作时间记录与使用笔记本电脑播放电影和电视节目等活动分开。法官因此判决该女子败诉并向公司返回部分工资。

消息来源:《卫报》
老王点评:真是,都不能愉快的摸鱼了。

谷歌将在 Chrome 开发中使用 Rust

谷歌今天宣布,他们将允许 Rust 代码进入 Chromium 代码库。谷歌正在努力将 Rust 工具链引入他们的 Chromium 构建系统,并将允许在 Chrome/Chromium 中使用 Rust 库。在 Chromium 中使用 Rust 可以避免内存安全错误,这将有助于加快开发速度,提高 Chrome/Chromium 浏览器的整体安全性。

消息来源:Phoronix
老王点评:继安卓之后,谷歌又将 Rust 加入到 Chrome 开发中,这将对改善浏览器安全有很大的意义。

数千用 Rust 开发的项目面临拒绝服务攻击

Rust Hyper 包是一个非常流行的处理 HTTP 请求的 Rust 库。安全研究人员发现,大量包含 Hyper 的项目容易受到精心设计的 HTTP 请求引起的拒绝服务攻击,漏洞源于在使用 Hyper 库时忘记对 HTTP 请求设置适当的限制。目前,Rust 的包存储库 crates.io 中列出的 2,579 个项目依赖于 Hyper,下载量已超过 6700 万次,但大量项目尚未回应修复的消息。

消息来源:The Register
老王点评:并不是使用了 Rust 就高枕无忧了,还是会有各种其他的安全漏洞。

美国最幸福、压力最小的工作是伐木工和农民

根据美国劳工统计局的调查,农业、伐木业和林业在所有主要行业类别中具有最高的自我报告的幸福水平,以及最低的自我报告的压力水平。压力最大的行业是包括金融和保险在内的行业,其次是教育和专业和技术行业。

消息来源:华盛顿邮报
老王点评:看看手里的键盘,突然不香了。

苹果公司因非法获取用户数据而被罚

法国数据保护机构 CNIL 周三对苹果公司处以 800 万欧元的罚款。CNIL 称,苹果公司未能 “在将用于广告目的的标识符存入法国 iPhone 用户(iOS 14.6 版本)的终端之前获得他们的同意”。在运行较低版本的 iOS 的 iPhone 中,苹果的个性化广告隐私设置是默认打开的。较新版本的 iPhone 操作系统已纠正了这一问题。

消息来源:Gizmodo
老王点评:虽然苹果公司在隐私方面已经算是不错了,但是也难免会有这种有意无意的错误。

Rustlings 是由 Rust 团队维护的开源项目,旨在帮助你通过调试代码的方式来学习 Rust。

Ferris the crab under the sea, unofficial logo for Rust programming language

在我上一篇 关于 Rustup 的文章 中,我向你们展示了如何安装 Rust 工具链。但是,如果不能上手操作一下 Rust 的话下载工具链又有什么用?学习任何语言都包括阅读现有的代码和写很多的示例程序,这是精通一门语言的好方法。然而,我们还可以走第三条路:调试代码。

通过调试来学习牵扯到尝试去编译一个已经写好的(满是漏洞的)示例程序,理解编译器生成的错误信息,修复示例代码,然后再重新编译。重复这个过程直到代码能够成功被编译并运行。

Rustlings 是一个由 Rust 团队维护的开源项目,旨在帮助你通过调试代码来学习 Rust。它也会一路为你提供提示。如果你是一名 Rust 初学者,并且刚开始阅读或已经读完了 Rust 书籍,那么 Rustlings 就是理想的下一步。Rustllings 帮助你将运用书中所学,并转向开发更大的项目。

安装 Rustlings

我使用(并推荐)Fedora 电脑来体验 Rustlings,但是任何 Linux 发行版都可以。要安装 Rustlings,你必须下载并运行它的安装脚本。通常建议你以不具备任何特别权限的普通用户(非 root 用户)来运行脚本。

记住,你需要 Rust 工具链来使用 Rustlings。如果你还没有这些工具链,请参考我 关于 Rustup 的文章

当你准备好时,下载这个安装脚本:

$ curl -L https://raw.githubusercontent.com/rust-lang/rustlings/main/install.sh  > rustlings_install.sh
$ file rustlings_install.sh
rustlings_install.sh: Bourne-Again shell script, ASCII text executable

阅读脚本以了解它会做什么:

$ less rustlings_install.sh

然后运行安装:

$ bash rustlings_install.sh
[...]
Installing /home/tux/.cargo/bin/rustlings
Installed package `rustlings v4.8.0 (/home/tux/rustlings)` (executable `rustlings`)
All done!

运行 rustlings 以开始。

Rustlings 练习

你现在可以使用命令 rustlings。与标志 --help 一起执行来查看可选的选项。

$ rustlings --help

这个安装脚本也克隆了 Rustlings 的 Git 仓库,并安装了运行示例程序所需的依赖。你可以在 ruslings 下的 exercises 目录查阅这些示例程序。

$ cd rustlings
$ pwd
/home/tux/rustlings
$ ls
AUTHORS.md  Cargo.toml        CONTRIBUTING.md  info.toml install.sh README.md  target Cargo.lock  CHANGELOG.md  exercises install.ps1  LICENSE src tests
$ ls -m exercises/
advanced_errors, clippy, collections, conversions, enums, error_handling, functions, generics, if, intro, macros, mod.rs, 
modules, move_semantics, option, primitive_types, quiz1.rs, quiz2.rs, quiz3.rs, quiz4.rs, README.md, 
standard_library_types, strings, structs, tests, threads, traits, variables

从命令行列出所有练习

命令 ruslings 提供给你一个 list 命令用以展示每个示例程序,它的完整路径,以及状态 (默认为 “待定”)。

$ rustlings list
Name         Path                                 Status
intro1       exercises/intro/intro1.rs            Pending
intro2       exercises/intro/intro2.rs            Pending
variables1   exercises/variables/variables1.rs    Pending
variables2   exercises/variables/variables2.rs    Pending
variables3   exercises/variables/variables3.rs    Pending
[...]

在显示结尾处,你会有一个进度报告用来追踪进度。

Progress: You completed 0 / 84 exercises (0.00 %).

查看示例程序

命令 rustlings list 向你展示了现有的程序,所以你可以在任何时候查看这些程序的代码,你只需要将完整路径复制到你的终端作为命令 cat 或者 less 的参数:

$ cat exercises/intro/intro1.rs

验证你的程序

现在你可以开始调试程序了。你可以使用命令 verify 来做这件事。注意 Rustlings 选择了列表里的第一个程序(intro1.rs)并尝试去编译它,最后编译成功:

$ rustlings verify
Progress: [-----------------------------------] 0/84
✅ Successfully ran exercises/intro/intro1.rs!

You can keep working on this exercise,
or jump into the next one by removing the `I AM NOT DONE` comment:

 6 |  // Execute the command `rustlings hint intro1` for a hint.
 7 |  
 8 |  // I AM NOT DONE
 9 |

正如你从结果中所见,尽管示例代码成功编译了,你依然需要做一些工作。每个示例程序的源文件中都带有以下注释:

$ grep "NOT DONE" exercises/intro/intro1.rs
// I AM NOT DONE

虽然第一个程序的编译没有问题,除非你去掉注释 I AM NOT DONE,Rustlings 不会移到下一个程序。

来到下一个练习

一旦你从 intro1.rs 中去掉这些注释,你就可以通过再一次运行命令 rustlings verify 来到下一个练习。这一次,你会发现 Rustlings 尝试去编译这个系列中的下一个程序(intro2.rs),但是遇到了一个错误。你应该调试并修复这个问题,并前进。这是你理解为什么 Rust 说程序有漏洞的至关重要的一步。

$ rustlings verify
Progress: [>------------------------] 1/84
⚠️  Compiling of exercises/intro/intro2.rs failed! Please try again. Here's the output:
error: 1 positional argument in format string, but no arguments were given
 --> exercises/intro/intro2.rs:8:21
  |
8 |         println!("Hello {}!");
  |                         ^^

error: aborting due to previous error

来点提示

Rustlings 有一个非常好用的 hint 参数,这个参数会告诉你示例程序中哪里出错了,以及如何去修复它。你可以认为这是在编译错误信息基础之上,一个额外的帮助选项。

$ rustlings hint intro2
Add an argument after the format string.

基于以上提示,修复这个程序就很简单了。你只需要在语句 println 中加一个额外的参数。这个 diff 对比应该能帮你理解发生的变化:

< println!("Hello {}!", "world");
---
> println!("Hello {}!");

一旦你做出了修改,并从源代码中去掉了注释 NOT DONE,你可以再一次运行 rustlings verify 来编译并运行代码。

$ rustlings verify
Progress: [>-------------------------------------] 1/84
✅ Successfully ran exercises/intro/intro2.rs!

追踪进度

你无法在一天之内做完所有的练习,忘记练到哪也很常见。你可以执行命令 list 来查看你的练习状态。

$ rustlings list
Name         Path                                  Status
intro1       exercises/intro/intro1.rs             Done   
intro2       exercises/intro/intro2.rs             Done   
variables1   exercises/variables/variables1.rs     Pending
variables2   exercises/variables/variables2.rs     Pending
variables3   exercises/variables/variables3.rs     Pending
[...]

运行特定的练习

如果你不想从头开始并且想要跳过一些练习,Rustlings 允许你使用命令 rustlings run 来专注特定的练习。如此可以运行指定的程序而不需要验证之前的课程。例如:

$ rustlings run intro2
Hello world!
✅ Successfully ran exercises/intro/intro2.rs
$ rustlings run variables1

敲入练习名字可能会变得乏味,但 Rustlings 为你准备了便利的命令 next 用来移向系列中的下一个练习。

$ rustlings run next

替代命令 watch

如果你不想在每次修改后还要敲一次 verify,你可以在终端窗口中运行命令 watch,然后再继续修改源代码以解决问题。命令 watch 会检测到这些修改,然后重新编译以查看这些问题是否被解决。

$ rustlings watch

通过调试学习

Rust 编译器以提供非常有意义的错误信息而被熟知,这些错误信息会帮助你理解在你代码中的问题。这通常意味着更快的调试。Rustlings 是练习 Rust,学会阅读错误信息,并理解 Rust 语言的优秀途径。来看看 GitHub 上 Rustlings 5.0.0 的最新功能吧。

下载 Rust 速查表

via: https://opensource.com/article/22/7/learn-rust-rustlings

作者:Gaurav Kamathe 选题:lkxed 译者:yzuowei 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Rust FFI 和 bindgen 工具是为 Rust 调用 C 库而设计的。Rust 很容易与 C 语言对话,从而与任何其它可以与 C 语言对话的语言对话。

为什么要从 Rust 调用 C 函数?简短的答案就是软件库。冗长的答案则触及到 C 在众多编程语言中的地位,特别是相对 Rust 而言。C、C++,还有 Rust 都是系统语言,这意味着程序员可以访问机器层面的数据类型与操作。在这三个系统语言中,C 依然占据主导地位。现代操作系统的内核主要是用 C 来写的,其余部分依靠汇编语言补充。在标准系统函数库中,输入与输出、数字处理、加密计算、安全、网络、国际化、字符串处理、内存管理等等,大多都是用 C 来写的。这些函数库所代表的是一个庞大的基础设施,支撑着用其他语言写出来的应用。Rust 发展至今也有着可观的函数库,但是 C 的函数库 —— 自 1970 年代就已存在,迄今还在蓬勃发展 —— 是一种无法被忽视的资源。最后一点是,C 依然还是编程语言中的 通用语:大部分语言都可以与 C 交流,透过 C,语言之间可以互相交流。

两个概念证明的例子

Rust 支持 FFI( 外部函数接口 Foreign Function Interface )用以调用 C 函数。任何 FFI 所需要面临的问题是调用方语言是否涵盖了被调用语言的数据类型。例如,ctypes 是 Python 调用 C 的 FFI,但是 Python 并没有包括 C 所支持的无符号整数类型。结果就是,ctypes 必须寻求解决方案。

相比之下,Rust 包含了所有 C 中的原始(即,机器层面)类型。比如说,Rust 中的 i32 类对应 C 中的 int 类。C 特别声明了 char 类必须是一个字节大小,而其他类型,比如 int,必须至少是这个大小(LCTT 译注:原文处有评论指出 int 大小依照 C 标准应至少为 2 字节);然而如今所有合理的 C 编译器都支持四字节的 int,以及八字节的 double(Rust 中则是 f64 类),以此类推。

针对 C 的 FFI 所面临的另一个挑战是:FFI 是否能够处理 C 的裸指针,包括指向被看作是字符串的数组指针。C 没有字符串类型,它通过结合字符组和一个非打印终止符(大名鼎鼎的 空终止符)来实现字符串。相比之下,Rust 有两个字符串类型:String&str (字符串切片)。问题是,Rust FFI 是否能将 C 字符串转化成 Rust 字符串——答案是 肯定的

出于对效率的追求,结构体指针在 C 中也很常见。一个 C 结构体在作为一个函数的参数或者返回值的时候,其默认行为是传递值(即,逐字节复制)。C 结构体,如同它在 Rust 中的对应部分一样,可以包含数组和嵌套其他结构体,所以其大小是不定的。结构体在两种语言中的最佳用法是传递或返回引用,也就是说,传递或返回结构体的地址而不是结构体本身的副本。Rust FFI 再一次成功处理了 C 的结构体指针,其在 C 函数库中十分普遍。

第一段代码案例专注于调用相对简单的 C 库函数,比如 abs(绝对值)和 sqrt(平方根)。这些函数使用非指针标量参数并返回一个非指针标量值。第二段代码案例则涉及了字符串和结构体指针,在这里会介绍工具 bindgen,其通过 C 接口(头文件)生成 Rust 代码,比如 math.h 以及 time.h。C 头文件声明了 C 函数的调用语法,并定义了会被调用的结构体。两段代码都能在 我的主页上 找到。

调用相对简单的 C 函数

第一段代码案例有四处 Rust 对标准数学库内的 C 函数的调用:两处分别调用了 abs(绝对值)和 pow(幂),两处重复调用了 sqrt(平方根)。这个程序可以直接用 rustc 编译器进行构建,或者使用更方便的命令 cargo build

use std::os::raw::c_int;  // 32位
use std::os::raw::c_double; // 64位

// 从标准库 libc 中引入三个函数。
// 此处是 Rust 对三个 C 函数的声明:
extern "C" {
  fn abs(num: c_int) -> c_int;
  fn sqrt(num: c_double) -> c_double;
  fn pow(num: c_double, power: c_double) -> c_double;
}

fn main() {
  let x: i32 = -123;
  println!("\n{x}的绝对值是: {}.", unsafe { abs(x) });

  let n: f64 = 9.0;
  let p: f64 = 3.0;
  println!("\n{n}的{p}次方是: {}.", unsafe { pow(n, p) });

  let mut y: f64 = 64.0;
  println!("\n{y}的平方根是: {}.", unsafe { sqrt(y) });

  y = -3.14;
  println!("\n{y}的平方根是: {}.", unsafe { sqrt(y) }); //** NaN = NotaNumber(不是数字)
}

顶部的两个 use 声明是 Rust 的数据类型 c_intc_double,对应 C 类型里的 intdouble。Rust 标准模块 std::os::raw 定义了 14 个类似的类型以确保跟 C 的兼容性。模块 std::ffi 中有 14 个同样的类型定义,以及对字符串的支持。

位于 main 函数上的 extern "C" 区域声明了 3 个 C 库函数,这些函数会在 main 函数内被调用。每次调用都使用了标准的 C 函数名,但每次调用都必须发生在一个 unsafe 区域内。正如每个新接触 Rust 的程序员所发现的那样,Rust 编译器极度强制内存安全。其他语言(特别是 C 和 C++)作不出相同的保证。unsafe 区域其实是说:Rust 对外部调用中可能存在的不安全行为不负责。

第一个程序输出为:

-123的绝对值是: 123.
9的3次方是: 729.
64的平方根是: 8.
-3.14的平方根是: NaN.

输出的最后一行的 NaN 表示 不是数字 Not a Number :C 库函数 sqrt 期待一个非负值作为参数,这使得参数 -3.14 生成了 NaN 作为返回值。

调用涉及指针的 C 函数

C 库函数为了提高效率,经常在安全、网络、字符串处理、内存管理,以及其他领域中使用指针。例如,库函数 asctime(ASCII 字符串形式的时间)期待一个结构体指针作为其参数。Rust 调用类似 asctime 的 C 函数就会比调用 sqrt 要更加棘手一些,后者既没有牵扯到指针,也不涉及到结构体。

函数 asctime 调用的 C 结构体类型为 struct tm。一个指向此结构体的指针会作为参数被传递给库函数 mktime(时间作为值)。此结构体会将时间拆分成诸如年、月、小时之类的单位。此结构体的 字段 field 类型为 time_t,是 int(32位)和 long(64 位)的别名。两个库函数将这些破碎的时间片段组合成了一个单一值:asctime 返回一个以字符串表示的时间,而 mktime 返回一个 time_t 值表示自 “ 纪元 Epoch 以来所经历的秒数,这是一个系统的时钟和时间戳的相对时间。典型的纪元设置为 1900 年或 1970 年,1 月 1 日 0 时 0 分 0 秒。(LCTT 校注:Unix、Linux 乃至于如今所有主要的计算机和网络的时间纪元均采用 1970 年为起点。)

以下的 C 程序调用了 asctimemktime,并使用了其他库函数 strftime 来将 mktime 的返回值转化成一个格式化的字符串。这个程序可被视作 Rust 对应版本的预热:

#include <stdio.h>
#include <time.h>

int main () {
  struct tm sometime;  /* 时间被打破细分 */
  char buffer[80];
  int utc;

  sometime.tm_sec = 1;
  sometime.tm_min = 1;
  sometime.tm_hour = 1;
  sometime.tm_mday = 1;
  sometime.tm_mon = 1;
  sometime.tm_year = 1; /*LCTT 校注:注意,相对于 1900 年的年数*/
  sometime.tm_hour = 1;
  sometime.tm_wday = 1;
  sometime.tm_yday = 1;

  printf("日期与时间: %s\n", asctime(&sometime));

  utc = mktime(&sometime);
  if( utc < 0 ) {
    fprintf(stderr, "错误: mktime 无法生成时间\n");
  } else {
    printf("返回的整数值: %d\n", utc);
    strftime(buffer, sizeof(buffer), "%c", &sometime);
    printf("更加可读的版本: %s\n", buffer);
  }

  return 0;
}

程序输出为:

日期与时间: Fri Feb  1 01:01:01 1901
返回的整数值: 2120218157
更加可读的版本: Fri Feb  1 01:01:01 1901

(LCTT 译注:如果你尝试在自己电脑上运行这段代码,然后得到了一行关于 mktime 的错误信息,然后又在网上随便找了个在线 C 编译器,复制代码然后得到了跟这里的结果有区别但是没有错误的结果,不要慌,我的电脑上也是这样的。导致本地机器上 mktime 失败的原因是作者没有设置 tm_isdst,这个是用来标记夏令时的标志。tm_isdst 大于零则夏令时生效中,等于零则不生效,小于零标记未知。加入 sometime.tm_isdst = 0= -1 后应该就能得到跟在线编译器大致一样的结果。不同的地方在于结果第一行我得到的是 Mon Feb ...,这个与作者代码中 sometime.tm_wday = 1 对应,这里应该是作者写错了;第二行我和作者和网上得到的数字都不一样,这大概是合理的,因为这与机器的纪元有关;第三行我跟作者的结果是一样的,1901 年 2 月 1 日也确实是周五,这是因为 mktime 其实会修正时间参数中不合理的地方。至于夏令时具体是如何影响 mktime 这个问题,我能查到的只有 mktime 的计算受时区影响,更底层的原因我也不知道了。)

总的来说,Rust 在调用库函数 asctimemktime 时,必须处理以下两个问题:

  • 将裸指针作为唯一参数传递给每个库函数。
  • 把从 asctime 返回的 C 字符串转化为 Rust 字符串。

Rust 调用 asctime 和 mktime

工具 bindgen 会根据类似 math.htime.h 之类的 C 头文件生成 Rust 支持的代码。下面这个简化版的 time.h 就可以用来做例子,简化版与原版主要有两个不同:

  • 内置类型 int 被用来取代别名类型 time_t。工具 bindgen 可以处理 time_t 类,但是会生成一些烦人的警告,因为 time_t 不符合 Rust 的命名规范:time_t 以下划线区分 timet;Rust 更偏好驼峰式命名方法,比如 TimeT
  • 出于同样的原因,这里选择 StructTM 作为 struct tm 的别名。

以下是一份简化版的头文件,mktimeasctime 在文件底部:

typedef struct tm {
  int tm_sec;  /* 秒 */
  int tm_min;  /* 分钟 */
  int tm_hour;   /* 小时 */
  int tm_mday;   /* 日 */
  int tm_mon;  /* 月 */
  int tm_year;   /* 年 */
  int tm_wday;   /* 星期 */
  int tm_yday;   /* 一年中的第几天 */
  int tm_isdst;  /* 夏令时 */
} StructTM;

extern int mktime(StructTM*);
extern char* asctime(StructTM*);

bindgen 安装好后,mytime.h 作为以上提到的头文件,以下命令(% 是命令行提示符)可以生成所需的 Rust 代码并将其保存到文件 mytime.rs

% bindgen mytime.h > mytime.rs

以下是 mytime.rs 中的重要部分:

/* automatically generated by rust-bindgen 0.61.0 */

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct tm {
  pub tm_sec: ::std::os::raw::c_int,
  pub tm_min: ::std::os::raw::c_int,
  pub tm_hour: ::std::os::raw::c_int,
  pub tm_mday: ::std::os::raw::c_int,
  pub tm_mon: ::std::os::raw::c_int,
  pub tm_year: ::std::os::raw::c_int,
  pub tm_wday: ::std::os::raw::c_int,
  pub tm_yday: ::std::os::raw::c_int,
  pub tm_isdst: ::std::os::raw::c_int,
}

pub type StructTM = tm;

extern "C" {
  pub fn mktime(arg1: *mut StructTM) -> ::std::os::raw::c_int;
}

extern "C" {
  pub fn asctime(arg1: *mut StructTM) -> *mut ::std::os::raw::c_char;
}

#[test]
fn bindgen_test_layout_tm() {
  const UNINIT: ::std::mem::MaybeUninit<tm> = ::std::mem::MaybeUninit::uninit();
  let ptr = UNINIT.as_ptr();
  assert_eq!(
  ::std::mem::size_of::<tm>(),
  36usize,
  concat!("Size of: ", stringify!(tm))
  );
  ...

Rust 结构体 struct tm,跟原本在 C 中的一样,包含了 9 个 4 字节的整型字段。这些字段名称在 C 和 Rust 中是一样的。extern "C" 区域声明了库函数 astimemktime 分别需要只一个参数,一个指向可变实例 StructTM 的裸指针。(库函数可能会通过指针改变作为参数传递的结构体。)

#[test] 属性下的其余代码是用来测试 Rust 版的时间结构体的布局。通过命令 cargo test 可以进行这些测试。问题在于,C 没有规定编译器应该如何对结构体中的字段进行布局。比如说,C 的 struct tm 以字段 tm_sec 开头用以表示秒;但是 C 不需要编译版本遵循这个排序。不管怎样,Rust 测试应该会成功,而 Rust 对库函数的调用也应如预期般工作。

设置好第二个案例并开始运行

bindgen 生成的代码不包含 main 函数,所以是一个天然的模块。以下是一个 main 函数初始化了 StructTM 并调用了 asctimemktime

mod mytime;
use mytime::*;
use std::ffi::CStr;

fn main() {
  let mut sometime  = StructTM {
    tm_year: 1,
    tm_mon: 1,
    tm_mday: 1,
    tm_hour: 1,
    tm_min: 1,
    tm_sec: 1,
    tm_isdst: -1,
    tm_wday: 1,
    tm_yday: 1
  };

  unsafe {
    let c_ptr = &mut sometime; // 裸指针

    // 调用,转化,并拥有
    // 返回的 C 字符串
    let char_ptr = asctime(c_ptr);
    let c_str = CStr::from_ptr(char_ptr);
    println!("{:#?}", c_str.to_str());

    let utc = mktime(c_ptr);
    println!("{}", utc);
  }
}

这段 Rust 代码可以被编译(直接用 rustc 或使用 cargo)并运行。输出为:

Ok(
    "Mon Feb  1 01:01:01 1901\n",
)
2120218157

对 C 函数 asctimemktime 的调用必须再一次被放在 unsafe 区域内,因为 Rust 编译器无法对这些外部函数的潜在内存安全风险负责。此处声明一下,asctimemktime 并没有安全风险。调用的两个函数的参数是裸指针 ptr,其指向结构体 sometime (在 stack 中)的地址。

asctime 是两个函数中调用起来更棘手的那个,因为这个函数返回的是一个指向 C char 的指针,如果函数返回 Mon 那么指针就指向 M。但是 Rust 编译器并不知道 C 字符串 (char 的空终止数组)的储存位置。是内存里的静态空间?还是 heap asctime 函数内用来储存时间的文字表达的数组实际上是在内存的静态空间里。无论如何,C 到 Rust 字符串转化需要两个步骤来避免编译错误:

  • 调用 Cstr::from_ptr(char_ptr) 来将 C 字符串转化为 Rust 字符串并返回一个引用储存在变量 c_str 中。
  • c_str.to_str() 的调用确保了 c_str 是所有者。

Rust 代码不会增加从 mktime 返回的整型值的易读性,这一部分留作课外作业给感兴趣的人去探究。Rust 模板 chrono::format 也有一个 strftime 函数,它可以被当作 C 的同名函数来使用,两者都是获取时间的文字表达。

使用 FFI 和 bindgen 调用 C

Rust FFI 和工具 bindgen 都能够出色地协助 Rust 调用 C 库,无论是标准库还是第三方库。Rust 可以轻松地与 C 交流,并透过 C 与其他语言交流。对于调用像 sqrt 一样简单的库函数,Rust FFI 表现直截了当,这是因为 Rust 的原始数据类型覆盖了它们在 C 中的对应部分。

对于更为复杂的交流 —— 特别是 Rust 调用像 asctimemktime 一样,会涉及到结构体和指针的 C 库函数 —— bindgen 工具是优秀的帮手。这个工具会生成支持代码以及所需要的测试。当然,Rust 编译器无法假设 C 代码对内存安全的考虑会符合 Rust 的标准;因此,Rust 必须在 unsafe 区域内调用 C。


via: https://opensource.com/article/22/11/rust-calls-c-library-functions

作者:Marty Kalin 选题:lkxed 译者:yzuowei 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Linux 6.1 发布,拉开 Rust 进入 Linux 内核的大幕

上周末,Linus Torvalds 发布了 Linux 内核 6.1,并开启了 6.2 的合并窗口。之所以专门提到 6.1 这个版本,是因为这个版本更具有里程碑意义。在 6.1 中,首次初步支持了 Rust,对 MGLRU 页面回收算法进行了大修,以及很多重要的特性和驱动更新。在 6.1 中没有任何供终端用户使用的 Rust 功能,而且在 6.2 中也将如此,会继续增加对开发者有用的功能,不会引入任何新的 Rust 编写的硬件驱动之类的东西。

消息来源:Phoronix
老王点评:终于尘埃落定了,以后随着内核中 Rust 代码的增多,Linux 内核的安全应该会上一个等级,就像安卓一样。

AlphaCode 是如何在编程竞赛中超过一半的程序员

AlphaCode 首先使用了 GitHub 上的庞大代码库进行训练,熟悉语法和编码规范。然后收集编程竞赛的数千个问题,训练它将问题描述翻译到代码。当遇到新问题时,AlphaCode 会编写出多达百万个候选编程方案,然后过滤掉不好的。它只保留通过测试用例的 1% 程序。为了进一步缩小范围,它会根据程序的输出与虚拟输入的相似性分群,从最大的群开始逐一递交程序,直到找到成功的程序或达到最大递交限额的 10 个程序。经过训练 AlphaCode 解决了 34% 的指定问题。

消息来源:《科学》杂志
老王点评:虽然是针对编程竞赛做的专门训练,但是完全可以针对某一类特定场景的需求进行编程,乃至于可以覆盖大多数常用场景的编程。我认为 AI 编程的时代的到来可能比我们大多数人预期的要快。

亚马逊计划干掉条形码

亚马逊称,条形码很难找到,而且可能被贴在形状怪异的产品上,这是机器人不能很好地解决的问题。因此,该公司说它有一个干掉条形码的计划。亚马逊利用仓库中物品的照片训练了一个计算机模型,让机器人在捡起和翻转物品时识别物品。这个被称为多模式识别的系统并不打算很快完全取代条形码,但该公司表示,它加快了处理包裹的时间。

消息来源:CNET
老王点评:确实,随着人工智能的发展,条形码可能就成了过渡性技术了。

回音

  • Chrome 110 中 取消 了对 JPEG-XL 的 支持,用户和开发者反对无效。