传递给 size_of_val 的双符号如何工作?

How does double ampersands passed to size_of_val work?

我读了 Apress 出版的一本书 Beginning Rust - Get Started with Rust 2021 Edition

在其中一个代码示例中,作者没有详细或清楚地解释代码是如何工作的。这是代码片段

/* In a 64-bit system, it prints:
16 16 16; 8 8 8
In a 32-bit system, it prints:
8 8 8; 4 4 4
*/
fn main() {
    use std::mem::*;
    let a: &str = "";
    let b: &str = "0123456789";
    let c: &str = "abcdè";
    print!("{} {} {}; ",
        size_of_val(&a),
        size_of_val(&b),
        size_of_val(&c));
    print!("{} {} {}",
        size_of_val(&&a),
        size_of_val(&&b),
        size_of_val(&&c));
}

我的问题是它是如何工作的,因为 size_of_val 需要引用并且这是在 &str 的声明中完成的。但是怎么打印出来!声明,作者在变量前放了另一个符号?除此之外,当我们传递变量时没有额外的符号,例如 size_of_val(a or b or c),我们得到的大小是 a 0,b 10 和 c 6,但是当我们传递带有符号的变量,例如size_of_val(&a or &b or &c),然后就像作者描述的主要功能上面的注释一样,大小为16 16 16或8 8 8。最后是第二次打印!语句(宏),作者把双&号放在参考的大小?它是如何工作的。只是不要明白,因为我认为这会产生错误,因为 size_of_val 只接受一个参考,但随后出现在打印中!宏有另一个和号,第二个宏有双和号...

size_of_val()函数是declared as follows:

pub fn size_of_val<T>(val: &T) -> usize
where
    T: ?Sized, 

这意味着:给定任何类型 T?Sized 约束意味着“真的任何类型,甚至是未定型的”),我们取一个参考T 并返回一个 usize.

a为例(bc是一样的)

当我们评估 size_of_val(a) 时,编译器知道 a 具有类型 &str,因此它推断泛型参数为 str(没有引用) ,所以完整的调用是 size_of_val::<str>(a /* &str */),它与签名匹配:我们为 T == str.

给出 &str

str 的大小是多少? str实际上是一个连续的字节序列,将字符串编码为UTF-8。 a 包含 "",空字符串,当然是零字节长。所以 size_of_val() returns 0。对于 b,有 10 个 ASCII 字符,每个都是一个字节长的 UTF8 编码,所以它们加起来有 10 个字节长。 C 包含 4 个 ASCII 字符 (abcd),即四个字节和一个 Unicode 字符 (è),它是两个字节宽,编码为 \xC3\xA8(十进制为 195 和 168)。所以总长度为六个字节。

当我们计算 size_of_val(&a) 时会发生什么? &a&&str 因为 a&str,所以编译器推断 T&str&str 的大小是常量并且总是指针大小的两倍:这是因为 &str,即指向 str 的指针应该包括数据地址和长度。在 64 位平台上,这是 16 (8 * 2);在 32 位的是 8 (4 * 2)。这称为 ,即除了地址之外还携带额外元数据的指针(请注意,保证 不会是长度的两倍,所以不要不依赖它,但实际上它是)。

当我们评估size_of_val(&&a)时,&&a的类型是&&&str,所以T被推断为&&str。虽然 &str(指向 str 的指针)是一个胖指针,这意味着它的大小增加了一倍,但指向胖指针的指针是一个普通的瘦指针(与胖指针相反:一个指针只携带地址,没有任何额外的元数据),这意味着它是一个机器字大小。所以 64 位平台为 8 个字节,32 位平台为 4 个字节。