26、Sized trait

Sized trait

事实上,&str 远比表面上看起来更复杂,即使我们已经深入探讨过 Deref 强制转换机制之后也是如此。

根据我们之前对内存布局的讨论,人们可能会合理地预期 &str 在栈上应当只以一个 usize(即指针)的形式存在。然而实际上并非如此。&str 除了保存指向字符串数据的指针之外,还额外存储了一些元数据:它所指向的字符串 slice 的长度。回到前一节的例子中:

let mut s = String::with_capacity(5);
s.push_str("Hello");
// Create a string slice reference from the `String`, 
// skipping the first byte.
let slice: &str = &s[1..];

我们可以得到内存情况的图示:

&s[1..]的内存布局

这是怎么回事?

动态大小类型

str是一个动态大小类型(dynamically sized type(DST))

DST是一个在编译时不确定其大小的类型,当我们对DST有一个引用时,比如&str,它必须引入额外的关于它指向的数据的信息。这就是胖指针

&str的情况下,它存储它指向的切片的长度。我们之后可以看到更多的DST示例。

The Sized trait

Rust的标准库定义了叫做Sized的trait。

pub trait Sized {
    // This is an empty trait, no methods to implement.
}

 

编译时已知大小的数据类型就是Sized。换句话说Sized类型不是DST。

Marker trait

Sized是我们第一个marker trait的例子。

marker trait是一种不需要任何方法实现的trait。他没有任何行为。它仅用于标记一个类型具有某些特征。然后编译器根据这些标记启用某些行为或优化。

Auto trait

特别的,Sized也是一个Auto trait

我们不需要去显式地实现它,编译器会基于类型定义自动地为我们去实现它。

例子

我们之前看到的所有类型都是Sized类型:u32,String,bool等等。

 

str不是Sized

&str是Sized

 

我一开始还挺纳闷的,为什么String会是Sized呢?我猜这里应该特指栈上的数据,栈上的数据大小已知就是Sized,应该是说结构体这一块,字符串字面量的内存空间大小不也是已知的吗,所以我感觉怪怪的。问了一下AI,它又说&str没有实现Sized,奇怪了。之后我再研究一下!

 

哦哦,明白了,&str确实实现了Sized,他的字段是固定的。问题在于&str接受字符串字面量的时候,字符串字面量并不是存储在栈上,而是存储在静态存储的区域,&str到底它还是引用,它指向哪里是它的事情,它可以指向堆上的切片,也可以指向字符串字面量,大概就是这个意思吧。

练习

这次的练习并没有让我们做题,而是告诉了我们一件事情

image-20250910193635693

size_of方法,是获取栈上分配的内存大小的,适用于实现了Sized的类型,str没有实现Sized类型,于是报错,这个代码不合法!

 

更准确一些的解释:

std::mem::size_of 要求其类型参数必须是 Sized 的,因为它需要在编译期确定类型大小。而 str 是一个动态大小类型(DST),其大小在编译时未知,因此不实现 Sized。所以 size_of::<str>() 会导致编译错误。正确的做法是使用 &str 来获取指向字符串的引用的大小(如 size_of::<&str>()),但这也仅表示指针和长度的结构体大小(通常为 16 或 24 字节),不代表字符串内容的实际长度。

image-20250910193828566

确实,加了&可以正常通过,但是这段代码似乎也没有什么意义吧?

 

这一节的学习就到这里了。

 

阅读剩余
THE END