【译】Rust 中的 char 和 Go 中的 rune 有什么区别?

Rust 中的 char 和 Go 中的 rune 有什么区别?

在处理 UTF-8 文本方面,Rust 和 Go 有着类似的方式。Rust 给字符串类型提供了 .chars() 方法,这个方法返回字符序列(毫不奇怪)。另一方面, Go 提供了 []rune(str),它返回 rune 切片。这两者有何区别呢?

答案是 char 是一个 Unicode 标量值,而 rune 是 Unicode 码点。这也太。。没有用了吧。这两个有什么不同?

对于这个问题,一个糟糕但是比较正确的答案是 “Unicode 标量值是除了高代理码点和低代理码点之外的值”。啊,你可能需要一些上下文来理解它,所以我会尽我最大的努力由浅入深地解释。

unicode 的崇高目标是统一所有字符的数值表示方式。它们通过为每个字符分配一个位于 0~0x10FFFF 范围内的唯一整数来实现这一点。这个唯一的整数叫做码点/代码点(code point)[1]

代码点的概念是抽象的 —— 它们并不一定是存储在你的计算机上。为此,我们需要某种商定的方案来对 unicode 文本进行编解码。

简单的解决方案是使用足够大的整数类型来存储这些代码点。理论上最大代码点的值是 0x10FFFF(十进制值是 1,114,111),它可以使用 21 个比特位进行表示。大于 21 位的最小整数的类型是 u32,即 4 字节。这 4 个字节被称为单个单元或一个代码单元。[2]

这是一种真实存正的编码,叫做 UTF-32。这种编码的缺点是,我们需要 4 个字节来表示大多数英语文本,而以前只使用一个字节(ascii)即可。这意味着如果使用 UTF-32 编码,1GB 的 ASCII 文本将需要 4GB 空间。由于这个原因,于是大家又寻找更优的编码方案。

最流行方案的是 UTF-8, 你可能听说过。它使用 1~4 个字节来表示任意的 unicode 代码点。对于 UTF-8,代码单元是单个字节。web 上的大多数文本都是使用 UTF-8 方式编码的。在处理一个字节时,它可以告诉你需要处理多少个字节,就能得到你需要的代码点,得出它到底在什么范围内:

UTF-8 字节, 来自 https://en.wikipedia.org/wiki/UTF-8

另一种流行的编码方式是 UTF-16,它需要使用两个字节作为代码单元。大部分代码点使用一个代码单元就能表示,但另一些则需要两个代码单元。

可以使用单个代码单元进行编码的代码点称为 Basic Multilingual Plane(BMP)[3],它包含 0~0xFFFF 范围内的所有代码点。在此范围之外的代码点需要两个代码单元表示。

Basic Multilingual Plane, 来自 https://en.wikipedia.org/wiki/UTF-16

用来表示一个代码点的两个单元称为代理对。这一对中的第一个单元称为高位代理,第二个单元称为低位代理。从代码点到代理对需要进行一些良好定义的转换,但是这种转换的细节对我们的目的而言并不重要。

每个代理实际上都在 BMP 中!你可以在上面的图形中看到它们在 0xD8~0xDF 范围内。不过在 unicode 标准中存在着 UTF-16 编码,这有点奇怪。

这种 UTF-16 的存在,会导致在所有的环境中都必须以特殊方式对其进行处理。在 Rust 的例子中,它只是简单地报出异常说,高代理码点和低代理码点不是有效的字符。因此,Rust 中的 char 表示标量值[5]。

如果你尝试解码一个高、低位代理会发生什么?Rust 只是告诉你,它不是一个有效的字符:

fn main() {
    // 0xD800 是第一个高代理
    let c = core::char::from_u32(0xD800);
    println!("{:?}", c); // 打印 "None"
}

总结

  • Unicode 分配了一个唯一的数值作为每个字符的表示方式,称为代码点
  • 有很多个编码方式可以对代码点进行编码。UTF-16 是其中之一。
  • 16 比特位(UTF-16 编码方式的代码单元)不足以表示所有代码点,因而需要一对代码单元方可表示所有的代码点
  • 一对代码单元称为代理对
  • 对于表示某个代码点的代理对,其中的代码单元只有位于代理对中才有意义
  • Unicode 标量值指的是除代理对以外的所有代码点

参考

1.一个代码点是“Unicode 编码空间中的任意值,即 0~0x10FFFF 范围内的整数值” ↩︎

2.一个代码单元是指“用于处理或交换的编码了的最小 bit 组合” ↩︎

3.Plane 是指“65,536 个连续的 Unicode 代码点的范围” ↩︎

4一个代理对是指“两个 16 位代码单元组成的字符的表示” ↩︎

5.一个 Unicode 标量值是 “除了高位代理码点和低位代理码点外的所有代码点” ↩︎

posted @ 2020-10-17 14:19  suhanyujie  阅读(187)  评论(0编辑  收藏  举报