rust学习二十.12、RUST动态大小类型DST以及Sized特质

DST(dynamic size type)-中译“动态大小类型"。本文简要讨论动态大小类型的一些问题。

一、前言

rust作为一门静态类型语言,和大部分其它静态类型语言(C,C++,C#,JAVA)一样,希望在编译的时候知道每个实例/类型的大小。

作为静态类型语言,优点是毋庸置疑的的:

1.类型错误(如字符串与整数运算)在编译阶段即可被捕获,减少运行时崩溃风险

2.编译器可基于类型信息优化内存分配与代码执行效率

但无论哪一种静态类型语言,都有同样的问题:实际业务场景中,必然有动态大小的类型,那么应该如何处理了?

每个静态类型语言都有它的处理机制,但由于rust的设计哲学和目标,所以它的处理方式是非常特别的!

无论如何,rust必须能够处理动态大小类型,否则这个语言无法用(或者变为极其难用的玩具)。

二、RUST动态类型

如前,rust也有动态类型,例如常见的str。

只是可惜的是,我们常用的其实是&str,注意不是str。如果直接let a:str="abc"是报告编译错误的:

doesn't have a size known at compile-time
现在聊聊动态类型的几个问题.

2.1、如何处理动态大小类型

rust使用了新类型设计模式(个人更愿意看作是封装模式)来解决这个问题。

具体而言就是用智能指针来解决这个问题。

如我们所知,智能指针的典型结构:一个指向数据的指针、少量的其它元数据、额外实现的一些特质(例如Deref,Drop).

因此,编译器会把智能指针视为一个固定大小。 是的,String也可以看作式某种智能指针。

我们来看经典的Box盒子指针的定义:

pub struct Box<
    T: ?Sized,
    #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global,
>(Unique<T>, A);
#[lang = "ptr_unique"]
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>;
#[unstable(feature = "allocator_api", issue = "32838")]
pub unsafe trait Allocator {
    //此处略
}

注意,T是?Sized,意思T可以是固定大小或者动态大小。其次根据文档说明 ?T(T是特质)目前只能有?Sized,换言之,不会有?Drop,?Deref之类的申明。

在Box的底层通过 *const T(不可变原始指针)来指向实际的数据,整体上可以看作是固定大小的。

 

2.2、Sized特质

Sized特质,故名思意就是大小的意思,在rust中表示一个特质,表示被它绑定(限定)的类型是固定大小。rust编译器会为每一个添加了Sized特质的类型实现具体内容。

?Size则表示类型是可以固定也可以不是固定大小。其次根据文档说明 ?T(T是特质)目前只能有?Sized,换言之,不会有?Drop,?Deref之类的申明。

看看Box的定义就是知道类型是?Sized,而实践也告诉我们,可以在Box中存放标量类型和堆栈类型。

2.3、动态分发(dyn)

动态分发(dynamic dispatch),意思就是在运行的时候才确定特质对应的实际类型。

在编码的时候,如果使用指针存储一个特质,那么必须添加一个dyn,这样rustc通过编译,并在运行的时候,会把特质替换为实际的类型实例。

三、示例

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);
}

本例基本模仿了书本上的例子,演示了三种情况:

1.不能直接定义一个动态大小类型,否则编译器报错

2.如何用智能指针处理动态大小类型,以便可以通过编译

3.Sized特质的使用,以及如何使用Box存储特质。使用了dyn(动态分发)

动态分发(dyn)

函数fn feed_animal_dyn(animals: Vec<Box<dyn Animal>>)只接收Animal特质的Box指针向量,无法定义为:

fn feed_animal_dyn(animals: Vec<Box<Animal>>),这是因为特质本身是无意义的,必须和特定类型关联,要关联必然涉及到动态分发。

 

演示结果:

 

四、小结

1.绝大部分静态类型语言都支持固定大小类型和动态大小类型,包括rust

2.rust使用指针来解决动态大小类型的编译问题和运行问题

3.动态分发机制可以解决指针中定义特质的问题

posted @ 2025-04-08 19:20  正在战斗中  阅读(153)  评论(0)    收藏  举报