rust学习二十.12、RUST动态大小类型DST以及Sized特质
<p>DST(dynamic size type)-中译“动态大小类型"。本文简要讨论动态大小类型的一些问题。</p><h1>一、前言</h1>
<p>rust作为一门静态类型语言,和大部分其它静态类型语言(C,C++,C#,JAVA)一样,希望在编译的时候知道每个实例/类型的大小。</p>
<p>作为静态类型语言,优点是毋庸置疑的的:</p>
<p><strong>1.<span style="color: rgba(51, 51, 51, 1); font-family: "PingFang SC", Arial, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; text-align: left; text-indent: 0; text-transform: none; word-spacing: 0; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgba(255, 255, 255, 1); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none">类型错误(如字符串与整数运算)在编译阶段即可被捕获,减少运行时崩溃风险</span></strong></p>
<p><strong>2.<span style="color: rgba(51, 51, 51, 1); font-family: "PingFang SC", Arial, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; text-align: left; text-indent: 0; text-transform: none; word-spacing: 0; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgba(255, 255, 255, 1); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none">编译器可基于类型信息优化内存分配与代码执行效率</span></strong></p>
<p>但无论哪一种静态类型语言,都有同样的问题:实际业务场景中,必然有动态大小的类型,那么应该如何处理了?</p>
<p>每个静态类型语言都有它的处理机制,但由于rust的设计哲学和目标,所以它的处理方式是非常特别的!</p>
<p>无论如何,rust必须能够处理动态大小类型,否则这个语言无法用(或者变为极其难用的玩具)。</p>
<h1>二、RUST动态类型</h1>
<p>如前,rust也有动态类型,例如常见的str。</p>
<p>只是可惜的是,我们常用的其实是&str,注意不是str。如果直接let a:str="abc"是报告编译错误的:</p>
<div style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1); font-family: Consolas, "Courier New", monospace; font-weight: normal; font-size: 14px; line-height: 19px; white-space: pre">
<div><span style="color: rgba(0, 128, 0, 1)">doesn't have a size known at compile-time</span></div>
<div>现在聊聊动态类型的几个问题.</div>
<h2>2.1、如何处理动态大小类型</h2>
<p>rust使用了新类型设计模式(个人更愿意看作是封装模式)来解决这个问题。</p>
<p>具体而言就是用智能指针来解决这个问题。</p>
<p>如我们所知,智能指针的典型结构:一个指向数据的指针、少量的其它元数据、额外实现的一些特质(例如Deref,Drop).</p>
<p>因此,编译器会把智能指针视为一个固定大小。 是的,String也可以看作式某种智能指针。</p>
<p>我们来看经典的Box盒子指针的定义:</p>
<pre class="language-rust highlighter-hljs"><code>pub struct Box<
T: ?Sized,
# A: Allocator = Global,
>(Unique<T>, A);
#
pub struct Unique<T: ?Sized> {
pointer: NonNull<T>,
// NOTE: this marker has no consequences for variance, but is necessary
// for dropck to understand that we logically own a `T`.
//
// For details, see:
// https://github.com/rust-lang/rfcs/blob/master/text/0769-sound-generic-drop.md#phantom-data
_marker: PhantomData<T>,
}
pub struct NonNull<T: ?Sized> {
pointer: *const T,
}
pub struct PhantomData<T: ?Sized>;
#
pub unsafe trait Allocator {
//此处略
}</code></pre>
<p>注意,T是?Sized,意思T可以是固定大小或者动态大小。其次根据文档说明 ?T(T是特质)目前只能有?Sized,换言之,不会有?Drop,?Deref之类的申明。</p>
<p>在Box的底层通过 *const T(不可变原始指针)来指向实际的数据,整体上可以看作是固定大小的。</p>
<p> </p>
<h2>2.2、Sized特质<br><br></h2>
<p>Sized特质,故名思意就是大小的意思,在rust中表示一个特质,表示被它绑定(限定)的类型是固定大小。rust编译器会为每一个添加了Sized特质的类型实现具体内容。</p>
<p>?Size则表示类型是可以固定也可以不是固定大小。其次根据文档说明 ?T(T是特质)目前只能有?Sized,换言之,不会有?Drop,?Deref之类的申明。</p>
<p>看看Box的定义就是知道类型是?Sized,而实践也告诉我们,可以在Box中存放标量类型和堆栈类型。</p>
<h2>2.3、动态分发(dyn)</h2>
<p>动态分发(dynamic dispatch),意思就是在运行的时候才确定特质对应的实际类型。</p>
<p>在编码的时候,如果使用指针存储一个特质,那么必须添加一个dyn,这样rustc通过编译,并在运行的时候,会把特质替换为实际的类型实例。</p>
</div>
<h1>三、示例</h1>
<pre class="language-rust highlighter-hljs"><code>trait Animal {
fn eat(&self);
}
struct Tiger {
name: String,
age: u8,
}
struct Pig {
name: String,
age: u8,
}
impl Animal for Tiger {
fn eat(&self) {
println!("{}岁{} 正在吃野猪", self.age, self.name);
}
}
impl Animal for Pig {
fn eat(&self) {
println!("{}岁{} 正在吃竹笋和地瓜", self.age, self.name);
}
}
impl Tiger {
fn clone(&self) -> Tiger {
Tiger {
name: self.name.clone(),
age: self.age,
}
}
}
fn feed_animal_dyn(animals: Vec<Box<dyn Animal>>) {
for animal in animals {
animal.eat();
}
}
/**
* Sized特质测试,告知编译器这个参数可以是固定大小也可以是动态大小,而非固定大小
*/
fn feed_animal<T: Sized>(animal: T)
where
T: Animal,
{
animal.eat();
}
fn main() {
//这样定义会报告编译错误:doesn't have a size known at compile-time
//let me:str="lzf";
dst_test();
dyn_test();
sized_test();
}
fn sized_test() {
let tiger = Tiger {
name: "大王🐯".to_string(),
age: 3,
};
let 武松的老虎 = tiger.clone();
feed_animal(武松的老虎);
}
/**
* 动态大小类型测试,使用&T和Box<T>来封装动态大小类型(DST)
*/
fn dst_test() {
//Box指针封装动态大小类型(DST).编译器认为Box指针式固定大小,典型的障眼法
let code: Box<str> = Box::from("Hello, world!");
println!("{}", code);
let name:Box<&str> = Box::from("狄仁杰");
println!("{}", name);
}
/**
* 动态分发测试,使用dyn告诉编译器这是一个动态分发,而非静态分发,是特质而不是其它的类型(stuct,enum等)
*/
fn dyn_test() {
let tiger = Tiger {
name: "松崽🐅".to_string(),
age: 10,
};
let pig = Pig {
name: "小胖🐖".to_string(),
age: 5,
};
// 必须使用as 关键字,将Tiger和Pig转换为特质对象(trait object)
// 必须使用dyn关键字,告诉编译器这是一个动态分发(dynamic dispatch),即在运行的时候才使用具体的类型
let animals = vec![
Box::new(tiger) as Box<dyn Animal>,
Box::new(pig) as Box<dyn Animal>,
];
feed_animal_dyn(animals);
}</code></pre>
<p>本例基本模仿了书本上的例子,演示了三种情况:</p>
<p>1.不能直接定义一个动态大小类型,否则编译器报错</p>
<p>2.如何用智能指针处理动态大小类型,以便可以通过编译</p>
<p>3.Sized特质的使用,以及如何使用Box存储特质。使用了dyn(动态分发)</p>
<p><strong>动态分发(dyn)</strong></p>
<p>函数fn feed_animal_dyn(animals: Vec<Box<dyn Animal>>)只接收Animal特质的Box指针向量,无法定义为:</p>
<p>fn feed_animal_dyn(animals: Vec<Box<Animal>>),这是因为特质本身是无意义的,必须和特定类型关联,要关联必然涉及到动态分发。</p>
<p> </p>
<p>演示结果:</p>
<p><img src="https://img2024.cnblogs.com/blog/1177268/202504/1177268-20250408191110728-206928788.png"></p>
<p> </p>
<h1>四、小结</h1>
<p>1.绝大部分静态类型语言都支持固定大小类型和动态大小类型,包括rust</p>
<p>2.rust使用指针来解决动态大小类型的编译问题和运行问题</p>
<p>3.动态分发机制可以解决指针中定义特质的问题</p>
</div>
<div id="MySignature" role="contentinfo">
<p>本文来自博客园,作者:正在战斗中,转载请注明原文链接:https://www.cnblogs.com/lzfhope/p/18815198</p><br><br>
来源:https://www.cnblogs.com/lzfhope/p/18815198
頁:
[1]