返回文章列表

文章

从零构建一个文件编辑器-2

本章主要介绍raw模式和打印输入字符以及错误处理的相关问题

部分内容超过 Notion API 单页读取上限,已尽力加载可访问内容。

目录
  1. Raw模式
  2. 进入Raw模式
  3. 显示按键
  4. 错误处理
  5. 总结与展望
  6. 📎 参考文章

Raw模式#

首先我们看如下代码:

use std::io::{self, Read};

fn main() {
    for b in io::stdin().bytes() {
        let c = b.unwrap() as char;
        println!("{}", c);
    }
}

尝试运行运行一下代码,我们可以发现,这段代码会显示我们在terminal中输入的内容:例如: 这里面有很多细节,例如,不会立即输出我们从键盘的输入,只会在我们输入内容后,按下回车键,才会输出我们输入的内容。 当我们输入Ctrl-C(Ctrl-D)才会退出(终止)整个程序。 这种行为对许多程序都很有用:它允许用户输入一行文本,然后Backspace修改错误,直到输入完全符合他们的要求,最后按下Enter将其发送到程序。这就是终端的工作方式,与 Rust 无关。但对于用户界面更复杂的程序(例如文本编辑器),这种方法效果不佳。我们希望在每次按键时都进行处理,以便能够立即响应。

进入Raw模式#

作为编辑器我们需要的是raw模式,在Rust中我们可以通过crossterm这个crate进入raw模式,首先添加项目依赖

cargo add crossterm

添加完毕后我们下面我们看看该如何使用crossterm,代码如下(绿色部分为新添加的代码,项目源码见参考文章): 在代码的第5行我们进入raw模式,在输入‘q’后第10行退出raw模式,进入熟模式。 尝试一下,你会发现你输入的每个字符都会立即打印出来,而且一旦你输入q,程序就会结束。 当前我们是使用crossterm这个crate的enable_raw_mode()函数直接进入raw模式的,这种方式很简洁,当然我们也可以自己编写rust的相关代码进入raw模式,但是这种方式比较复杂,我们看下enable_raw_mode() 的实现大概就知道了,或者看一下kilo的原始C语言实现。

显示按键#

为了更好地理解原始模式下输入的工作原理,让我们打印出读取到的每个字节。我们将打印每个字符的二进制表示、其 ASCII 值,以及如果它是可打印字符,则打印它所代表的字符。我们通过main()如下更改来实现这一点: 从本质上讲,字符只是数字,而数字在内存中内部表示为01(我们称之为“位”)。假设我们01100001的内存中有一个地方(我们称 8 位为一个字节,所以这是一个字节)。将二进制数转换为十进制数很容易,如果我们对 执行此操作01100001,我们会得到数字97。存在一个将这样的数字映射到字符的标准。这个标准称为 ASCII,当你在 ASCII 表中查找 97 时,结果为小写a。果然,如果我们a在新程序中输入,我们会看到打印出所有三项内容:a09701100001。 这也是为什么我们可以轻松地教我们的程序将我们从键盘读取的二进制文件视为字符的原因。基本上,我们只是告诉计算机“请将此数字视为一个字符”。底层二进制数仍然保持不变,但 Rust 明白我们想要遵循 ASCII 的约定,并a在我们要求时打印出一个。 ASCII 码 0-31 都是不可打印字符(上面提到的响铃符号、回车符和换行符也包含在内),127 也是控制字符。ASCII 码 32-126 都是可打印的。is_control()因此,只需查看代码的范围即可。

错误处理#

让我们通过改进错误处理的方式来结束本章(例如:我们以前没有处理过错误,但现在我们想要处理)。在这里,我将兑现我的承诺,并解释unwrap()我们在此过程中遇到的一些偶然问题。 首先介绍一些简单的理论。在许多其他编程语言中,如果函数遇到错误,它不会返回,而是“抛出异常”,你可以在上文中捕获该异常,然后尝试恢复。这意味着函数实际上有两种方式将控制权返回给调用者:要么返回值,要么抛出异常,而这两种方式都需要不同的代码来处理,这使得代码的推理变得更加困难。 在 Rust 中,你可以像返回其他任何返回值一样从函数中返回错误,从而以更简洁的方式处理异常情况。为了简洁地实现这一点,Rust 引入了Result的概念,它有两种变体:OkErr。以下是一个我们稍后会用到的比喻: 假设我欠你钱,你向我借钱。那么,我要么把钱还给你,要么告诉你,不行,我什么也没有。我这样告诉你,“请求欠款”是一个函数,它要么返回一些钱,要么抛出异常。请注意,如果我没有钱,返回 0 欧元,但如果我真的欠你钱,那么这个函数就无效了,所以在这里抛出异常更接近于获取欠款的心理模型。 现在,我不再给你钱,而是给你一个盒子。无论我有钱还是没钱,我都会这样做。如果我有你的钱,那是一个标着 的粉色盒子Ok。里面装着你需要的钱。否则,它是一个标着 的黑色盒子Err。盒子里装着我为什么什么都没给你的信息(我没有钱,或者钱?!) 现在.unwrap(),用比喻来说,假设盒子是粉色的,然后取出结果。如果盒子是黑色的,程序就会立即停止。 我们想要的行为是尽可能地将错误向上传递 - 让它们处理上面的黑匣子! - 并且在最高级别,当除了我们自己之外没有人可以将错误传递给时,我们最终会中止该程序。

总结与展望#

在本章中,我们学习了 Rust 中的依赖项、读取用户输入、错误处理的基础知识,以及大量关于终端和打字机工作原理的信息。 如果你觉得本章对编码的讲解比较少,那么你可以期待一下:下一章我们将构建文本编辑器的基础。

📎 参考文章#

未支持的 Notion 内容:external_object_instance 在 Notion 中打开